问题介绍
求一个数据集合里前k个最大元素的集合或者最小元素的集合。一般情况数据量比较大。
画图分析
当有数十亿个数据的时候,很明显我们不可能使用排序的方法,而且数据都不能一下全部加载到内存里,最好的方式就是使用堆来进行解决。
- 用数据前k个元素进行建堆
取前k个最大的元素 – 建小堆
取前k个最小的元素 – 建大堆
- 然后再用剩余的n-k个元素依次和堆顶元素比较,符合条件的替换堆顶元素,并向下调整。
代码
这里使用了文件的相关知识,如果有些遗忘,这是关于文件方面知识的博客链接:文件操作。
堆的向下调整在这里也不做过多的赘述,如果有些遗忘,这是关于堆的实现的博客链接:堆的实现
因为这里需要造一堆数据,便于我们对代码进行测试,然后把造的数据存放在文件里。数据是随机值,所以执行一次即可,要不然不方便测试
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a; //存放数据的数组
int capacity; //容量,不够可增容
int size; //数组内有效数据
}HP;
//交换
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType x = *a;
*a = *b;
*b = x;
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//左右孩子的小值,
if (child + 1 < n && a[child] > a[child + 1])
{
child++;
}
//如果孩子比双亲小,交换
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//寻找数据中最大的前十个
void PrintTopK(const char* file, int k)
{
//开辟一个k个元素大小数组,用于存放k个数据
int* topk = (int*)malloc(sizeof(int) * k);
if (NULL == topk)
{
perror("topk::malloc");
return;
}
FILE* fout = fopen(file, "r");
if (NULL == fout)
{
perror("fopen error");
return;
}
//读文件中前k个数据,存放入开辟好的topk数组中
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &topk[i]);
}
//建堆--用topk中的元素建小堆
for (int i = (k - 2) / 2; i >= 0; i--)
{
AdjustDown(topk, k, i);
}
//建立一个值,用来存放从文件里读取的数据
int val = 0;
//这里建立的ret用来接收fscanf函数的返回值,为了判断读取是否结束,并不是用来存放数据
int ret = fscanf(fout, "%d", &val);
while (ret != EOF)
{
//比堆顶元素大,则交换。再向下调整,大的下沉,小的上浮
if (val > topk[0])
{
topk[0] = val;
AdjustDown(topk, k, 0);
}
//这里需要再进行取值、接收和判断
ret = fscanf(fout, "%d", &val);
}
//打印前十个值
for (int i = 0; i < k; i++)
{
printf("%d ", topk[i]);
}
printf("\n");
//使用完不要忘记释放,topk是malloc出来的,需要释放,不然存在内存泄漏
free(topk);
topk = NULL;
fclose(fout);
fout = NULL;
}
//造数据。这里造了一百万个数据,存放在文件里,读者在测试的时候可以造更多,但是不建议因为耗时过长,毕竟只是测试。
void CreateNData()
{
size_t n = 1000000;
//使用随机值造数据,所有数据都是随机的。srand和后面提到的rand是用来造随机值的。
srand((unsigned)time(0));
//这里使用的文件操作的一些相关知识,在代码目录的开始,有相关知识的链接。
FILE* fin = fopen("data.txt", "w");
if (NULL == fin)
{
perror("fopen error");
return;
}
//一个个的造数据,并存放在文件里
for (size_t i = 0; i < n; i++)
{
int x = rand() % 10000;
fprintf(fin, "%d\n", x);
}
//保持良好习惯,使用完文件指针时,要再关闭一下。
fclose(fin);
fin = NULL;
}
int main()
{
//造数据的接口执行一次就好,就可以注释掉了。不然每次都会重新造数据,不利于测试观察
//CreateNData();
//假设我们需要找到最大的前十个数据,所以使用文件前十个数据,建立一个十个元素的小堆即可
PrintTopK("data.txt", 10);
return 0;
}
总结
TOP-K问题展现了在一些方面,堆的使用还是比较方便的。