堆的使用:解决TOP-K问题

问题介绍

求一个数据集合里前k个最大元素的集合或者最小元素的集合。一般情况数据量比较大。

画图分析

当有数十亿个数据的时候,很明显我们不可能使用排序的方法,而且数据都不能一下全部加载到内存里,最好的方式就是使用堆来进行解决。

  1. 用数据前k个元素进行建堆

取前k个最大的元素 – 建小堆
取前k个最小的元素 – 建大堆

  1. 然后再用剩余的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问题展现了在一些方面,堆的使用还是比较方便的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kpl_20

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值