选择问题即:寻找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,内存中只要能容下这个堆就可以了。
原文来自:博客园(华夏35度)http://www.cnblogs.com/zhangchaoyang 作者:Orisun