【初阶数据结构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");
}

结语:

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

相关推荐
Yvonne爱编码1 小时前
二叉树高频题精讲 | 从入门到熟练掌握二叉树操作
java·开发语言·数据结构·链表·二叉树
kaikaile19951 小时前
基于PCNN和NSCT的图像融合MATLAB实现
开发语言·图像处理·算法·matlab
Zik----1 小时前
cs研究生面试机试题(持续更新)
算法
1231566802 小时前
PAT 1017 A除以B
c语言·数据结构·算法·pat考试
芯片-嵌入式2 小时前
具身智能(2):OpenExplorer下的模型量化
人工智能·深度学习·算法
voltina2 小时前
一致性哈希+虚拟节点
数据结构
Yusei_05232 小时前
C++14入门
c++·算法
YLXA2 小时前
1.helle_cuda学习
linux·学习·算法
Storynone2 小时前
【Day21】LeetCode:93. 复原IP地址,78. 子集,90. 子集 II
python·算法·leetcode