算法导论-顺序统计-快速求第i小的元素

目录                                                              

1、问题的引出-求第i个顺序统计量

2、方法一:以期望线性时间做选择

3、方法二(改进):最坏情况线性时间的选择

4、完整测试代码(c++)

5、参考资料

内容                                                               

1、问题的引出-求第i个顺序统计量                                        

   什么是顺序统计量?及中位数概念

   在一个由元素组成的集合里,第i个顺序统计量(order statistic)是该集合第i小的元素。例如,最小值是第1个顺序统计量(i=1),最大值是第n个顺序统计量(i=n)。一个中位数(median)是它所在集合的“中点元素”。当n为奇数时,中位数是唯一的;当n为偶数时,中位数有两个。问题简单的说就是:求数组中第i小的元素。

   那么问题来了:如何求一个数组里第i小的元素呢?

   常规方法:可以首先进行排序,然后取出中位数。由于排序算法(快排,堆排序,归并排序)效率能做到Θ(nlogn),所以,效率达不到线性;  在本文中将介绍两种线性的算法,第一种期望效率是线性的,第二种效率较好,是在最坏情况下能做到线性效率。见下面两个小节;

2、方法一:以期望线性时间做选择                                       

这是一种分治算法:以快速排序为模型:随机选取一个主元,把数组划分为两部分,A[p...q-1]的元素比A[q]小,A[q+1...r]的元素比A[q]大。与快速排序不同,如果i=q,则A[q]就是要找的第i小 的元素,返回这个值;如果i < q,则说明第i小的元素在A[p...q-1]里;如果i > q,则说明第i小的元素在A[q+1...r]里;然后在上面得到的高区间或者低区间里进行递归求取,直到找到第i小的元素。

下面是在A[p...q]中找到第i小元素的伪码:

复制代码
1 RandomSelect(A,p, q,k)//随机选择统计,以期望线性时间做选择
2 {
3     if (p==q) return A[p];
4     int pivot=Random_Partition(A,p,q);//随机选择主元,把数组进行划分为两部分
5     int i=pivot-p+1;
6     if (i==k )return A[pivot];
7     else if (i<k) return RandomSelect(A,pivot+1,q,k-i);//第k小的数不在主元左边,则在右边递归选择
8     else return RandomSelect(A,p,pivot-1,k);//第k小的数不在主元右边,则在左边递归选择
9 }
复制代码

在最坏情况下,数组被划分为n-1和0两部分,而第i个元素总是落在n-1的那部分里,运行时间为Ө(n^2);但是,除了上述很小的概率情况,其他情况都能达到线性;在平均情况下,任何顺序统计量都可以在线性时间Θ(n)内得到。

实现代码(c++):

  View Code

3、方法二(改进):最坏情况线性时间的选择                       

      相比于上面的随机选择,我们有另一种类似的算法,它在最坏情况下也能达到O(n)。它也是基于数组的划分操作,而且利用特殊的手段保证每次划分两边的子数组都比较平衡;与上面算法不同之处是:本算法不是随机选择主元,而是采取一种特殊的方法选择“中位数”,这样能使子数组比较平衡,避免了上述的最坏情况(Ө(n^2))。选出主元后,后面的处理和上述算法一致。

     那么问题又来了,这种特殊的手段是什么呢?

如上图所示:

1) 将输入数组的n个元素划分为n/5组,每组(上图中的每列为一组)5个元素,且至多只有一个组有剩下的n%5个元素组成

2)  首先对每组中的元素(5个)进行插入排序,然后从排序后的序列中选择出中位数(图中黄色数)。

3) 对第2步中找出的n/5个中位数,递归调用SELECT以找出其中位数x(图中红色数)。(如果有偶数个中位数取较小的中位数)

这三个步骤就可以选出一个很好的主元,下面的处理和方法一一致(递归)

OK! 下面是完整的算法步骤:

1)  将输入数组的n个元素划分为n/5组,每组(上图中的每列为一组)5个元素,且至多只有一个组有剩下的n%5个元素组成

2)  首先对每组中的元素(5个)进行插入排序,然后从排序后的序列中选择出中位数(图中黄色数)。

3) 对第2步中找出的n/5个中位数,递归调用SELECT以找出其中位数x(图中红色数)。(如果有偶数个中位数取较小的中位数)

4) 调用PARTITION过程,按照中位数x对输入数组进行划分。确定中位数x的位置k。

5) 如果i=k,则返回x。否则,如果i<k,则在地区间递归调用SELECT以找出第i小的元素,若干i>k,则在高区找第(i-k)个最小元素。

大致伪码:

复制代码
 1 WorseLinearSelect(vector<T> &A,int p,int q,int k)
 2 {
 3     // 将输入数组的n个元素划分为n/5(上取整)组,每组5个元素,
 4     // 且至多只有一个组有剩下的n%5个元素组成。
 5     if (p==q) return A[p];
 6 
 7     int len=q-p+1;
 8     int medianCount=1;
 9     if (len>5)
10         medianCount = len%5 >0 ? len/5 + 1 : len/5;
11     vector<T> medians(medianCount);//存放每组的中位数
12 
13     // 寻找每个组的中位数。首先对每组中的元素(至多为5个)进行插入排序,
14     // 然后从排序后的序列中选择出中位数。
15     int m=p;
16     for (int j=0,m=p;j<medianCount-1;j++)
17     {
18         medians[j] = GetMedian(A,m,m+4);
19         m+=5;
20     }
21     medians[medianCount-1] = GetMedian(A,m,q);
22     //对第2步中找出的n/5(上取整)个中位数,递归调用SELECT以找出其中位数pivot。
23     //(如果是偶数去下中位数)
24     int pivot = WorseLinearSelect(medians,0,medianCount-1,(medianCount+1)/2);
25     //调用PARTITION过程,按照中位数pivot对输入数组进行划分。确定中位数pivot的位置r。
26     int r = partitionWithPivot(A,p,q,pivot);
27     int num = r-p+1;
28     //如果num=k,则返回pivot。否则,如果k<num,则在地区间递归调用SELECT以找出第k小的元素,
29     //若干k>num,则在高区找第(k-num)个最小元素。
30     if(num==k) return pivot;
31     else if (num>k) return WorseLinearSelect(A,p,r-1,k);
32     else return WorseLinearSelect(A,r+1,q,k-num);
33 }
复制代码

该算法在最坏情况下运行时间为Θ(n)

代码实现(c++):

  View Code

4、完整测试代码(c++)                                                  

完整源码下载地址Github

Select.h

  View Code

main.cpp

  View Code

5、参考资料                                                                    

【1】http://blog.csdn.net/xyd0512/article/details/8279371

【2】http://blog.chinaunix.net/uid-26822401-id-3163058.html

【3】http://www.tuicool.com/articles/mqQBfm

【4】http://www.cnblogs.com/Anker/archive/2013/01/25/2877311.html


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值