启幕数据结构算法雅航新章,穿梭C++梦幻领域的探索之旅——二叉树序列构造探秘——堆的奥义与实现诗篇

在这里插入图片描述

在这里插入图片描述

人无完人,持之以恒,方能见真我!!!
共同进步!!

文章目录

  • 一、堆的定义与结构
  • 二、堆的实现
    • 1.堆的初始化和销毁
      • 堆的初始化
      • 堆的销毁
    • 2.向上调整算法和入堆
      • 向上调整算法
      • 入堆
    • 3.向下调整算法和出堆顶数据
      • 向下调整算法
      • 出堆
    • 4.堆的有效数据个数和判空
      • 堆的有效数据个数
      • 堆的判空
    • 5.取堆顶数据
  • 三、堆的源码

一、堆的定义与结构

堆的本质是一颗完全二叉树,只是它的要求比完全二叉树更加严格,它要求每颗子树的根节点都是当前子树的最大值或最小值,当根节点是最大值时,它就是一个大根堆,当根节点是最小值时,它就是一个小根堆

在上篇文章中我们也提到了,存储完全二叉树可以使用数组,存储非完全二叉树可以使用链表,而堆就是一种特殊的完全二叉树,所以堆的存储我们就使用数组,也就是顺序表的形式,如图:

在这里插入图片描述

我们将堆这个完全二叉树从上至下,从左至右的数据存放在数组中,至于怎么保证它每颗子树的根节点都是当前子树的最大值或最小值,我们在入堆和出堆的位置细讲,而顺序表的结构我们已经很熟悉了,这里直接写出来:

typedef int HPDataType;typedef struct Heap
{HPDataType* arr;int size;int capacity;
}HP;

二、堆的实现

1.堆的初始化和销毁

堆的初始化

堆的初始化就是将数组置空,有效数据个数和容量大小置0,如下:

//堆的初始化
void HPInit(HP* php)
{assert(php);php->arr = NULL;php->size = php->capacity = 0;
}

堆的销毁

堆的销毁就是先判断数组是否为空,不为空就将它释放掉,因为数组的空间是我们向操作系统申请来的,不会主动释放,如果我们不主动释放就会造成内存泄漏,最后我们将数组置空,有效数据个数和容量大小置0,如下:

//销毁
void HPDestroy(HP* php)
{assert(php);if (php->arr)//不为空就释放{free(php->arr);}php->arr = NULL;php->size = php->capacity = 0;
}

2.向上调整算法和入堆

接下来就是入堆操作,也就是向堆中插入数据,但是我们要知道,一般往数组中插入数据都是向数组尾部插入,那么是不是就会出现,原本堆的每颗子树的根节点都是当前子树的最大值或最小值,但是从尾部插入数据后会打破这个平衡,如图:

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

可以看到,原本的堆是一个小根堆,但是我们插入一个5之后,它就不构成小根堆了,这个时候就要用到我们的向上调整算法,当然,如果插入一个数据后还依然构成小根堆的话,我们就不做处理即可

向上调整算法

在讲解向上调整算法时,我们就统一以小根堆为例,向上调整算法的本质就是将我们插入的数据当作孩子节点,让它和它的父节点进行比较

那么有了孩子节点,怎么找到父节点呢?其实我们在上一篇讲过,父节点parent的下标等于(child-1)/2,找到父节点后,我们就看插入的数据是不是比它的父节点还小,如果是那么就直接进行交换,否则就不做操作,如图:

在这里插入图片描述

在这里插入图片描述

但是我们发现交换一次后还是没有构成小根堆,所以向上调整算法要求,只要我们做了交换,那么就让child走到parent,parent再走到新的child的父节点,继续进行比较,直到child为0,此时它就没有父亲节点了,停止向上调整,如图:

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

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType x = *p1;*p1 = *p2;*p2 = x;
}//向上调整
void AdjustUp(HPDataType* arr,int child)
{//根据传来的孩子节点算父亲节点int parent = (child - 1) / 2;//child>0是循环条件,因为要保证数据是数组元素while (child > 0){//如果是建小堆,就是<if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);//更新父节点和子节点child = parent;parent = (child - 1) / 2;}else//child >= parent,不用交换{break;}}
}

