这个题目很常见,方法也很多,这里总结了编程之美给出的几个比较好的方法,也算是对这个问题的一个总结。
方法一、partition算法,每次partition的复杂度为O(n),总的平均时间复杂度为O(nlogn)
分析:运用partition算法,如果返回的provit > k-1,则说明要找的数都在前面,把end= provit-1;如果provit < k-1,表明找到一部分,先把部分数据保存,然后start = provit + 1;如果正好相等,则保存后结束,具体代码如下:
int partition(vector<int>& numbers,int start,int end)//此partition为了满足题目的需要,把大的数据移到前面
{
int provit = numbers[start];
while(start < end)
{
while(start < end && numbers[end] <= provit)end --;
if(start < end) numbers[start++] = numbers[end];
while(start < end && numbers[start] >= provit) start ++;
if(start < end) numbers[end--] = numbers[start];
}
numbers[start] = provit;
return start;
}
vector<int> SearchTheKthNum(vector<int>& numbers,int k)
{
vector<int> res;
int start = 0,end = numbers.size()-1,i;
while(start <= end)
{
int provit = partition(numbers,start,end);
if(provit > k-1)end = provit-1;//要找的数都在前面
else if(provit < k-1)//找到部分
{
for(i=start;i<=provit;i++)res.push_back(numbers[i]);
start = provit + 1;
}
else//找到全部
{
for(i=start;i<=provit;i++)res.push_back(numbers[i]);
break;
}
}
return res;
}
方法二、堆排序
分析:建立一个大根堆,每次取堆顶元素后,调整堆,循环k次后即可,时间复杂度为O(nlogn)
void shiftDown(vector<int>& numbers,int index,int length)//向下调整堆
{
int i = ((index+1)<<1)-1,tmp = numbers[index];
while(i <= length -1)
{
if(i<length-1 && numbers[i]<numbers[i+1])i++;
if(numbers[i] < tmp)break;
numbers[index] = numbers[i];
index = i;
i = ((index+1)<<1)-1;
}
numbers[index] = tmp;
}
void createTree(vector<int>& numbers)//建堆
{
int length = numbers.size(),i;
for(i = (length>>1)-1;i>=0;i--)
{
shiftDown(numbers,i,length);
}
}
vector<int> SearchTheKthNum(vector<int>& numbers,int k)
{
int length = numbers.size(),i;
assert(length>=0 && length >= k && k>=0);
createTree(numbers);
vector<int> res(k);
for(i=0;i<k;i++)
{
res[i] = numbers[0];//选取当前最大元素
numbers[0] = numbers[length-i-1];
shiftDown(numbers,0,length-i-1);//选取后调整堆
}
return res;
}
方法三、计数排序(适用范围:数据的范围比较小,即数据比较集中,例如待排序的数据是一个公司的员工的年龄)
分析:首先找到待查数据的最大最小值,获得数据的范围,然后对每个元素进行计数,最后从后向前遍历查找最大的k个元素。时间复杂度为O(n),但是空间复杂度为O(Max-Min),如果数据比较分散,空间复杂度很大
void GetTheMaxAndMinValue(vector<int>& numbers,int& Max,int& Min,int length)//找到待查数据的最大最小值
{
int i;
for(i=1,Max = numbers[0],Min = numbers[0];i<length;i++)
{
if(numbers[i] > Max)Max = numbers[i];
else if(numbers[i] < Min)Min = numbers[i];
}
}
vector<int> SearchTheKthNum(vector<int>& numbers,int k)
{
int length = numbers.size(),i,j=0,Max,Min;
assert(length>=0 && length >= k && k>=0);
vector<int> res(k);
GetTheMaxAndMinValue(numbers,Max,Min,length);
vector<int> count(Max-Min+1,0);
for(i=0;i<length;i++)
{
count[numbers[i]-Min]++;//对每个数据进行计数
}
for(i=count.size()-1;j < k && i >= 0;i--)
{
while(count[i] > 0 && j < k)
{
res[j++] = i + Min;
count[i] --;
}
}
return res;
}
方法四、位比特(适用范围:数据是没有重复的整数)
分析:和方法三类似,只是这里用一个bit表示一个数据,优点就是节省空间,缺点就是只能用于没有重复数据的查找,例如查找IP、电话号码等
void GetTheMaxAndMinValue(vector<int>& numbers,int& Max,int& Min,int length)
{
int i;
for(i=1,Max = numbers[0],Min = numbers[0];i<length;i++)
{
if(numbers[i] > Max)Max = numbers[i];
else if(numbers[i] < Min)Min = numbers[i];
}
}
void bit_set(int& data,int index)
{
data = (data | (0x01 << index));
}
vector<int> SearchTheKthNum(vector<int>& numbers,int k)
{
int length = numbers.size(),i,j=0,Max,Min;
int INTLEN = 32;
assert(length>=0 && length >= k && k>=0);
vector<int> res(k);
GetTheMaxAndMinValue(numbers,Max,Min,length);
vector<int> bit((Max-Min)/INTLEN+1,0);
for(i=0;i<length;i++)
{
bit_set(bit[(numbers[i]-Min)/INTLEN],(numbers[i]-Min)%INTLEN);//遍历数据,将出现的数据的对应位置位
}
for(i=bit.size()-1;i>=0;i--)//查找置位的数据位中的最高的k个位
{
int shift = INTLEN -1;
while(j < k && shift >= 0)
{
int tmp = (0x01 << shift);
if((tmp & bit[i])!=0)
{
res[j++] = (i * INTLEN + shift + Min);
}
shift -= 1;
}
}
return res;
}
以上若有不对的地方,欢迎指正,谢谢