编程之美系列之寻找最大的K个数

时间:2021-01-30 20:43:46

题目描述:有很多无序的数,姑且家丁它们各不相等,怎么选出其中最大的若干个数呢?这里我不想去写一些很没有意义的思路。神马先排序取前k个这种弱爆了并且一点也不适用的思想我就不想废话了,因为如果数据量很大的时候,对所有数据排序肯定是费力不讨好的事情,换换思路,不能全部排序,那就部分排序吧!这里介绍两个比较实用的。当然为了有点递进关系,先来一个比较差一点的方法。如果对排序算法还不是很熟悉的,可以参考博客: 排序算法
1、快排。回忆一下快排的思想,选一个关键元素key,利用key将数组分成两部分,左边一部分比key大,右边一部分比key小(当然本身的定义是左边一部分比key小,右边一部分比key大),那么key的位置有下面三种情况:
*如果key正好放在第k个数的位置上面,OK,那说明前k大的数我们已经找到了,就不需要排序了。
*如果key的位置比k小,那说明前面找到的最大的元素中还不足k个,那就在后面一段的数据中继续找。
*如果key的位置比k小,那说明前面找到的最大的元素的个数超过k了,那就在前面的一段数据中分出k个就不可以了。
如果是需要找最小的k个元素,类似的思想,这里就不废话了。代码如下:

void QuickSort(int *arr, int Begin, int End, int k)
{
if(!arr || Begin >= End || End < 0)
return;
int i,j;
int key;
i = (double)rand()/RAND_MAX * (End - Begin + 1) + Begin;
key = arr[i];
arr[i] = arr[Begin];
arr[Begin] = key;
i = Begin;
j = End;
while(i < j)
{
while(i < j && arr[j] <= key)//找到后面第一个比key大的元素
--j;
if(i < j)
arr[i++] = arr[j];
while(i < j && arr[i] >= key)//找到前面第一个比key小的元素
++i;
if(i < j)
arr[j--] = arr[i];
}
arr[i] = key;
if(i == k - 1)//恰好k个,返回
return;
else if(i < k - 1)//不足k个,在后面继续找
QuickSort(arr, i + 1, End, k);
else//多于k个,在前面分出k个
QuickSort(arr, Begin, i - 1, k);
}

2、堆排序。如果数据量很多的时候,快排有个什么坏处呢?它需要将所有数据都一次导入内存,在海量数据的情况下,显然是不能做到的,但是堆排序就可以。如果我们需要找最大的k个数,那就维护一个由k个数组成的最小堆,堆顶元素是堆的最小值。每次进来一个数,先与堆顶元素比较,如果小于堆顶元素,那一定小于堆中的任何一个元素。如果大于堆顶元素,OK,用这个数替换堆顶元素,然后调整新得到的堆。代码如下:

//将开始元素s到nLen组成的堆调整为最小堆
void MinHeapAdjust(int *arr, int s, int nLen)
{
if(!arr || s < 1 || nLen < 1 || s > nLen)
return;
int i,t;
t = arr[s];
for(i = s<<1; i <= nLen; i = s<<1)
{
if(i < nLen && arr[i] > arr[i|1])//找到左右子树中最小的一个
i |= 1;
if(t < arr[i])//t已经很小了,就不往下沉了
break;
arr[s] = arr[i];
s = i;
}
arr[s] = t;
}

3、给出两种方法的main函数调用,如果不需要,可以直接pass掉。

//打印数组
void Print(int *arr, int nLen)
{
for(int i = 0; i < nLen; ++i)
printf("%d ", arr[i]);
printf("\n");
}
int main()
{
const int N = 30;
int arr[N];
int i,n,k,v;
/* //快排的main函数调用部分
while(scanf("%d %d", &n, &k) != EOF)
{
for(i = 0; i < n; ++i)
scanf("%d", &arr[i]);
printf("输入k:");
scanf("%d", &k);
QuickSort(arr, 0, n - 1, k);
Print(arr, k);
}
*/
//堆排序main函数的调用部分
while(scanf("%d %d", &n, &k) != EOF)
{
for(i = 1; i <= k; ++i)
scanf("%d", &arr[i]);
//得到最小堆
for(i = k>>1; i > 0; --i)
MinHeapAdjust(arr, i, k);
for(i = k; i < n; ++i)
{
scanf("%d", &v);
if(v > arr[1])
{
arr[1] = v;
MinHeapAdjust(arr, 1, k);
}
}
Print(&arr[1], k);
}
}