顺序统计与中值
问题:已知n个未排序的数,求找出其中第k小的数,即排名第k的数。(k<n)
最简单的方法:将n个数排序,然后返回A[k]。但已经证明常见的排序算法(比如快排等)最快也就是O(nlogn)。那么我们可以做的更好,比如在线性时间O(n)里解决该问题吗?
这种问题是比较常见的,比如要找出最大的数,最小的数,以及中位数等等,现在我们尝试在线性时间内找出中位数
随机的分治算法
该方法非常类似于随机化快速排序,其分析过程也跟随机化快速排序类似
//随机产生一个从low到high之间的数 int random(int low,int high){ srand((unsigned)time(NULL)); return rand()%(high-low+1)+low; } //基于随机化的主元划分过程 int Randomized_Partition(int array[],int start,int end){ swap(&array[start],&array[random(start,end)]); int key = array[start]; int i=start; int j= i+1; while(j<=end){ if(array[j]<=key){ swap(&array[++i],&array[j++]); } else{ j++; } } swap(&array[i],&array[start]); return i; } //在数组array中从p到q中找出第i小的元素 int Random_Select(int array[],int p,int q,int i){ if(p == q) return array[p]; int r = Randomized_Partition(array,p,q); int k = r - p + 1; //k是划分元素的序号 if(i==k) return array[r]; if(i<k) return Random_Select(array,p,r-1,i); else return Random_Select(array,r+1,q,i-k); }
举一个简单的例子:在下面的数组中找出第7小的数
6,10,13,5,8,3,2,11
首先第一步是找出划分主元,原算法中是一个随机化选择的过程,简单起见就选择第一个元素“6”好了,经过一个类似快速排序的过程,数组变成如下形式
2,5,3,6,8,13,10,11
经过一个划分过程,“6”这个元素落在了第4的位置,且左边的元素都小于6,右边的元素都大于6。这样的一个过程保证了:即使原数组经过排序后,“6”也依然会处在第4的位置。这时返回的r=3 (第一个数的下标为0)。我们找到了第4小的数,现在要我们找出第7小的数,这就表示要找的数比6大,它处在6右边的位置,那么6左边的数对我们来讲就没有用处了,那么就去掉左边的4个数,然后递归的在右边找地(7-4)=第3小的数,即使原序列中的第7小的数。
现在,我们来证明为什么该算法是线性的时间的,该算法的分析过程和随机化快速排序很类似
首先考虑该算法的最好情况下的运行时间和最坏情况下的运行时间
最好情况:每次划分主元恰好处在数组的中间位置,得到递归式
T(n)=T(n/2)+O(n)
根据前面文章中已经讨论过的求解递归式的方法,使用主方法中的方法三,得到该递归式的时间复杂度T(n)=O(n)
最坏情况:每次划分主元,都得到0:n-1的结果,得到递归式
T(n)=T(n-1)+O(n)
这是一个求n个元素的和等差数列,得到T(n)=O(n2)
可见在最差情况下该算法还不如一个nlogn的先排序后输出的算法,不过好在最坏情况产生的概率是非常小的。
平均情况:找出所有产生的情况,求期望值,又用到指示器随机变量(Indicator random variables)
使用指示器随机变量,可以帮助我们处理的不只是函数T(n),而且是一个随机变量,T(n)依赖于随机的选择,然后使用指示器随机变量为的是写出T(n)的递归式。指示器随机变量的定义如下:
1 (划分后左边数组的长度为k,右边数组的长度为n-k-1,即两边长度的比为k:n-k-1)
Xk = {
0 (其他情况)
这样便可以得到T(n)的定义
T[max{0,n-1}]+O(n) (划分情况为0:n-1)
T[max{1,n-2}]+O(n) (划分情况为1:n-2)
T(n)≤{ T[max{2,n-3}]+O(n) (划分情况为2:n-3)
…………………………
T[max{n-1,0}]+O(n) (划分情况为n-1:0)
现在我们得到了不同分割对应的情况,但我们有指示器随机变量Xk来标记每种情况什么时候会发生,我们只需要把每种情况下计算得到的值乘以指示器随机变量Xk,不对应当前分割的情况时Xk的值为0,对应当前分割情况时Xk等于1,因此把所有加起来也是一样的。
T(n)=∑ Xk(T[max{k,n-k-1}]+O(n)) (∑表示k从0到n-1求和)
然后对T(n)求期望值
E(T(n))=E(∑ Xk(T[max{k,n-k-1}]+O(n))) 根据线性关系,求和符号可提出来放到外面
=∑(E(Xk(T[max{k,n-k-1}]+O(n))) 求两个随机变量的积的期望,而这两个随机变量是相互独立的(因为算法前提是选择的随机数字都是相互独立的,所有Xk的值取决于划分时产生的随机数,他们之间是相关的,而T[max{k,n-k-1}]是每次递归产生一个随机数,按照规定每次产生的随机数都会独立于首次调用时产生的随机数,即Xk),因此他们的积的期望就等于他们的期望的积。
=∑(E(Xk)*E(T[max{k,n-k-1}]+O(n))) Xk的期望是1/n
=1/n∑E(T[max{k,n-k-1}])+1/n∑O(n) 其中1/n∑O(n) = O(n),然后去掉max,注意到max过程是一个对称的过程,T[max{0,n-1}]和T[max{n-1,0}]是等价的
=2/n∑E[T(k)] + O(n) ∑表示k从n/2到n-1
然后就没法再化简了,接下来就要用代换法来证明了。好在我们已经知道了结论,
我们要证明:E[T(n)]≤cn (c>0) 已知:
E[T(n)] = 2/n∑E[T(k)] + O(n) ∑表示k从n/2到n-1
≤ 2/n∑ck + O(n)
=2c/n∑k + O(n) 这里的求和结果大概为3/8*n2
≤3cn/4 + O(n)
=cn-(cn/4-O(n))
这样只要取一个足够大的c,该不等式就能成立,因此该算法的期望时间就是T(n)=O(n)
那么有什么方法能让该算法在最坏情况下的时间复杂度也是线性的呢?计算机界的几个大牛共同想出了一个方法,该算法复杂,抽象,难以理解,堪称最难理解的算法之一。该算法保证选择的主元总是处于数组的中间位置,即在O(n)的时间里找到一个处于中间的划分主元。发明该算法的牛人有:
Manuel Blum:因奠定了计算复杂性理论的基础和在密码术及程序校验方面的贡献而获得1995年图灵奖
Robert W. Floyd:因(词法)分析理论,编程语言语义,自动程序验证,自动程序综合生成和算法分析上的贡献而获得1978年图灵奖
Ronald Linn Rivest:就是著名的RSA里的那个R,是2002年图灵奖得住
Vaughan Ronald Pratt:著名的KMP算法里的P就是他
Robert Endre Tarjan:1986年获得图灵奖得主
反正这个算法我是没看懂……
算法论文在这儿
http://people.csail.mit.edu/rivest/BlumFloydPrattRivestTarjan-TimeBoundsForSelection.pdf
posted on 2012-07-09 01:30 len_sround 阅读(398) 评论(0) 编辑 收藏 举报