前言:编程之美上并没有给出实际的代码,这里我参考yoona博主的做法自己也写了一遍,并记录下来。
与此相似的题目是:
– 求第k大的数
– 求第k小的数
这里给出比较好的两个做法,分别是快排思想和堆思想,至于怎么做的这里就不重述了,直接上代码。
快排思想解
int quicksort_cut(int arr[],int left,int right)
{
if (arr == NULL || left >= right)
{
return -1;
}
int i,j,temp;
i = left; j = right;
temp = arr[left];
while(i<j)
{
while(temp > arr[j] && i < j)
j--;
if (i<j){
arr[i] = arr[j];
i++;
}
while(temp <= arr[i] && i < j)
i++;
if (i<j){
arr[j] = arr[i];
j--;
}
}
//此时 i==j
arr[i] = temp;
return i;
}
int get_k_bigs(int arr[],int left,int right,int k)
{
int cut_index,n;
if (left >= right || arr == NULL)
{
return -1;
}
//划分,返回划分位置.
cut_index = quicksort_cut(arr,left,right);
n = cut_index - left +1;
if (k == n)
{
return cut_index;
}
else if (n<k) //如果小于k个,还要在右边拿k-n个
{
return get_k_bigs(arr,cut_index+1,right,k-n);
}
else //如果大于k个,则范围缩小为left--cut_index
{
return get_k_bigs(arr,left,cut_index,k);
}
}
测试代码:
int main()
{
int a[] = {4,5,1,6,2,7,3,8};
vector<int>* result;
int x = get_k_bigs(a,0,7,4);
result = new vector<int>(a,a+x+1);
for (vector<int>::iterator iter = result->begin();iter != result->end();iter++)
{
cout << *iter << " ";
}
cout << endl;
delete result;
system("pause");
return 0;
}
堆思想解.
用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最大K个数中的最小的一个。每次扫描一个数据X,如果X比堆顶元素Y小,则不需要改变原来的堆,因为这个元素比最大的K个数要小。如果X比堆顶元素大,那么用X替换堆顶元素Y,在替换之后,X可能破坏了最小堆的结构,需要调整堆来维持堆的性质。调整过程时间复杂度为O(logK)。
当数据量很大时(这时候数据已经不能全部装入内存,所以要求尽可能少的遍历数组)可以采用这种方法。
实现:
//index:调整的堆节点;n:堆元素个数;该函数只调整一个节点。
//index,必须是从1开始算起。
void min_heap_shift(int a[],int index,int n){
int i,j,temp,finished;
temp = a[index];
i = index;
j = i*2; //i的子节点
finished = 0;
while(j<=n && !finished)
{
if (a[j] > a[j+1] && j < n)
j++;
if (temp <= a[j])
{
finished = 1;
}
else
{
a[i] = a[j];
i = j;
j = 2*i;
}
}
a[i] = temp;
}
/* * 建堆:将一个数组a[1~k]变成一个最小堆 * 避免额外空间,就在原来数组中建 */
void build_min_heap(int a[],int k){
int i;
for(i = k/2;i >= 1;i--){
min_heap_shift(a,i,k);
}
}
/* *注意arr是从1开始的。 *k表示要提取的最大的k个数 *n表示a数组从1开始的长度 */
void get_k_bigs(int a[],int k,int n)
{
if (a == NULL || k>n)
{
return;
}
int i;
build_min_heap(a,k);
for (i = n;i>k;i--)
{
if (a[1] < a[i])
{
a[1] ^= a[i];
a[i] ^= a[1];
a[1] ^= a[i];
min_heap_shift(a,1,k);
}
}
}
测试代码:
int main()
{
int n = 6;
int k = 3;
//a[0]不用,堆的根结点是从1开始的
int a[] = {0,3,17,8,27,7,20};
get_k_bigs(a,k,n);
for(int i = 1;i <= k;i++){
printf("%d ",a[i]);
}
system("pause");
return 0;
}
总结
其实,这道题给我的感触是蛮深的,知识的迁移,知识的灵活应用,都是很好的锻炼,最基本的锻炼了我对快排和堆排的理解。
最后,感谢(http://blog.csdn.net/sunnyyoona/article/details/26380473)这篇博文讲述!