【C数据结构】堆的建立、堆排序和Top K问题

1、堆的概念

在数据结构中,堆在逻辑上可以理解为一颗完全二叉树,而在物理结构上普遍用数组表示。

比如:
在这里插入图片描述

而在堆中又分为两种

  1. 小堆:在逻辑结构中,除叶结点任意节点都小于其子节点的值,称作小堆。
  2. 大堆:在逻辑结构中,除叶结点任意节点都大于其子节点的值,称作大堆。

在这里插入图片描述
从堆的下标关系来看,如果父节点,那么与其左子节点关系固定为parent2+1,右子节点关系固定为parent2+2。
(12+1=3,12+2=4)

而不管是左子节点还是右子节点,parent = (child-1)/2; ( (3-1) / 2 = 1,(4-1) / 2 = 1)
在这里插入图片描述

2、堆的调整算法

完整堆排序内容

2.1、向上调整算法

如果要实现一个数组里的数组,从下标为0位置开始依次进堆的操作。
以下以建小堆为例。
在这里插入图片描述
中间过程
在这里插入图片描述

这样从第一个数据开始,到入堆最后一个数据,每个数据都通过与父节点比较,确保满足建小堆条件。
在这里插入图片描述
代码实现

void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

时间复杂度计算:
在这里插入图片描述

2.3、向下调整算法

向下调整算法有一个前提就是,只有当左右子树为堆的时候,才能使用。
比如,以87为根的左右子树都满足小堆条件,那么这个时候就可以用向下调整算法。

将父节点(如:87)和左右子节点小的那个交换(比如21),一直重复这个操作,直至到达叶子结点。

在这里插入图片描述
在这里插入图片描述

如果要删除堆顶的数据,该怎么办?

在这里插入图片描述
如果根节点左右子树不为堆怎么办,一直向下找到满足左右子树为堆的根(保底找到为左右子节点为叶节点的根节点,因为叶结点满足小堆的条件)。
堆排序中堆的建立
在这里插入图片描述

时间复杂度计算:
在这里插入图片描述

所以和向上调整算法相比,向下调整算法的时间复杂度要更高效。

3、TopK问题

TopK问题就是从N个数里面,找出前K个最大的数。
有两种方法

  1. 通过堆排序,再挑取前面K个数。时间复杂度O(N*LogN)空间复杂度O(N)
  2. 通过堆选数:
    a. 建大堆:建好大堆,每次取出堆顶数据,再调整成大堆。时间复杂度O(N+K*LogN)空间复杂度O(N)
    b. 建小堆:对前K个数建好小堆,再对N-K个数进行筛选,如果数大于堆顶数据,那么进堆并向下调整,最后小堆里的数就是前K个最大的数。时间复杂度O(K+(N-k)LogK),当N远大K就是O(N),空间复杂度O(K)

以上三种方法,值得注意的是,当数据量很大时,第一种方法和第二种方法中的建大堆,就会因为空间的问题难以进行,比如N达到100亿,也就需要40GB的内存去储存数据,而建小堆会很有效的避免这个问题。

代码实现以及测试:

void CreatFile(int size)
{
	
	FILE* fin = fopen("nums.txt", "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return -1;
	}
	int i = 0;
	for (i = 0; i < size; i++)
	{
		fprintf(fin, "%d\n", rand() % 100000);
	}

	fclose(fin);
}

int main()
{

	//写入大量数据到文件
	//srand(time(NULL));
	int size = 1000000;
	/*CreatFile(size);*/

	//前k个建立小堆
	int k = 10;
	int* a = (int*)malloc(sizeof(int) * k);
	int i = 0;
	FILE* fout = fopen("nums.txt", "r");
	
	//从文件读取K个
	for (i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &a[i]);
	}
	//读取后进行向下调整
	for (i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, k, i);
	}

	//N-K进小堆比较
	int val = 0;
	while (fscanf(fout, "%d", &val) != EOF)
	{
		if (val > a[0])
		{
			Swap(&val, &a[0]);
			AdjustDown(a, k, 0);
		}
	}

	//打印
	for (i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	fclose(fout);

	return 0;
}

因为是随机数据写入文件,为了确定结果是我们要的前K个最大的,我们在文件中自己输入10个最大的数,这样在打印后能更清楚。

输出结果
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值