【初阶数据结构和算法】二叉树顺序结构---堆的定义与实现(附源码)

在这里插入图片描述

文章目录

  • 一、堆的定义与结构
  • 二、堆的实现
    • 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 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{break;}}
}

   在上面我们演示的是一个小根堆的写法,就是比较孩子和父亲谁小,如果我们要构建一个大根堆,就要比较孩子和父亲谁大,只需要将比较时的小于改成大于即可

入堆

   有了向上调整算法我们入堆就很简单了,只需要将数据插入到数组最后,然后调用向上调整函数,就可以让我们的堆不被打乱
   但是我们同时要注意,插入数据之前要检查数组空间大小是否足够,如果不够的话要进行扩容,如下:

//入堆
void HPPush(HP* php, HPDataType x)
{assert(php);//检查空间是否足够if (php->size == php->capacity){php->capacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr, php->capacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc");return;}php->arr = tmp;}//插入数据php->arr[php->size] = x;//调用向上调整算法AdjustUp(php->arr, php->size);php->size++;
}

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

   在正式了解向下调整算法和出堆顶数据之前,首先我们要知道堆删除数据是删除堆顶的数据,也就是下标为0的数据,因为堆顶的数据是最特殊的,它是整个堆最大或最小的值,我们在堆的应用会讲到它的用法
   那么了解了这一点之后,我们再来想想怎么删除堆顶数据,如果直接头删的话,那么之前堆的结构会完全乱套,我们画个图就知道了,以小根堆为例,如下:
在这里插入图片描述
   可以看到如果我们直接对堆进行头删的话,整个堆的数据都被打乱了,结构也变乱了,我们要调整的话也无从下手,接下来我们就来介绍删除堆顶数据的正确做法
   删除堆顶数据的正确做法就是,交换最后一个数据和堆顶数据,然后让size- -,这样我们就只会影响最后一个数据和堆顶数据,不会影响其它节点,如图:
在这里插入图片描述
在这里插入图片描述
   经过上面的操作,我们就可以发现,我们删除了堆顶数据,只是说将堆中的最后一个数据移到了堆顶,但是也只改变了堆中的最后一个数据的位置,不至于像头删那样将整个堆的结构打乱
   那么将堆中的最后一个数据放到了堆顶,此时堆很可能不是一个有效的堆,所以我们需要从堆顶向下调整整个堆,我们需要一个向下调整算法

向下调整算法

   经过上面的分析,我们知道堆删除数据后,堆顶元素可能不符合堆的要求,所以我们要从堆顶开始向下调整,要注意的是,我们举例都是以小根堆为例
   具体方法就是,将堆顶当作父节点parent,根据2*parent找到它的孩子节点child,最后让父节点和孩子节点进行比较,如果孩子节点更小就进行交换,然后让父亲走到孩子的位置,孩子再走到新父亲的孩子节点
   如果孩子节点比父节点更大的话就不做修改,跟我们的向上调整算法类似,但是我们要注意一个点,我们在向下调整的时候,需要看当前父节点的左孩子和右孩子谁小,父节点要和小的那个孩子进行交换,为什么呢?
   因为如果父节点和较大的那个孩子进行交换的话,较大的那个孩子就成了堆顶,另一个较小的孩子就比堆顶小,不满足小根堆的条件,如图:
在这里插入图片描述
在这里插入图片描述
   可以发现,在这种情况下,我们经过交换后并不符合堆的要求,因为原本的右孩子较小,但是父节点是和左孩子进行交换的,导致较大的左孩子到了堆顶,不符合堆的要求
   所以我们在进行向下调整时,找到左孩子child后,还要先判断一下左右孩子谁更小,如果左孩子更小就不需要做更改,如果右孩子更小就让child++,这样就可以让child走到更小的右孩子了(注意左右孩子的关系,右孩子比左孩子的下标大1)
   那么有了正确的思路之后我们重新走一遍上面的过程,看看有没有问题,如图:
在这里插入图片描述
在这里插入图片描述
   那么有了上图的思路,我们直接根据思路写出对应的代码即可,如下:

//向下调整算法
void AdjustDown(HPDataType* arr, int parent, int n)
{//根据给出的孩子父节点算出对应的左孩子int child = parent * 2 + 1;//如果child没有越界就持续向下调整while (child < n){//如果右孩子存在并且更小,就让child++走到更小的右孩子上if (child + 1 < n && arr[child + 1] < arr[child]){child++;}//如果孩子比父亲更小就进行交换if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]); //交换完之后parent重新走到child的位置//child走到新parent的左孩子位置parent = child;child = parent * 2 + 1;}//如果较小的孩子都比父节点大//说明调整完毕,退出循环else{break;}}
}

