C语言实现的堆的向下调整算法和建堆算法

Posted 胖仙人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现的堆的向下调整算法和建堆算法相关的知识,希望对你有一定的参考价值。

->Gitee源代码点击这里<-
堆是特殊的二叉树,其性质为:
①堆中的某个结点总是不大于或不小于其子结点
②堆总是完全二叉树。(第1到第n-1层为满,最后一层从左往右结点连续)
我们经常用顺序结构的数组来实现二叉树
对于一个普通的数组int a[10] = 18,29,42,8,50,9,22,88,19,74;
如果该数组表示的是二叉树,则其逻辑结构为

而目前该二叉树还能称为堆,所以我们要借助一些算法来把一个表示二叉树的数组变成堆。

一、向下调整算法

当父节点的子树已经是大堆或者小堆,而整个二叉树还不满足堆的条件时,则需要向下调整算法来把二叉树变成堆

如图,根节点18的子树都符合大堆的条件(父节点>=子结点),但二叉树本身还不是大堆,这是我们就需要利用向下调整算法来将该二叉树调整为大堆,即:

调整思路:
以二叉树18,29,42,8,28,9,22,3,4,19为例
从需要调整的那个结点开始,把该节点视作父节点,比较其子节点的大小,取大的那个和父结点比较,如果大于父节点,则交换两个结点的值

图中要调整的是结点18 ,比较其两个子节点29 和42的大小,42较大,再比较42和18,42较大,交换42和18
此时42变为根结点,18变为右子树的父节点。如下图:

此时二叉树仍然不满足大堆的条件,以18为父节点的右子树还符合大堆的条件,仍需要向下调整,重复上述步骤,交换18 和 22:

得到下图结果:

此时二叉树已经调整为大堆
其用数组表示为:42,29,22,8,28,9,18,3,4,19;
我们借助该例子来写出大堆的向下调整算法的代码实现
实现代码时,我们要了解以下内容:
a. 父节点坐标parent和子节点的关系为:
右子结点坐标 right= parent*2+2;
左子节点坐标 left = parent*2+1;
b. 设计算法函数时,我们不但要将数组传给函数,还要告诉函数从那个结点开始调整,即参数还需要一个坐标变量;同时调整过程是一个循环的动作,还需要传入数组的长度控制循环的停止
c. 向下调整的过程中(以调大堆为例),本质是就是不断地调整父节点和子节点的关系;当父节点比两个子结点中最大的那个结点还要小时,则说明还需要调整;反之则已经调整完成
d. 调整过程中可能会出现一个父节点只有一个子节点的情况,如上图中的[4]结点和[9]结点,此时则要注意细节,防止越界访问

void Swap(int* a, int* b) //交换函数

	int tmp = *a;
	*a = *b;
	*b = tmp;


void AdjustDown(int* a, int pos,int size)

	int parent = pos;
	int node = parent * 2 + 1;   //先默认左节点是两个结点中较大的一个
	while (parent * 2 + 1 < size) //调整过程是一个循环,当找到最后一层,即父节点已经没有子节点的时候,则说明调整完成
	
		if (a[node] < a[node + 1] && node+1<size) //比较左节点和右节点的大小,同时要注意没有右节点的情况
		
			node = node + 1;   //如果右节点较大,则和parent交换的结点就要变成右节点
		
		if (a[parent] < a[node])  //判断父结点和子节点的大小,如果子节点大,则交换
		
			Swap(&a[parent], &a[node]);//交换
            
			//交换完成后,需要重新定位父节点和子节点
			parent = node;
			node = parent * 2 + 1;
		
		else 
		
			break;   //如果父节点大,则说明已经整个二叉树已经调整完成了
		
	

int main()

	int a[10] =  18, 29, 42, 8, 28, 9, 22, 3, 4, 19 ;
	int size = sizeof(a) / sizeof(a[0]);
	AdjustDown(a, 0,size);
	for (int i = 0; i < size;i++)
	
		printf("%d ", a[i]);
	
	return 0;

[运行结果]

二、建堆算法

建堆算法建立在向下调整算法的基础之上。其思想在于,从一棵二叉树的最后一个子树开始调整,把每一个子树都调整成大堆(或小堆),以小到大,从而将整个二叉树调整为大堆(或小堆)
以二叉树int a[10] = 18,29,42,8,50,9,22,88,19,74;为例

要将整个二叉树变为大堆,则从最后一个子树开始调整

利用向下调整算法,将小子树调整成小子大堆:

依次类推,从后往前调整每一棵小子树,直到把整个二叉树调整完成
倒数第二棵子树

倒数第三棵子树

倒数第四棵子树

调整整棵树,此时,根节点的左右子树已经都满足大堆的条件,则可以对根节点使用向下调整算法,将整棵二叉树调整为大堆
![image.png](https://img-blog.csdnimg.cn/img_convert/ac51435c428c3455f42e4bba69dfcf42.png#clientId=uf2e80c41-92ff-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=317&id=u44e59152&margin=[object Object]&name=image.png&originHeight=633&originWidth=676&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52811&status=done&style=none&taskId=udfbd0805-ab96-4487-b056-5f92ebc7bbd&title=&width=338)
数组表示为:88,74,42,29,50,9,22,8,19,18
接下来就是代码实现:
总结建堆算法,就是将子树使用向下调整算法变为大堆,由小到大。
所以首先,我们要有向下调整算法

void Swap(int* a, int* b) //交换函数

	int tmp = *a;
	*a = *b;
	*b = tmp;


void AdjustDown(int* a, int pos,int size)

	int parent = pos;
	int node = parent * 2 + 1;   //先默认左节点是两个结点中较大的一个
	while (parent * 2 + 1 < size) //调整过程是一个循环,当找到最后一层,即父节点已经没有子节点的时候,则说明调整完成
	
		if (a[node] < a[node + 1] && node+1<size) //比较左节点和右节点的大小,同时要注意没有右节点的情况
		
			node = node + 1;   //如果右节点较大,则和parent交换的结点就要变成右节点
		
		if (a[parent] < a[node])  //判断父结点和子节点的大小,如果子节点大,则交换
		
			Swap(&a[parent], &a[node]);//交换
            
			//交换完成后,需要重新定位父节点和子节点
			parent = node;
			node = parent * 2 + 1;
		
		else 
		
			break;   //如果父节点大,则说明已经整个二叉树已经调整完成了
		
	

其次,如何建堆:

void CreateHeap(int* a, int size)

	int child = size - 1;   //要找到最后一个子树的父节点,就要先找最后一棵子树的子节点
	int parent = ((child)-1) / 2;  //通过子节点找到父节点
	for (int i = parent; parent >=0 ; parent--)  //循环,将所有子树转换为大堆,当二叉树的根结点也向下调整完毕后,循环停止
	
		AdjustDown(a, parent, size);
	

测试:

int main()

	int a[10] =  18,29,42,8,50,9,22,88,19,74 ;
	int size = sizeof(a) / sizeof(a[0]);
	CreateHeap(a, size);
	for (int i = 0; i < size; i++)
	
		printf("%d ", a[i]);
	
	return 0;

以上是关于C语言实现的堆的向下调整算法和建堆算法的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现的堆的向下调整算法和建堆算法

基于C语言的堆排序

基于C语言的堆排序

基于C语言的堆排序

堆的实现及应用

堆排序+TOPK问题