【堆——解决Top-K问题】

一、前言

在上一篇文章中,我们对建堆的两种方式:向上调整和向下调整,做了时间复杂度的分析。我们发现,向下调整建堆的这种方式时间复杂度最低为 O(N)。接下来,我们要利用堆来进行排序。
从n个数据中选出最大的K个。这里我们使用向下调整的方式建堆且用向下调整的方式完成排序。

二、原理分析

1.要选出最大值,这里我们需要建一个小堆。取n个数据中的前k个数据放入数组中,然后调用向下调整函数。(因为数据是随意的,所以根的左右子树可能不为堆,所以不能从根开始进行向下调整。要重最后一个非叶子结点开始调整,前面关于堆的文章中有细说。)。调整后的k个元素的数组是一个小堆。
2.从剩下的n-k个数据中依次读取数据,与根元素(即数组中最小的)相比较,如果大于根元素,则替换掉根元素,同时进行一次向下调整。调整后的根元素又一次变成最小的数据,
3.重复操作2.最后比较完之后得到的就是n个数据中最大的k个数据。

三、代码实现

1.数据生成

我们需要n个随机的自然数,而且还要有一定的数量和随机性,这里使用一个函数生成1000个随机数,并将它存在文件中。这个代码之前在C语言的文章中有仔细讲过。

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

2.将n个数据中的前K个放入数组中

动态开辟k个数组空间,从文件中读入K个数据,放入数组中。

FILE* fout = fopen(filename, "r");//以读的方式打开文件
if (fout == NULL)
{
	perror("fopen fail");
	exit(-1);
}
//开辟k个元素的空间
int* minheap = (int*)malloc(sizeof(int) * k);
if (minheap == NULL)
{
	perror("malloc fail");
	exit(-1);
}
//将前k个数据读入数组中
for (int i = 0; i < k; i++)
{
	fscanf(fout, "%d", &minheap[i]);
}

3.向下调整建小堆

从最后一个非叶子结点开始向下调整。注意,这里的向下调整函数需要时建小堆的函数。如果你写的向下调整函数是建大堆的,那么建出来的就是大堆。后面就选不出最大的。

//从最小的非叶子结点开始,以向下调整的方式建小堆
for (int i = (k - 1 - 1) / 2; i >= 0; --i)
{
	AdjustDown(minheap, i,k);
}
	

4.替换根结点,再调整

从剩下的n-k个元素中读取数据与根节点进行比较,比根结点大的则替换根节点,再进行一次向下调整。

//读取数据文件中剩下的n-k个元素,依次与小堆中的元素比较,若比小堆中的元素大,则替换,并向下调整
int number = 0;
while (fscanf(fout, "%d", &number) != EOF)
{
	if (minheap[0] < number)
	{
		minheap[0] = number;
		AdjustDown(minheap, 0, k);
	}
}

5.将结果打印出来


	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}

总结

1.造随机数据
2.动态开辟数组并读入k个数据
3.向下调整建立小堆
4.读取剩下的n-k个数据并与根数据进行比较,比根数据大,则替换,同时进行一次向下调整。
注意:获取最大的数据要建小堆,获取最小的数据要建大堆。也就是一个大于符号和小于符号的区别。但是要小心使用。

到这里,关于堆这个数据结构已经讲完了,下一章进入二叉树的链式存储。喜欢的朋友们关注一下~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值