出堆

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

//出堆顶元素
void HPPop(HP* php)
{assert(php);php->size--;Swap(&php->arr[0], &php->arr[php->size]);AdjustDown(php->arr, 0, php->size);
}

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 <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int HPDataType;typedef struct Heap
{HPDataType* arr;int size;int capacity;
}HP;//堆的初始化
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* x, HPDataType* y)
{HPDataType tmp = *x;*x = *y;*y = tmp;
}//向上调整
void AdjustUp(HPDataType* arr, int child)
{int parent = (child - 1) / 2;while (child > 0){if (arr[child] < arr[parent]){Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//入堆
void HPPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){php->capacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr, php->capacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc");return;}php->arr = tmp;}php->arr[php->size] = x;AdjustUp(php->arr, php->size);php->size++;
}//向下调整算法
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]); parent = child;child = parent * 2 + 1;}else{break;}}
}//出堆顶元素
void HPPop(HP* php)
{assert(php);php->size--;Swap(&php->arr[0], &php->arr[php->size]);AdjustDown(php->arr, 0, php->size);
}//取堆顶数据
HPDataType HPTop(HP* php)
{assert(php);return php->arr[0];
}//堆的有效数据个数
int HPSize(HP* php)
{assert(php);return php->size;
}//堆的判空
bool HPEmpty(HP* php)
{return php->size == 0;
}

   今天堆的分享就到这里啦,有什么疑问欢迎私信,下一篇文章我们就开始介绍二叉树链式结构了,感受递归的暴力美学,敬请期待吧!
   bye~

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

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

相关文章

进程的知识

1. 冯诺依曼体系结构 输入设备&#xff1a;键盘&#xff0c;话筒&#xff0c;网卡&#xff0c;磁盘&#xff08;外存&#xff09; 外设&#xff1a; 输出设备&#xff1a;显示器&#xff0c;磁盘&#xff0c;网卡&#xff0c;打印机 CPU运算器控制器 存储器&#xff1a…

创建HTTPS网站

每天&#xff0c;我们都会听到网络上发生身份盗窃和数据侵权的案例&#xff0c;这导致用户对自己访问的网站更加怀疑。他们开始更加了解自己将个人信息放在哪里以及信任哪些类型的网站。了解如何使网站使用HTTPS变得比以往任何时候都更加重要。 解读缩略词&#xff1a;HTTP与HT…

pytest+allure生成报告显示loading和404

pytestallure执行测试脚本后&#xff0c;通常会在电脑的磁盘上建立一个临时文件夹&#xff0c;里面存放allure测试报告&#xff0c;但是这个测试报告index.html文件单独去打开&#xff0c;却显示loading和404, 这个时候就要用一些办法来解决这个报告显示的问题了。 用命令产生…

如何使用ST7789展现图片?[ESP--4]

本节我们继续ESP和ST 7789的话题&#xff0c;这节课我们来学学如何展示图片,话不多说&#xff0c;先上效果 好&#xff0c;教程开始~前情提要&#xff0c;要看懂这篇&#xff0c;建议搭配楼主的前两期文章 使用ESP32驱动LCD-ST7789屏幕[ESP–2] 加速你的LCD-ST7789屏幕&#xf…

南京仁品耳鼻喉专科医院:12月启动公益义诊月

专业医疗资源送至“家门口”&#xff01;南京仁品耳鼻喉专科医院启动公益义诊月 随着2024年即将步入尾声&#xff0c;南京仁品耳鼻喉医院为回馈社会&#xff0c;提升公众健康福祉&#xff0c;将于12月隆重推出“三甲专家公益义诊月”活动。此次活动旨在通过汇聚众多耳鼻喉领域…

数据结构—排序算法(python实现)

数据结构 脑图排序算法1.冒泡排序1.1步骤1.2python代码实现冒泡&#xff1a;1.3分析冒泡 2.插入排序2.1步骤2.2python代码实现插入排序&#xff1a;2.3分析插入 3.选择排序3.1步骤3.2python代码实现&#xff1a;3.3分析选择 4.快速排序4.1步骤4.2python代码实现&#xff1a;4.3…

Pinia管理用户数据

Pinia 是 Vue3 的新一代状态管理库&#xff0c;提供了更简单的 API 和更好的 TypeScript 支持。它作为 Vuex 的替代方案&#xff0c;成为了管理 Vue 应用状态的首选。Pinia 是 Vue3 的新一代状态管理库。与 Vuex 相比&#xff0c;Pinia 提供了更简单的 API、更好的性能&#xf…

远程协助软件Todesk免费版有什么限制

