本能的想法就是先排序,然后选出需要的数,平均时间复杂度是O(nlgn).
但是其实这个算法可以在O(n)平均时间复杂度内选择出来的。
基本思路:
在一个递归算法里面,每一次递归分区,我们丢弃一部分元素,然后再剩下的部分递归。这样问题的大小就呈几何级别减少了。
比如每次递归丢弃1/3的数据,剩下的就是2n/3数量了,再次递归剩下4n/9,如此递归下去。
本人觉得这个算法虽然genius。但是由于过于复杂(相对比排序查找算法来说),所以用的不多,属于特殊情况的高级用法了。
不过思想确实值得我们学习的,想出这个算法的人的确是天才来的。
写本算法程序的问题总结:
1. 下标问题要搞清楚,这些类型的下标处理一定要把low和up的下标和C\C++的下标对应起来,就是说low和up都一定需要存储数据。
2. 没完全搞清楚问题的时候,不要动手编程,不要期望程序会magically work!
例如
问题一:下标如何处理?
问题二:不能被整除的时候为什么不用处理?
问题三:中间需要递归多少次?
问题四: vec2为什么不是长度为1?如果数据重复的时候要如何处理?
问题五:截去的那一部分是如何截去的?
答:
问题一:下标如前面1的处理,这很重要。
问题二:因为到后面可以递归处理了;
问题三:中间两次也用递归处理,这样数据大量的时候就充分发挥了这个算法的优点了;
问题四:vec2保存等于中间值,这样就可以处理有相等数值的问题了;
问题五:通过最后面的对比截取的。
3. 第k个数应该如何处理?是从0开始还是从1开始?
这里应该是下标从1开始的,看个人喜欢了,也可以改为从0开始
4. 中间递归取中间数的时候这个中间数如何取?尤其是偶数的时候应该是去哪一个数?
这里这个中间数的其实并不是很重要,偶数的时候也问题不大,比如当剩下4个数的时候,这里是取C++下标2。但是如果取1也是没有问题的。
下面是O(n)时间复杂度的查找第k大数的优化算法, 但是由于应该是O(4n),所以常数项有点大,那么对于数据小的时候,不太实用。
C++完整程序:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//low and up have to be according indices in C++;
//k mean the kth number, according C++ stlye index is k-1
int selectKthNum(vector<int> &vi, int low, int up, int k)
{
int p = up - low +1;
if(p<6)
{
sort(vi.begin()+low, vi.begin()+up+1);
return vi[k-1];
}
//如果不被5整除也是可以的,因为后面递归循环,可以把前面截取的尾数考虑上,所以结果也是精确的。
int q = p/5;
vector<int> medVec;
//这里的中间数取法,应该注意偶数的时候,比如是4的时候,这里是取第三个数,但是也可以取第二个数吧。
int mid = q/2;
//例如可以修改为:
//int mid = (q+1)/2; //结果也是一样的
for (int i = 0; i < 5; i++)
{
int m = selectKthNum(vi, i*q, (i+1)*q-1,mid+1);
medVec.push_back(m);
}
//这里的中间数取法,应该注意偶数的时候,比如是4的时候,这里是取第三个数,但是也可以取第二个数吧。
int mNum = selectKthNum(medVec, 0, medVec.size()-1, medVec.size()/2+1);
//例如可以修改为:
//int mNum = selectKthNum(medVec, 0, medVec.size()-1, (medVec.size()+1)/2);
//结果不变
vector<int> vec1, vec2, vec3;
for (int i = low; i <= up; i++)
{
if(vi[i] > mNum) vec3.push_back(vi[i]);
if(vi[i] == mNum) vec2.push_back(vi[i]);
if(vi[i] < mNum) vec1.push_back(vi[i]);
}
if(vec1.size()>=k)
return selectKthNum(vec1, 0, vec1.size()-1, k);
else if(vec1.size()+vec2.size()>=k)
return mNum;
else if(vec1.size()+vec2.size()<k)
return selectKthNum(vec3, 0, vec3.size()-1, k-vec1.size()-vec2.size());
}
int main()
{
int a[] = {3,5,7,9,2,12,1,0,8,14,4,6,10,11,13,5,8,12};
vector<int> va(a, a+18);
int b[] = {31,25,37,49,52,63,71,20,87,95,34};
vector<int> vb(b, b+11);
for(auto x: va)
cout<<x<<" ";
cout<<endl;
int mid = selectKthNum(va, 0, va.size()-1, 13);
cout<<"The Kth number is: \n";
cout<<mid<<endl;
cout<<endl;
system("pause");
return 0;
}
运行结果:
Reference:
Algorithms Design Techniques and Analysis by Alsuwaiyel