选择问题
选择问题即:寻找N个元素中的第K个最大者。
选择问题的特殊情况是找最大者或最小者,这当然很简单了。还是一个特例找中位数。
《寻找N个元素中的前K个最大者》方法总结是在这里看到的 http://zhangliang008008.blog.163.com/blog/static/25136049200882423842325/,我觉得解法二和解法四用得广泛一些,编程实现了一下。
利用快速排序中的partition操作
经过partition后,pivot左边的序列sa都大于pivot右边的序列sb;
如果|sa|==K或者|sa|==K-1,则数组的前K个元素就是最大的前K个元素,算法终止;
如果|sa|<K-1,则从sb中寻找前K-|sa|-1大的元素;
如果|sa|>K,则从sa中寻找前K大的元素。
一次partition(arr,begin,end)操作的复杂度为end-begin,也就是O(N),最坏情况下一次partition操作只找到第1大的那个元素,则需要进行K次partition操作,总的复杂度为O(N*K)。平均情况下每次partition都把序列均分两半,需要log(2,K)次partition操作,总的复杂度为O(N*log(2,K))。
#include<iostream> #include<cstdlib> #include<ctime> #include<vector> #include<algorithm> using namespace std; /*分割,pivot左边的都比右边的大*/ template<typename Comparable> int partition(vector<Comparable> &a,int left,int right){ int begin=left; int end=right; Comparable pivot=a[left]; while(left<right){ while(left<right && a[right]<=pivot) right--; a[left]=a[right]; while(left<right && a[left]>=pivot) left++; a[right]=a[left]; } a[left]=pivot; return left-begin; //返回pivot左边的元素个数 } template<typename Comparable> void findKMax(vector<Comparable> &vec,int left,int right,int k){ if(k>right-left+1) return; //由于partition时,总是固定地选取首元素作为轴,所以事先打乱一下顺序比较好,防止算法退化 //random_shuffle(vec.begin()+left,vec.begin()+right); int n=partition(vec,left,right); if(n==k || n==k-1) return; if(n<k-1) findKMax(vec,left+n+1,right,k-n-1); else if(n>k) findKMax(vec,left,left+n-1,k); } int main(){ int total=5;//原先有5个元素 int k=3;//选取前3个最大的 srand(time(0)); vector<int> a(total); for(int i=0;i<a.size();i++) a[i]=rand()%100; for(int i=0;i<a.size();i++) cout<<a[i]<<"\t"; cout<<endl; findKMax(a,0,a.size()-1,k); for(int i=0;i<a.size();i++) cout<<a[i]<<"\t"; cout<<endl; return 0; }
在选择pivot的时候传统的做法是选第1个元素作为pivot,一种优化的方法是随机选,更好的方法是三元取中法,更更好的方法是取五分化中项的中项,即把序列分为M组,每组5个元素,对每个组进行组内排序得到中项,然后对M个组按中项进行排序,取中间那个组的中项作为pivot。
利用小根堆实现
顺序读取数组中的前K个元素,构建小根堆。小根堆的特点是根元素最小,并且一次调整(deleteMin)操作的时间复杂度为log(2,K)。
接下来从数组中取下一个元素,如果该元素不比堆顶元素大,则丢弃;否则用它替换堆顶元素,然后调整小根堆。
当把数组中的元素全部读出来后,小根堆中保留的就是前K大的元素。
初始建堆操作需要K*log(2,K)--这是最多的操作次数,从数组中读取后N-K个元素和堆顶元素一一比较,最坏的情况是每次都要替换堆顶元素,都要调整小根堆,复杂度为(N-K)*log(2,K)。总的复杂度为O(N*log(2,K))。
#include<iostream> #include<cstdlib> #include<ctime> #include<vector> using namespace std; template<typename Comparable> void percolate(vector<Comparable> &vec,int index){ int i=index; int j=2*i+1; while(j<vec.size()){ if(j<vec.size()-1 && vec[j]>vec[j+1]) j++; if(vec[i]<vec[j]) break; else{ swap(vec[i],vec[j]); i=j; j=2*i+1; } } } template<typename Comparable> void buildHeap(vector<Comparable> &vec){ int len=vec.size(); for(int i=(len-1)/2;i>=0;i--) percolate(vec,i); } int main(){ srand(time(0)); vector<int> a(10); //原先有10个元素 for(int i=0;i<a.size();i++) a[i]=rand()%100; vector<int> b(7); //找出a中最大的前7个元素 for(int i=0;i<b.size();i++) b[i]=a[i]; buildHeap(b); for(int i=b.size();i<a.size();i++){ if(a[i]>b[0]){ b[0]=a[i]; percolate(b,0); } } vector<int>::iterator iter=a.begin(); while(iter!=a.end()){ cout<<*iter<<"\t"; iter++; } cout<<endl; iter=b.begin(); while(iter!=b.end()){ cout<<*iter<<"\t"; iter++; } cout<<endl; return 0; }
用partition方法时需要知道总元素个数N,且内存中要能够容下这么多元素,而使用堆就完全没有这些限制了,堆的大小是K,内存中只要能容下这个堆就可以了。
本文来自博客园,作者:张朝阳,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/2236860.html