大名鼎鼎的远程todesk也开始出限制了&#xff0c;国内远程协助一直是向日葵一家独大&#xff0c;todesk起来以后慢慢占领了部分市场&#xff0c;随用户越来越多&#xff0c;其服务器也开始不堪重负了&#xff0c;于2024年的6月发了公告&#xff0c;出告了限制发表的措施具体如下…

电路基础——相量法

相量法 为什么要使用相量表示&#xff1f; 电路方程是微分方程&#xff1a; 电路的运算&#xff08;如KCL、KVL方程运算&#xff09;会涉及到两个正弦量的相加&#xff1a; 如下图所示同频率的正弦量相加仍得到同频率的正弦量&#xff0c;因此只需确定初相位和有效值。 基于上…

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libncurses.so.5的问题 2024/11/29 21:11 缘起&#xff1a;中科创达的高通CM6125开发板的Android10的编译环境需要。 vendor/qcom/proprietary/commonsys/securemsm/seccamera/service/jni/jni_if.…

redis的应用----缓存

redis的应用----缓存 一、缓存的概念二、使用redis作为缓存2.1使用redis作为缓存的原因2.2缓存机制的访问步骤 三、缓存的更新策略3.1定期更新3.2实时更新3.3淘汰策略 四、缓存常见的问题4.1缓存预热(Cache preheating)4.2缓存穿透(Cache penetration)4.3缓存雪崩(Cache avalan…

【S500无人机】--地面端下载

之前国庆的时候导师批了无人机&#xff0c;我们几个也一起研究了几次&#xff0c;基本把无人机组装方面弄的差不多了&#xff0c;还差个相机搭载&#xff0c;今天我们讲无人机的调试 硬件配置如下 首先是地面端下载&#xff0c;大家可以选择下载&#xff1a; Mission Planne地…

C++设计模式(装饰模式)

一、介绍 1.动机 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”&#xff0c;由于继承为类型引入的静态特质&#xff0c;使得这种扩展方式缺乏灵活性&#xff1b;并且随着子类的增多&#xff08;扩展功能的增多&#xff09;&#xff0c;各种子类的组合&#xff0…

Spring Cloud(Kilburn 2022.0.2版本)系列教程(五) 服务网关(SpringCloud Gateway)

Spring Cloud(Kilburn 2022.0.2版本)系列教程(五) 服务网关(SpringCloud Gateway) 一、服务网关 1.1 什么是网关 在微服务架构中&#xff0c;服务网关是一个至关重要的组件。它作为系统的入口&#xff0c;负责接收客户端的请求&#xff0c;并将这些请求路由到相应的后端服务…

MySQL底层概述—7.优化原则及慢查询

大纲 1.Explain概述 2.Explain详解 3.索引优化数据准备 4.索引优化原则详解 5.慢查询设置与测试 6.慢查询SQL优化思路 1.Explain概述 使用Explain关键字可以模拟查询优化器来执行SQL查询语句&#xff0c;从而知道MySQL是如何处理SQL语句的&#xff0c;从而分析出查询语句…

【前端】Vue3+Vite如何进行多环境配置呢

在项目或产品的迭代过程中需要分不同的环境&#xff0c;那么使用vitevue3开发时&#xff0c;该如何进行配置呢 1、添加配置文件 .env.xxx .env.xxx 需要与src在同一级目录下 例如&#xff1a; 开发环境&#xff1a; .env.development 开发环境&#xff1a; .env.test 生产环…

FreeSWITCH 简单图形化界面36 -使用mod_sms发送短消息

FreeSWITCH 简单图形化界面36 -使用mod_sms发送短消息 0、测试环境1、mod_sms模块安装2、编写聊天规则2.1 使用xml文件测试一下 2.2 使用脚本文件测试一下 0、测试环境 http://myfs.f3322.net:8020/ 用户名&#xff1a;admin&#xff0c;密码&#xff1a;admin FreeSWITCH界面…

广域网技术

企业需要通过广域网将这些分散在不同地理位置的分支机构连接起来 早期广域网技术概述 广域网&#xff1a;连接不同地区局域网的网络&#xff0c;能够横跨几个洲提供远距离通信&#xff0c;形成国际性的远程网络 广域网设备角色介绍&#xff1a; CE&#xff1a;用户端连接服务…

[GKCTF 2021]签到

[GKCTF 2021]签到 wireshark跟踪http流&#xff0c;基本编解码&#xff0c;倒叙&#xff0c;栅栏密码 找到cat /f14g 把包里返回的字符串先hex解码&#xff0c;再base64解码&#xff0c;看到一个时间是倒叙&#xff0c;不含flag 继续往下面翻&#xff0c;可以看到cat%2Ff14g%7…

ROS VSCode调试方法

VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…