这里是建小根堆的写法,如果想要建大堆,那么将 小于 改成 大于 即可

入堆

有了向上调整算法我们入堆就很简单了,只需要将数据插入到数组最后,然后调用向上调整函数,就可以让我们的堆不被打乱

但是我们同时要注意,插入数据之前要检查数组空间大小是否足够,如果不够的话要进行扩容,如下:

//入堆
void HPPush(HP* php, HPDataType x)
{assert(php);//入堆前检查空间,不够就扩容if (php->size == php->capacity){//判断capacity是否相等于0,不相等就给2倍php->capacity = php->capacity == 0 ? 4 : 2 * php->capacity;//使用realloc初始化tmp数组为0HPDataType* tmp = (HPDataType*)realloc(php->arr,sizeof(HPDataType) *(php->capacity));if (NULL == tmp){perror("realloc fail\n");return;}php->arr = tmp;php->capacity *= 2;//这一步不要忘记}//判断空间后,存放数据php->arr[php->size] = x;//向上调整AdjustUp(php->arr, php->size);php->size++;
}

3.向下调整算法和出堆顶数据

在正式了解向下调整算法和出堆顶数据之前,首先我们要知道堆删除数据是删除堆顶的数据,也就是下标为0的数据,因为堆顶的数据是最特殊的,它是整个堆最大或最小的值,我们在堆的应用会讲到它的用法

那么了解了这一点之后,我们再来想想怎么删除堆顶数据,如果直接头删的话,那么之前堆的结构会完全乱套,我们画个图就知道了,以小根堆为例,如下:

在这里插入图片描述

在这里插入图片描述

可以看到如果我们直接对堆进行头删的话,整个堆的数据都被打乱了,结构也变乱了,我们要调整的话也无从下手,接下来我们就来介绍删除堆顶数据的正确做法

删除堆顶数据的正确做法就是,交换最后一个数据和堆顶数据,然后让size- -,这样我们就只会影响最后一个数据和堆顶数据,不会影响其它节点,如图:

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

经过上面的操作,我们就可以发现,我们删除了堆顶数据,只是说将堆中的最后一个数据移到了堆顶,但是也只改变了堆中的最后一个数据的位置,不至于像头删那样将整个堆的结构打乱

那么将堆中的最后一个数据放到了堆顶,此时堆很可能不是一个有效的堆,所以我们需要从堆顶向下调整整个堆,我们需要一个向下调整算法

向下调整算法

经过上面的分析,我们知道堆删除数据后,堆顶元素可能不符合堆的要求,所以我们要从堆顶开始向下调整,要注意的是,我们举例都是以小根堆为例

具体方法就是,将堆顶当作父节点parent,根据2*parent找到它的孩子节点child,最后让父节点和孩子节点进行比较,如果孩子节点更小就进行交换,然后让父亲走到孩子的位置,孩子再走到新父亲的孩子节点

如果孩子节点比父节点更大的话就不做修改,跟我们的向上调整算法类似,但是我们要注意一个点,我们在向下调整的时候,需要看当前父节点的左孩子和右孩子谁小,父节点要和小的那个孩子进行交换,为什么呢?

因为如果父节点和较大的那个孩子进行交换的话,较大的那个孩子就成了堆顶,另一个较小的孩子就比堆顶小,不满足小根堆的条件,如图:

在这里插入图片描述

可以发现,在这种情况下,我们经过交换后并不符合堆的要求,因为原本的右孩子较小,但是父节点是和左孩子进行交换的,导致较大的左孩子到了堆顶,不符合堆的要求

所以我们在进行向下调整时,找到左孩子child后,还要先判断一下左右孩子谁更小,如果左孩子更小就不需要做更改,如果右孩子更小就让child++,这样就可以让child走到更小的右孩子了(注意左右孩子的关系,右孩子比左孩子的下标大1)

那么有了正确的思路之后我们重新走一遍上面的过程,看看有没有问题,如图:

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

