经典算法总结之线性时间做选择

来源:互联网 发布:c语言九九乘法表倒三角 编辑:程序博客网 时间:2024/06/06 07:07

http://www.cnblogs.com/javaspring/archive/2012/08/17/2656208.html

问题:

输入:一个包含n个(不同的)数的集合A和一个数i, 1 <= I <= n。

输出:元素x∈A, 它恰大于A中其他的I – 1个元素(即求第k小数)。

本博文中寻找最大的K个数(TOP K算法)这篇文章也用了本文中的算法,大家可以参考。

三种算法:

1、 直接排序,输出数组第i个元素即可, 时间复杂度为O(nlgn)

2、 这种算法,利用“快排的或者类似二分”的思想,每次以枢纽为界,分两边,每次只需处理一边即可(抛弃另一边),平均情况下的运行时间界为O(n),这种算法以期望时间做选择。《算法都论》里是,在分治时用随机数来选取枢纽(算法导论中伪代码见图),好吧,这是理论上的算法,它没有考虑实际产生随机数的开销,事实上,效率一点也不高,已经测试过,产生随机数花费的开销真的很大,后边我用更快的三数中值又实现了一遍,思想是一样的,只是效率提高了。

C++完整代码:

  1. #include <iostream>  
  2. #include <vector>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. int partition(vector<int> &A,int p,int r){  
  7.     int x = A[r];  
  8.     int i=p-1;  
  9.     int temp;  
  10.     for(int j = p;j<r;++j){  
  11.         if(A[j]<=x){  
  12.             ++i;  
  13.             swap(A[i],A[j]);  
  14.         }  
  15.     }  
  16.     swap(A[i+1],A[r]);  
  17.         return i+1;  
  18. }  
  19.   
  20. inline int Random(int low, int high) {      
  21.     return (rand() % (high - low + 1)) + low;  
  22. }   
  23.   
  24. int Randomized_Partition(vector<int> &kp, int low, int high) {      
  25.     int i = Random(low, high);     
  26.     swap(kp[high], kp[i]);     
  27.     return partition(kp, low, high);  
  28. }  
  29.   
  30. void randomized_quickSort(vector<int> &A,int p,int r){  
  31.     if(p<r){  
  32.         int q = Randomized_Partition(A,p,r);  
  33.         randomized_quickSort(A,p,q-1);  
  34.         randomized_quickSort(A,q+1,r);  
  35.     }  
  36. }  
  37.   
  38. int randomized_select(vector<int> A,int p,int r,int i){  
  39.     if(p==r)  
  40.         return A[p];  
  41.     if(p>r) return -1;  
  42.     int q = Randomized_Partition(A,p,r);  
  43.     int k = q-p+1;  
  44.     if(i==k)  
  45.         return A[q];  
  46.     else if(i<k)  
  47.         return randomized_select(A,p,q-1,i);  
  48.     else return randomized_select(A,q+1,r,i-k);  
  49. }  
  50.   
  51. void main(){  
  52.     int a[10] = {9,10,8,7,6,5,4,3,2,1};  
  53.     vector<int> A(a,a+10);  
  54.     cout<<randomized_select(A,0,9,5)<<endl;  
  55. }   

3、 第三种算法以最坏情况线性时间做选择,最坏运行时间为O(n),这种算法基本思想是保证每个数组的划分都是一个好的划分,以5为基,五数取分,这个算法,算法导论没有提供伪代码,额,利用它的思想,可以快速返回和最终中位数相差不超过2的数,这样的划分接近最优,基本每次都二分了(算法导论中步骤见图)

  1. /*利用中位数来选取枢纽元,这种方法最坏情况下运行时间是O(n)   
  2. 这里求的中位数是下中位数算法导论里没有伪代码,  
  3. 写起来很麻烦注意这里的查找到的中位数,  
  4. 并不是真正意义上的中位数而是和真正中位数相差不超过2的一个数开始以为我写错了  
  5. ,又看了算法导论,应该就是这个意思返回的是[x - 1, x + 2]的一个数,中位数是x从下边的输出中也可以看出:*/  
  6. #include<iostream>  
  7. #include<cstdio>  
  8. using namespace std;   
  9. const int maxn = 14;//kp -> size  
  10. const int maxm = maxn / 5 + 1;//mid -> size  
  11. int kp[maxn];int mid[maxm]; //插入排序  
  12. void InsertionSort(int kp[], int n) {  
  13.     for (int j, i = 1; i < n; i++) {   
  14.         int tmp = kp[i];          
  15.         for (j = i; j > 0 && kp[j - 1] > tmp; j--) {      
  16.             kp[j] = kp[j - 1];       
  17.         }    
  18.         kp[j] = tmp;    
  19.     }  
  20. } //查找中位数, 保证每一个划分都是好的划分  
  21. int FindMedian(int kp[], int low, int high) {      
  22.     if (low == high) {          
  23.         return kp[low];      
  24.     }      
  25.     int index = low;//index初始化为low        
  26.     //如果本身小于5个元素,这一步就跳过      
  27.     if (high - low + 1 >= 5) {         //储存中位数到mid[]        
  28.         for (index = low; index <= high - 4; index += 5) {         
  29.             InsertionSort(kp + index, 5);        
  30.             int num = index - low;           
  31.             mid[num / 5] = kp[index + 2];      
  32.         }    
  33.     }     //处理剩下不足5个的元素    
  34.     int remain = high - index + 1;     
  35.     if (remain > 0) {       
  36.         InsertionSort(kp + index, remain);         
  37.         int num = index - low;      
  38.         mid[num / 5] = kp[index + (remain >> 1)];//下中位数   
  39.     }      
  40.     int cnt = (high - low + 1) / 5;      
  41.     if ((high - low + 1) % 5 == 0) {        
  42.         cnt--;//下标是从0开始,所以需要-1    
  43.     }//存放在[0…tmp]     
  44.     if (cnt == 0) {        
  45.         return mid[0];      
  46.     } else {          
  47.         return FindMedian(mid, 0, cnt);       
  48.     }  
  49. } int Qselect(int kp[], int low, int high, int k) {    
  50.     int pivotloc = FindMedian(kp, low, high);    //这里有点不一样,因为不知道pivotloc下标,所以全部都要比较     
  51.     int i = low - 1, j = high + 1;   
  52.     for (; ;) {       
  53.         while (kp[++i] < pivotloc) {}     
  54.         while (kp[--j] > pivotloc) {}       
  55.         if (i < j)  swap(kp[i], kp[j]);      
  56.         else break;   
  57.     }     int num = i - low + 1;   
  58.     if (k == num) return kp[i];      
  59.     if (k < num) {       
  60.         return Qselect(kp, low, i - 1, k);    
  61.     } else {   
  62.             return Qselect(kp, i + 1, high, k - num);    
  63.     }  
  64. }  
  65. int main() {      
  66.     int kp[maxn] = {10, 14, 8, 11, 7, 1, 2, 13, 3, 12, 4, 9, 6, 5};     
  67.     for (int i = 0; i < maxn; i++) {        
  68.         printf("中位数是:  %d\n", FindMedian(kp, 0, maxn - 1));         
  69.         printf("第%d小的数是:  ", i + 1);       
  70.         cout <<  Qselect(kp, 0, maxn - 1, i + 1) << endl << endl;     
  71.     }      
  72.     return 0;  
  73. }  

0 0
原创粉丝点击