【初阶数据结构09】——对堆用法的深入刨析

文章目录

前言

1.向上调整以及向下调整函数的解析

[1.1 向上调整函数](#1.1 向上调整函数)

[1.2 向下调整函数](#1.2 向下调整函数)

2.将数组建成堆

[2.1 向上调整建堆](#2.1 向上调整建堆)

[2.2 向下调整建堆](#2.2 向下调整建堆)

3.堆排序的实现

4.TopK的实现

结语:


前言

上一篇文章让我们对树、二叉树以及堆的基本概念有了一个大概的认识,这篇文章会对堆的用法作一个更加全面的解析。

1.向上调整以及向下调整函数的解析

在篇文章中我们只是对这两个函数做了一个简单的概述,下面我们做一个更加详细的分析,以便更好得理解后面的堆排序以及TopK问题。

1.1 向上调整函数

我们在堆尾或者说是数组的尾部插入一个数据,首先是检查数组的空间是否充足(不足就扩容),然后插入数据。这里首先我们要知道原堆是大堆还是小堆,如果是小堆那么要求它要比父亲大,大堆要求它比父亲小。

cpp 复制代码
void AdjustUp(HeapdataType* arr,int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[parent] > arr[child])
		{
			Swap(&arr[parent], &arr[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

我们在设计函数时,将函数的参数设为一个数组,是为了后面可以再次利用,然后如果是小堆,那么节点交换的条件是arr[parent] > arr[child],同理,如果是大堆那么就是arr[parent] < arr[child]。

然后父亲节点与孩子节点的关系 parent = (child - 1) / 2;

左孩子与父亲的关系:leftchild=parent*2+1;

左孩子与父亲的关系:rightchild=parent*2+2;

这些关系是对父节点与孩子节点定位的关键。

总结一下,因为我们将一个可能改变堆的结构的数插入堆尾,所以要进行堆的重新调整。

1.2 向下调整函数

我们删除堆头的数据后,会导致堆的关系完全紊乱,我们为了保证左右堆的完整性,选择先将要删除的数据与堆尾的数据进行交换,因为删除堆尾的数据是极为便利的,这时,要回复堆的结构只需要将现在的堆头的数据进行向下调整,这样就只需要改变一个数据了,不需要遍历堆中所有的节点。

cpp 复制代码
void AdjustDown(HeapdataType* arr, int size, int parent)
{
	//因为我们要找出左右孩子中较小的那一个进行交换
	//我们先假设左孩子是较小的那一个
	int child = 2 * parent + 1;
	while (child < size)
	{
		if (child + 1 < size && arr[child + 1] < arr[child])
		{
			child++;
		}
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

这里展示的是调整成小堆的函数。

2.将数组建成堆

为了进行堆排序,我们需要将一个数组进行建堆,方便后面进行排序。

2.1 向上调整建堆

cpp 复制代码
for (int i = 1;i < n;i++)
{
	AdjustUp(arr, i);//这里的i是孩子节点
}

思路,将第一个元素看成堆,然后不断进行插入,所以利用向上调整函数,总而言之,将这里与插入过程进行类比,会更好理解。这个方法很好理解,我们尝试分析一下它的时间复杂度,不难发现为O(n log n),那是否有更高效的建堆方法呢?有的,那就是向下调整建堆。

2.2 向下调整建堆

对向上调整建堆来说,会发现越是底层,需要进行调整的可能就越多,底层不仅节点多,同时层数越多,导致比较低效,所以我们尝试看看能否从底层开始向下调整。

我们从最后一个节点的父亲节点开始向下调整,这里可以看成删除节点之后对堆进行调整。

cpp 复制代码
int end = n - 1;//尾节点在数组中的位置
for (int i = (end - 1 - 1) / 2;i >=0;i--)
{
	AdjustDown(arr, end, i);
}

时间复杂度分析为O(n);

3.堆排序的实现

堆排序的核心要素分析:升序建大堆,降序建小堆。是不是很诧异?怎么跟我想的是反的。听听我的解释,这里以降序排序为例:

小堆的堆头,即数组的第一个数是不是里面最小的数,降序要让最小的在最后对吧?那我们就把堆尾与堆头进行交换,然后不把排好的数参与比较,利用向下调整把堆进行复原,这里就是和删除堆头的思想差不多,但是这里选择了把元素进行保留。

cpp 复制代码
int end = n - 1;
for (int i = (end - 1 - 1) / 2;i >=0;i--)
{
	AdjustDown(arr, end, i);
}
 end = n - 1;
while (end > 0)
{
	Swap(arr, &arr[end]);
	AdjustDown(arr,end,0);
	end--;
}

相较于我们之前学过的冒泡排序,堆排序更加高效,它的时间复杂度是O(n log n)。

4.TopK的实现

堆不仅可以用来进行排序,还可以在大量的数中找出前K个大/小 的数。后面以找前K个大的数为例进行讲解

具体思想:

方法一:来建一个大小为N大堆,找到堆头,然后将保存后删除,再进行向下调整(HeepPop),如此循环K次,就可以了,时间复杂度O(K*log(n));

方法二:建立一个大小为K的小堆,然后让数组前K个数入堆,如果有比堆头大的数,就代替堆头元素入堆,然后进行向下调整,如此循环往复,效率与方法一相近,只是对内存的要求不大。

cpp 复制代码
void CreateNDate()
{
	// 造数据
	int n = 100000;
	srand(time(0));
	FILE* fin = fopen("data.txt", "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)//写入数据
	{
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void TopK()
{
	int k;
	printf("请输入k的值\n");
	scanf("%d", &k);
	int* kminheap = (int*)malloc(sizeof(int) * k);
	if (kminheap == NULL)
	{
		perror("malloc fail");
		return;
	}
	FILE* fout = fopen("data.txt", "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	// 读取文件中前k个数
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);
	}
	//建一个小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}
	// 读取剩下的N-K个数
	int x = 0;
	while (fscanf(fout, "%d", &x) > 0)
	{
		if (x > kminheap[0])
		{
			kminheap[0] = x;
			AdjustDown(kminheap, k, 0);
		}
	}
	printf("最大前%d个数:", k);
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
	printf("\n");
}

结语:

堆的相关内容到这里就结束了,我们知道堆只适用于完全二叉树,后面我们将要开始非完全二叉树的学习。最后如果发现文章中有任何问题,请帮助我指正,同时欢迎各位在评论区交流讨论。

相关推荐
yuanmazhiwu4 分钟前
计算机毕业设计:Python智慧出行数据分析与模式识别系统 Django框架 可视化 数据分析 PyEcharts 交通 深度学习(建议收藏)✅
人工智能·python·算法·数据分析·django·flask·课程设计
沉鱼.449 分钟前
第十一届题目
算法
C雨后彩虹14 分钟前
箱子之字形摆放
java·数据结构·算法·华为·面试
小肝一下2 小时前
每日两道力扣,day5
数据结构·c++·算法·leetcode·职场和发展·hot100
jiang_changsheng2 小时前
亚马逊的2026年最新算法变革自然流量分发机制“文本匹配”到“多模态意图理解”的范式革命
大数据·算法·推荐算法
OOJO7 小时前
c++---list介绍
c语言·开发语言·数据结构·c++·算法·list
别或许9 小时前
1、高数----函数极限与连续(知识总结)
算法
田梓燊9 小时前
code 560
数据结构·算法·哈希算法
笨笨饿9 小时前
29_Z变换在工程中的实际意义
c语言·开发语言·人工智能·单片机·mcu·算法·机器人
kobesdu9 小时前
综合强度信息的激光雷达去拖尾算法解析和源码实现
算法·机器人·ros·slam·激光雷达