通过一系列的画图分析,我们直接根据思路写出对应的代码即可,如下:

//向下调整--多传一个n,是循环的结束条件
void AdjustDown(HPDataType* arr, int parent, int n)
{//根据父节点算出左孩子int child = parent * 2 + 1;while (child < n)//向下调整不能越界{//如果右孩子存在,并且比左孩子小if (child + 1 < n && arr[child + 1] < arr[child]){child++;//就让孩子变成右孩子}//孩子小于父亲就交换if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);//交换后,更新child 、 parentparent = child;child = 2 * parent + 1;}else{//父节点小于子节点break;}}
}

出堆

上面我们其实已经完整讲解了出堆的过程,这里我们再次回顾一下,出堆就是指删除堆中的堆顶数据,方法就是交换堆顶和最后一个数据,让size- -,间接删除了堆顶数据,然后最后一个数据到了堆顶,再对它进行向下调整即可
   那么有了思路我们就可以直接写代码了,如下:

//出堆顶元素
void HPPop(HP* php)
{assert(php);assert(!HPEmpty(php));//堆不为空才出堆//删除数据---size是总大小,所以-1是最后一个元素的下标Swap(php->arr[0], &php->arr[php->size-1]);//交换首尾元素php->size--;//交换删除后,得到新首尾,向下调整AdjustDown(php->arr, 0, php->arr);//0是父节点
}

4.堆的有效数据个数和判空

堆的有效数据个数

堆的有效数据个数由size记录,直接返回size即可

//堆的有效数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}

堆的判空

堆的判空就是判断堆的有效数据个数是否为0,也是跟size相关

//判空
bool HPEmpty(HP* php)
{return php->size == 0;
}

5.取堆顶数据

取堆顶数据就是取堆中下标为0位置的数据

//取堆顶元素
HPDataType HPTop(HP* php)
{assert(php);return php->arr[0];
}

