如何用O(n)时间复杂度查找第k大数的优化算法 C++程序

本能的想法就是先排序,然后选出需要的数,平均时间复杂度是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值