Topk问题:给定你一个数组a,内有n个元素,请问怎么可以找出他前k个最大/最小的数?
这个题我们可以利用堆的特性来解决。我们可以先创建一个大小为k的小堆(如果求前k个最小的数就创建大堆,这里以求前k个最大的数为例),并对其排序。此时排在堆顶的就是当前堆内最小的数。下一步我们将剩余n-k个数依次与堆顶元素比较,如果这个数比堆顶元素大,那么将堆顶元素删除并将这个数入堆,否则就检查下一个数。如此下来到最后,堆内剩下的元素就是所有数里最大的k个数了。
/*
这里是笔者实现的堆
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* a;
int size;
int capacity;
}HP;
void AdjustDown(int* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child] > a[child + 1])
child++;
if (a[child] < a[parent])
{
int tmp = a[parent];
a[parent] = a[child];
a[child] = tmp;
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void Adjustup(int* hp, HeapDataType child)
{
assert(hp);
int parents = (child - 1) / 2;
while (child)
{
if (hp[child] < hp[parents])
{
HeapDataType tmp = hp[parents];
hp[parents] = hp[child];
hp[child] = tmp;
child = parents;
parents = (child - 1) / 2;
}
else
break;
}
}
void HeapInit(HP* hp)
{
assert(hp);
hp->capacity = hp->size = 0;
hp->a = NULL;
}
void HeapDestroy(HP* hp)
{
assert(hp);
free(hp->a);
hp->capacity = hp->size = 0;
}
void HeapPush(HP* hp, HeapDataType x)
{
assert(hp);
if (hp->capacity == hp->size)
{
hp->capacity = (hp->capacity == 0 ? 4 : hp->capacity * 2);
HeapDataType* tmp = (HeapDataType*)realloc(hp->a, sizeof(HeapDataType) * hp->capacity);
if (tmp == NULL)
exit(-1);
hp->a = tmp;
}
hp->a[hp->size] = x;
hp->size++;
Adjustup(hp->a, hp->size - 1);
}
void HeapPrint(HP* hp)
{
assert(hp);
for (int i = 0; i < hp->size; i++)
{
printf("%d ", hp->a[i]);
}
printf("\n");
}
void HeapPop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
hp->a[0] = hp->a[hp->size - 1];
hp->size--;
AdjustDown(hp->a, hp->size, 0);
}
int HeapSize(HP* hp)
{
assert(hp);
return hp->size;
}
bool HeapEmpty(HP* hp)
{
assert(hp);
return hp->size == 0;
}
HeapDataType HeadTop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->a[0];
}
*/
void PrintTopK(int* a, int n, int k)
{
HP hp;
HeapInit(&hp);
for (int i = 0; i < k; i++)
{
HeapPush(&hp, a[i]);
//先将前k个元素入堆,当全部入完后已经自动将最小的元素列在堆顶
}
//此时取后面的n-k个元素依次与堆顶元素比较
for (int i = k; i < n; i++)
{
/*如果这个元素比堆顶元素大,那么直接删除堆顶元素并且将这个数入
堆,否则就跳到下一个元素*/
if (a[i] > hp.a[0])
{
HeapPop(&hp);
HeapPush(&hp, a[i]);
}
}
HeapPrint(&hp);
//最后打印一下堆内元素
HeapDestroy(&hp);
}
当求前k个最小的数时,就把堆变为大堆,判断条件改为当元素比堆顶元素小的时候删除堆顶元素并将这个元素入堆即可。