TopK问题:找出N个数里面最大/最小的前K个问题。
这里研究用堆解决
方法有三
1.堆排序 O(N*logN) (或者其他排序)
2.建立N个大堆 ,每次取堆顶K次O(N+logN*K),
3.N远大于K的情况下:O(K+(N-K)*logK)
建立数量为k的一个小堆,剩下N-k与小堆堆顶比较,大的进堆,
如下图,绿圈为随机取k个
不断把n-k个元素与小堆顶比较,大者交换。
最后就能保证小堆里都是前K个。
第三种方法与前两种方法相比,时间复杂度相差不大,优势在于空间复杂度。
代码
随机生成1000个数,并保证每个数字大小都在范围10000内。
随便取10个数令其超过一万,这10个数就是前k个数。
void TestTopk() {
int n = 10000;
int* arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < 10000; i++)
{
arr[i] = rand() % 10000;
}
arr[32] = n + 1;
arr[22] = n + 2;
arr[24] = n + 3;
arr[244] = n + 4;
arr[212] = n + 5;
arr[112] = n + 6;
arr[2324] = n + 7;
arr[2243] = n + 8;
arr[2] = n + 9;
arr[231] = n + 10;
PrintTopK(arr, n, 10);
}
实现找出前k个数的函数
void PrintTopK(int* a, int n, int k) {
//建堆 O(K)
//
//k个元素建堆(数组)
//抽k个数
int* heap = (int*)malloc(k * sizeof(int));
for (int i = 0; i < k; i++)
{
heap[i] = a[i];
}
//完成堆的结构构建:找最后一个非叶子结点下沉
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
HeapSink(heap, k, i);
}
//比较 O((K-N)logK)
//遍历K-N个
for (int i = k; i < n; i++)
{
if (a[i] > heap[0]) {
//与堆顶交换后下沉 :logK
swap(&a[i], &heap[0]);
HeapSink(heap, k, 0);
}
}
for (int i = 0; i < k; i++)
printf("%d ", heap[i]);
}
heapSink的实现
void HeapSink(int* a, int size, int parent) {
int child = parent * 2 + 1;
while (child < size) {
//选取2个孩子中较小的同时还得考虑,只有一个左子树的情况
if (child + 1 < size && a[child] > a[child + 1])
{
child++;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = child * 2 + 1;
}
else {
break;
}
}
}
运行结果
10001 10002 10003 10005 10004 10008 10009 10006 10007 10010