《算法导论》— Chapter 9 中位数和顺序统计学
序
在算法导论的第二部分主要探讨了排序和顺序统计学,第六章~第八章讨论了堆排序、快速排序以及三种线性排序算法。该部分的最后一个章节,将讨论顺序统计方面的知识。
在一个由n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。正如我们经常遇到的中位数问题,一个中位数是它所在集合中的“中点元素”。对于一个有序元素序列,当元素个数为奇数时,中位数位于 i = (n+ 1)/ 2 位置,当元素个数为偶数时,中位数又有下中位数 i = (n+1)/2 取下限 和上中位数 i = (n+1)/2 取上限。
本章讨论的是从一个由n个不同数值构成的集合中选择第i个顺序统计量的问题。
以期望线性时间做选择
该选择问题定义如下:
输入:一个包含n个不同数的集合A和一个数i , 1<= i <= n
输出:元素x ,它恰大于该集合中其它i-1个元素
虽然该问题可以利用堆排序、合并排序或者快速排序得到有序序列,然后直接返回下标为i个元素,得到时间复杂度为O(nlogn)的算法,但是本节介绍的是一个更快的算法,其平均时间复杂度为O(n)
该算法的实现,与快速排序有一定的类似,同样需要对输入序列设置主元,得到分割点,然后根据分割点元素值得到其是第k小元素,对比k与i的值,得到待求元素。
具体程序实现如下:
/**
* 线性时间做选择
*/
#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
using namespace std;
//选择第i个大小的元素算法声明
int RandomizedSelect(int *data, int l, int h, int i);
//求分割点
int partition(int * array, int low, int high);
//以low ~ high 之间的一个随机元素作为主元 , 求分割点
int RandomPartition(int *array, int low, int high);
//交换两个变量的值
void exchange(int &a, int &b);
int main()
{
//声明一个待排序数组
int array[N];
//设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "输入原序列:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
}
cout << endl << "求第 6 小的元素是:RandomizedSelect(array , 0 , N-1 , 6) = " ;
//调用随机线性选择算法
cout << RandomizedSelect(array, 0, N - 1 , 6);
cout << endl;
system("pause");
return 0;
}//main
int RandomizedSelect(int *data, int l, int h, int i)
{
//如果输入序列中仅有一个元素
if (l == h)
return data[l];
//求分割点pos,该位置左边元素均小于data[pos] , 右边元素均大于data[pos]
int pos = RandomPartition(data, l, h);
//求该分割点是第几小元素
int k = pos - l + 1;
//如果就是当前第i小元素,则返回data[pos]
if (k == i)
return data[pos];
else if (k < i)
return RandomizedSelect(data, pos + 1, h, i - k);
else
return RandomizedSelect(data, l, pos - 1, i);
}
int partition(int * array, int low, int high)
{
int i = low - 1;
//默认将划分段的最后一个元素为主元
int x = array[high];
for (int j = low; j<high; j++)
{
if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
{
i += 1;
exchange(array[i], array[j]);
}
}
exchange(array[i + 1], array[high]);
return i + 1;//所以循环完毕后,i+1就是该数组的分割点
}
int RandomPartition(int *array, int low, int high)
{
//找到low ~ high 之间的一个随机位置
int i = rand() % (high - low + 1) + low;
//交换该随机主元至尾部,
exchange(array[i], array[high]);
return partition(array, low, high);
}
void exchange(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
以上即是线性时间选择算法,在平均情况下,任何顺序统计量(特别是中位数)都可以在线性时间内得到。
最坏情况下线性时间的选择
在上一节中介绍的选择算法,平均情况下为O(n)的复杂度,本节介绍一个最坏情况下运行时间为O(n)的新的选择算法SELECT。该算法同样也取自了快速排序的划分算法Partition,作了相应修改,将主元元素作为函数的一个参数。
算法SELECT的执行步骤确定一个n>1个元素的输入数组中第i小的元素。
具体步骤见下程序实现:
/**
* 最坏情况线性时间的选择
*/
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
//快排的求分割点算法
int Partition(int * array, int low, int high);
//SELECT中修改的分割算法,以大小为val的元素作为主元
int _Partition(int *data, int l, int h, int val);
//线性选择算法,从下标l~h的data序列中找出第i小的元素
int Select(int *data, int l, int h, int i);
//求中位数的中位数,对输入序列分为n/5组,每组5个元素
int Find(int *data, int low, int high);
//交换两个变量的值
void exchange(int &a, int &b);
const int N = 10;
const int MAX = 101;
int main()
{
//声明一个待排序数组
int array[N];
//设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "输入原序列:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
}
cout << endl << "求第 6 小的元素是:RandomizedSelect(array , 0 , N-1 , 6) = ";
//调用随机线性选择算法
cout << Select(array, 0, N - 1, 6);
system("pause");
return 0;
}
int Partition(int * array, int low, int high)
{
int i = low - 1;
//默认将划分段的最后一个元素为主元
int x = array[high];
for (int j = low; j<high; j++)
{
if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
{
i += 1;
exchange(array[i], array[j]);
}
}
exchange(array[i + 1], array[high]);
return i + 1;//所以循环完毕后,i+1就是该数组的分割点
}
void exchange(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
//SELECT中修改的分割算法,以大小为val的元素作为主元
int _Partition(int *data, int l, int h, int val)
{
for (int i = l; i <= h; i++)
{
if (data[i] == val)
{
exchange(data[i], data[h]);
break;
}
}
return Partition(data, l, h);
}
//线性选择算法,从下标l~h的data序列中找出第i小的元素
int Select(int *data, int l, int h, int i)
{
//如果数组中只有一个元素,直接返回该元素
if (l == h)
return data[l];
//步骤1、2、3 将数组n个元素划分为n/5组;选出每组中位数;然后从5个中位数中选出中位数
int value = Find(data , l , h);
//步骤4 以此中位数为主元,划分输入序列
int pos = _Partition(data, l, h, value);
//步骤5 判断该分割点元素是否为第i个元素
int k = pos - l + 1;
if (k == i)
return data[pos];
else if (k < i)
return Select(data, pos + 1, h, i - k);
else
return Select(data, l, pos - 1, i);
}
int Find(int *data, int low, int high)
{
int mid[N] = { 0 }, k = 0;
for (int m = low , n = low+4 ; m < high; m+=5)
{
if (n >= high || m + 4 >= high)
n = high;
else
n = m + 4;
//对每组元素按照插入排序,求每组的中位数,加入数组mid
for (int j = m+1; j <= n; j++)
{
int key = data[j];
int i = j - 1;
while (i >= m && data[i] > key)
{
data[i + 1] = data[i];
i = i - 1;
}
data[i+1] = key;
}
cout << endl;
mid[k++] = data[(n + m) / 2];
}//for
return Select(mid, 0, k-1, (k+1) / 2);
}
总结
本章中选择算法之所以具有线性运行时间,是因为这些算法没有进行排序;线性时间的行为并不是类似第八章中的排序算法因为对输入做假设所得到的结果。
《算法导论》— Chapter 9 中位数和顺序统计学的更多相关文章
- 算法导论 第九章 中位数和顺序统计量(python)
第i个顺序统计量:该集合中第i小的元素(建集合排序后第i位 当然算法可以不排序) 中位数:集合中的中点元素 下中位数 上中位数 9.1最大值和最小值 单独的max或min每个都要扫一遍 n-1次比较 ...
- (搬运)《算法导论》习题解答 Chapter 22.1-1(入度和出度)
(搬运)<算法导论>习题解答 Chapter 22.1-1(入度和出度) 思路:遍历邻接列表即可; 伪代码: for u 属于 Vertex for v属于 Adj[u] outdegre ...
- 《算法导论》 — Chapter 7 高速排序
序 高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组.最坏情况执行时间为O(n^2). 尽管这个最坏情况执行时间比較差.可是高速排序一般是用于排序的最佳有用选择.这是由于其平均 ...
- 《算法导论》 — Chapter 8 线性时间排序
序 到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序.合并排序.堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势.它们都有一个相同的特点,以上所有排序的结果序列,各个 ...
- 《算法导论》— Chapter 15 动态规划
序 算法导论一书的第四部分-高级设计和分析技术从本章开始讨论,主要分析高效算法的三种重要技术:动态规划.贪心算法以及平摊分析三种. 首先,本章讨论动态规划,它是通过组合子问题的解而解决整个问题的,通常 ...
- 《算法导论》 — Chapter 7 快速排序
序 快速排序(QuickSort)也是一种排序算法,对包含n个数组的输入数组,最坏情况运行时间为O(n^2).虽然这个最坏情况运行时间比较差,但是快速排序通常是用于排序的最佳实用选择,这是因为其平均性 ...
- 《算法导论》— Chapter 11 散列表
1 序 在很多应用中,都要用到一种动态集合结构,它仅支持INSERT.SEARCH以及DELETE三种字典操作.例如计算机程序设计语言的编译程序需要维护一个符号表,其中元素的关键字为任意字符串,与语言 ...
- B树——算法导论(25)
B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...
- 基本数据结构(2)——算法导论(12)
1. 引言 这一篇博文主要介绍链表(linked list),指针和对象的实现,以及有根树的表示. 2. 链表(linked list) (1) 链表介绍 我们在上一篇中提过,栈与队 ...
随机推荐
- TC学习总结
带宽管理: TC中规定描述带宽: mbps = 1024 kbps = 1024 * 1024 bps => byte/s mbit = 1024 kbit => kilo bit/s m ...
- Jmeter安装说明
本文主要介绍Jmeter工具的安装 一.安装JDK 1.下载jdk,到官网下载jdk,下载jkd1.8即可,地址:http://www.oracle.com/technetwork/java/java ...
- Educational Codeforces Round 24 A
There are n students who have taken part in an olympiad. Now it's time to award the students. Some o ...
- 关于HashMap中hash()函数的思考
关于HashMap中hash()函数的思考 JDK7中hash函数的实现 static int hash(int h) { h ^= (h >>> 20) ^ (h >&g ...
- 【学习笔记】深入理解js原型和闭包(3)——prototype原型
既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...
- Android源码分析笔记--Handler机制
#Handler机制# Handler机制实际就是实现一个 异步消息循环处理器 Handler的真正意义: 异步处理 Handler机制的整体表述: 消息处理线程: 在Handler机制中,异步消息处 ...
- 《Python基础教程》 读书笔记 第五章(上)条件语句
5.1 print和import的更多信息 5.1.1使用逗号输出 打印多个表达式也是可行的,只要将它们用逗号隔开就好: >>>print'age:',42 age: 42 要同时输 ...
- sqlserver2012 offset
/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public Lic ...
- zipkin 服务追踪
服务追踪,就是对请求接口的追踪并保存. 在测试的过程中我们会发现,有时候,程序刚刚启动后,刷新几次,并不能看到任何数据,原因就是我们的spring-cloud-sleuth收集信息是有一定的比率的,默 ...
- zabbix利用微信报警
一.背景介绍 报警的方式有很多种,比如常用的邮件报警,短信,微信等.那这篇文章主要是实现微信报警. 值得注意的是,之前使用微信企业公众号,现在微信企业公众号更新成企业微信了.所以直接注册企业微信,进入 ...