三、堆的源码

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* arr;int size;int capacity;
}HP;#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"//初始化
void HPInit(HP* php)
{assert(php);php->arr = NULL;php->size = php->capacity = 0;
}//销毁
void HPDestroy(HP* php)
{assert(php);if (php->arr)//不为空就释放{free(php->arr);}php->arr = NULL;php->size = php->capacity = 0;
}void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType x = *p1;*p1 = *p2;*p2 = x;
}//向上调整
void AdjustUp(HPDataType* arr,int child)
{//根据传来的孩子节点算父亲节点int parent = (child - 1) / 2;//child>0是循环条件,因为要保证数据是数组元素while (child > 0){//如果是建小堆,就是<if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);//更新父节点和子节点child = parent;parent = (child - 1) / 2;}else//child >= parent,不用交换{break;}}
}//入堆
void HPPush(HP* php, HPDataType x)
{assert(php);//入堆前检查空间,不够就扩容if (php->size == php->capacity){//判断capacity是否相等于0,不相等就给2倍php->capacity = php->capacity == 0 ? 4 : 2 * php->capacity;//使用realloc初始化tmp数组为0HPDataType* tmp = (HPDataType*)realloc(php->arr,sizeof(HPDataType) *php->capacity);if (NULL == tmp){perror("realloc fail\n");return;}php->arr = tmp;php->capacity *= 2;//这一步不要忘记}//判断空间后,存放数据php->arr[php->size] = x;//向上调整AdjustUp(php->arr, php->size);php->size++;
}//向下调整--多传一个n,是循环的结束条件
void AdjustDown(HPDataType* arr, int parent, int n)
{//根据父节点算出左孩子int child = parent * 2 + 1;while (child < n)//向下调整不能越界{//如果右孩子存在,并且比左孩子小if (child + 1 < n && arr[child + 1] < arr[child]){child++;//就让孩子变成右孩子}//孩子小于父亲就交换if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);//交换后,更新child 、 parentparent = child;child = 2 * parent + 1;}else{//父节点小于子节点break;}}
}//判空
bool HPEmpty(HP* php)
{return php->size == 0;
}//出堆顶元素
void HPPop(HP* php)
{assert(php);assert(!HPEmpty(php));//堆不为空才出堆//删除数据---size是总大小,所以-1是最后一个元素的下标Swap(php->arr[0], &php->arr[php->size-1]);//交换首尾元素php->size--;//交换删除后,得到新首尾,向下调整AdjustDown(php->arr, 0, php->arr);//0是父节点
}//堆的有效数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}//取堆顶元素
HPDataType HPTop(HP* php)
{assert(php);return php->arr[0];
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/73797.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“Failed to Load SteamUI.dll” 错误详解:全面解析与高效解决方案,助你快速修复 Steam 客户端问题

在使用 Steam 客户端时&#xff0c;你是否遇到过 failed to load steamui.dll 错误&#xff1f;这个令人头疼的问题可能导致 Steam 无法正常启动&#xff0c;影响游戏体验。Failed to load steamui.dll 错误通常与文件损坏、系统配置或软件冲突有关&#xff0c;但无需担心&…

STM32 DAC详解:从原理到实战输出正弦波

目录 一、DAC基础原理1.1 DAC的作用与特性1.2 DAC功能框图解析 二、DAC配置步骤2.1 硬件配置2.2 初始化结构体详解 三、DAC数据输出与波形生成3.1 数据格式与电压计算3.2 正弦波生成实战3.2.1 生成正弦波数组3.2.2 配置DMA传输3.2.3 定时器触发配置 四、常见问题与优化建议4.1 …

CNN 稠密任务经典结构

FCN UNet FPN FCNUNETFPNpadding无&#xff08;逐渐变小&#xff09; 有&#xff08;左右对称&#xff09;上采样 双线性双线性 最近邻跳跃链接 相加 Cropcat 1x1卷积相加 三个网络差不多&#xff0c;UNet名字最直观&#xff0c;后续流传…

AI学习第二天--监督学习 半监督学习 无监督学习

目录 1. 监督学习&#xff08;Supervised Learning&#xff09; 比喻&#xff1a; 技术细节&#xff1a; 形象例子&#xff1a; 2. 无监督学习&#xff08;Unsupervised Learning&#xff09; 比喻&#xff1a; 技术细节&#xff1a; 形象例子&#xff1a; 3. 半监督学…

Elasticsearch:为推理端点配置分块设置

推理端点对一次可处理的文本量有限&#xff0c;具体取决于模型的输入容量。分块&#xff08;Chunking&#xff09; 是指将输入文本拆分成符合这些限制的小块的过程&#xff0c;在将文档摄取到 semantic_text 字段时会进行分块。分块不仅有助于保持输入文本在可处理范围内&#…

Unity打包Android平台调用sherpa-onnx

https://github.com/xue-fei/sherpa-onnx-unity 最初测试了PC的Win和Linux平台&#xff0c;直接从nuget缓存包中拷贝相关文件&#xff0c;按示例写了语音转文字和文字转语音的测试代码&#xff0c;功能都正常。 然后是Android端&#xff0c;看了示例发现有编译好的jni.so之类的…

传统会议室接入神旗视讯-2 Android会议室大屏设备 (Maxhub, Newline, TCL等)

随着企业对视频会议安全性、稳定性和统一管理的需求日益增长&#xff0c;私有化视频会议系统凭借其全平台兼容性、高安全性部署和智能化会控能力&#xff0c;成为政企客户的核心选择。Android会议室大屏设备&#xff08;Maxhub, Newline, TCL等&#xff09;作为国内主流智能会议…

个人blog系统 前后端分离 前端js后端go

系统设计&#xff1a; 1.使用语言&#xff1a;前端使用vue&#xff0c;并使用axios向后端发送数据。后端使用的是go的gin框架&#xff0c;并使用grom连接数据库实现数据存储读取。 2.设计结构&#xff1a; 最终展示&#xff1a;仅展示添加模块&#xff0c;其他模块基本相似 前…

分支结构- P5717-三角形分类-第二十一天

洛谷题单 第二十一天&#xff1a;3.18&#xff08;周二&#xff09; 题目&#xff1a;分支结构–P5717 代码 #include <stdio.h>//本题目卡住的点&#xff1a;1.逻辑问题 2.对if-else if-else结构的运行理解&#xff0c;导致了逻辑混乱//注意&#xff1a;程序会组个…

华为OD机试 - 最长回文字符串 - 贪心算法(Java 2024 E卷 100分)

题目描述 如果一个字符串正读和反读都一样(大小写敏感),则称之为一个「回文串」。例如: level 是一个「回文串」,因为它的正读和反读都是 level。art 不是一个「回文串」,因为它的反读 tra 与正读不同。Level 不是一个「回文串」,因为它的反读 leveL 与正读不同(因大小…

C语言文件操作入门

本节重点 理解文件的形式与基本概念二进制文件与文本文件文件的打开与关闭文件读写函数、文件缓冲区 正文开始--------------------------------------------------------------------------------------------------------------------- 一、为什么使用文件 程序运行时数据存…

Doris:联邦认证

LDAP​ 接入第三方 LDAP 服务为 Doris 提供验证登录和组授权服务。 LDAP 验证登录​ LDAP 验证登录指的是接入 LDAP 服务的密码验证来补充 Doris 的验证登录。Doris 优先使用 LDAP 验证用户密码&#xff0c;如果 LDAP 服务中不存在该用户则继续使用 Doris 验证密码&#xff…

stm32第六天继电器

一&#xff1a;继电器 1.继电器的工作原理 继电器是一个电控开关&#xff0c;工作原理基于电磁感应&#xff0c;继电器包括一个电磁线圈和一组触点。常用于控制高电流或高电压的电路&#xff0c;例如自动控制原理&#xff0c;电力系统和自动化设备中&#xff0c;由于可靠性和电…

Vue渲染函数 - render 函数

文章目录 Vue渲染函数 - render 函数1. 什么是 render 函数2、页面展示过程3、render 函数的参数4. 如何使用&#xff08;1&#xff09;基本渲染&#xff08;2&#xff09;传递属性和事件&#xff08;3&#xff09;条件渲染 5. render 函数的实际使用6.View Design 组件中的使用…

单片机自学总结

自从工作以来&#xff0c;一直努力耕耘单片机&#xff0c;至今&#xff0c;颇有收获。从51单片机&#xff0c;PIC单片机&#xff0c;直到STM32&#xff0c;以及RTOS和Linux&#xff0c;几乎天天在搞:51单片机&#xff0c;STM8S207单片机&#xff0c;PY32F003单片机&#xff0c;…

go回调函数的使用

在Go语言中&#xff0c;回调函数可以有参数&#xff0c;也可以没有参数。它们的定义和使用方式略有不同&#xff0c;但本质上都是将函数作为参数传递给另一个函数&#xff0c;并在适当的时候调用它。以下是带参数和不带参数的回调函数的示例和说明。 1. 不带参数的回调函数 不…

在 Ubuntu 中配置 NFS 共享服务的完整指南

前言 网络文件系统&#xff08;NFS&#xff09;作为 Linux 系统间实现文件共享的标准协议&#xff0c;在分布式计算和容器化部署场景中具有重要作用。本文将详细演示如何在 Ubuntu 系统上配置 NFS 服务端与客户端&#xff0c;并实现可靠的持久化挂载。 一、环境准备 系统要求…

TypeScript Symbols 深度解析:在 Vue3 中的高级应用实践

一、Symbols 核心特性解析 1.1 什么是 Symbol&#xff1f; Symbol 是 ES6 引入的原始数据类型&#xff0c;表示唯一且不可变的值&#xff0c;主要解决对象属性名冲突问题。在 TypeScript 中&#xff0c;我们通过 symbol 类型获得完整的类型支持&#xff1a; const SERIAL_KE…

无需刷机、root,畅享原生安卓的丝滑体验。

Apex Launcher 是一款历史悠久的 Android 桌面启动器&#xff0c;诞生于 Android 系统早期&#xff08;Android 4.0 时代&#xff09;。当时&#xff0c;Android 系统的默认界面被认为较为简陋&#xff0c;无法满足一些追求个性化和高效操作的用户需求。因此&#xff0c;许多开…