101.【C语言】数据结构之二叉树的堆实现(顺序结构) 下

目录

1.堆删除函数HeapPop

一个常见的错误想法:挪动删除

正确方法

设计堆顶删除函数HeapPop

解析向下调整函数AdjustDown

向下调整最多次数

向下调整的前提

代码实现

2.测试堆删除函数

运行结果

3.引申问题

运行结果

4.练习

分析

代码

执行过程图

运行结果


承接100.【C语言】数据结构之二叉树的堆实现 上文章

1.堆删除函数HeapPop

尾删没有任何意义,删头才有意义(即删堆顶),删除之后要调整二叉树,仍然符合大根堆的性质

拿上图举例,现删除堆顶元素50

一个常见的错误想法:挪动删除

即{50,30,8,5,12,0,2,15,3}变成{30,8,5,12,0,2,15,3}

将{30,8,5,12,0,2,15,3}画成二叉树会发现既不是大根堆也不是小根堆

此错误方法不仅效率低下还将父子兄弟之间的关系全部弄乱了

因此禁止使用挪动删除!

正确方法

让堆顶元素和数组的最后一个元素进行交换(Swap函数),接着删除最后一个元素(ps->size--),再向下调整

设计堆顶删除函数HeapPop

堆顶能删除的前提:堆不能是空的(调用HeapEmpty函数检验)

空堆检查函数HeapEmpty

cpp 复制代码
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

交换函数Swap

cpp 复制代码
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
cpp 复制代码
void HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a,php->size,0);
}

解析向下调整函数AdjustDown

核心思想

左孩子和右孩子哪一个大,parent就换哪个,重复前述步骤直到满足大根堆或小根堆的性质

以下面这个为例说明

经过两次向下调整后,变成大根堆

向下调整最多次数

最坏情况是调整到叶节点,即次

向下调整的前提

左右子树为堆,

代码实现

一开始默认为左孩子,如果右孩子值大于左孩子,则进行调整

cpp 复制代码
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

提问

上述代码写的有没有问题?

答:if (achild + 1 > achild)有潜在的问题,可能会越界访问

child+1可能会大于或等于n

细节分析

改成下面这样行吗?

cpp 复制代码
if (a[child + 1] > a[child] && child+1 < n)

不行,要遵循&&的运算规则:先左后右,因此上方代码会先判断achild + 1 > achild再判断child+1 < n,可能会先越界访问,之后判断child+1 < n就无效了

因此改为

cpp 复制代码
if (child+1 < n && a[child + 1] > a[child])

2.测试堆删除函数

main.c中写入以下代码,之后下断点至return 0;

cpp 复制代码
#include "Heap.h"
int main()
{
	HP hp;
	HeapInit(&hp);
	HeapPush(&hp, 1);
	HeapPush(&hp, 3);
	HeapPush(&hp, 0);
	HeapPush(&hp, 5);
	HeapPush(&hp, 8);
	HeapPush(&hp, 12);
	HeapPush(&hp, 2);
	HeapPush(&hp, 5);
	HeapPush(&hp, 30);
	HeapPush(&hp, 50);

	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}

	return 0;
 }

备注:只要堆没有删空while (!HeapEmpty(&hp))就继续执行,这里要强调的点是:堆顶永远是堆中最大的数 ,因此只有删了堆顶,堆顶下面最大的数才能上去

运行结果

显然进行了堆排序

3.引申问题

若要打印上方排好序的堆中的前k个数(k<=n),代码怎么写?

答:稍加改动即可

cpp 复制代码
	int k = 0;
	scanf("%d", &k);
	while (!HeapEmpty(&hp)&&k--)
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}

运行结果

4.练习

给一个乱序数组,设计一个排序函数HeapSort,要求建立大根堆,写出代码

要求:不另外开辟空间

代码模板

cpp 复制代码
??? HeapSort(??????)
{
    //??????
}

int main()
{
	int arr[] = { 1,4,2,5,7,2,6,8,9,2 };
	HeapSort(??????);
	return 0;
}

分析

在无序堆(既不是大根堆也不是小根堆)上建立大根堆,则需要对数组中的每一个元素进行向上调整

,则可写循环

代码

向上调整建堆

cpp 复制代码
void HeapSort(int* arr, int n)
{
	for (int i = 1; i < n; i++)
	{
		AdjustUp(arr, i);
	}
}
int main()
{
	int arr[] = { 1,4,2,5,7,2,6,8,9,2 };
	int size = sizeof(arr)/sizeof(arr[0]);
	HeapSort(arr,size);
	return 0;
}

执行过程图

运行结果

相关推荐
Rabitebla6 分钟前
【系统性学习笔记】Linux 权限篇(二):文件属性、权限修改与目录权限的底层逻辑
linux·c++·笔记·学习·算法
system_error_x13 分钟前
C++ 编程基础入门教程
开发语言·c++
Amos_Web17 分钟前
Markdonw语法
开发语言·前端·javascript
远离UE418 分钟前
半拉格朗日学习笔记 动力学 实时渲染
人工智能·算法
小郎君65826 分钟前
MPTT 不是万能编号:一棵百万节点树的增删改与批量事务
算法
FPGA小徐34 分钟前
【一生一芯 / PA】AM 核心文件逐行解析(上):从 start.S 到 gpu.c
前端·算法
Wang's Blog35 分钟前
Java 项目实战: 交友项目 - 回顾和今日内容介绍
java·开发语言·交友
初心未改HD1 小时前
DSH Skill开发实战:SKILL.md是给LLM看的操作手册,不是给人看的README,踩了4个坑
java·大数据·开发语言
Wang's Blog1 小时前
Java 项目实战: 交友项目 - SpringData-Mongo 环境搭建和 CRUD
java·开发语言·交友
Tairitsu_H1 小时前
[动态规划#1] 子数组 | 最大子数组和 | 环形子数组的最大和 | 乘积最大子数组 | 乘积为正数的最长子数组
算法·动态规划