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

在这里插入图片描述

文章目录

  • 一、堆的定义与结构
  • 二、堆的实现
    • 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.mzph.cn/diannao/62498.shtml

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

相关文章

【北京迅为】iTOP-4412全能版使用手册-第二十章 搭建和测试NFS服务器

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…

大数据新视界 -- 大数据大厂之 Hive 函数库:丰富函数助力数据处理(上)(11/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

【Docker】Docker配置远程访问

配置Docker的远程访问&#xff0c;你需要按照以下步骤进行操作&#xff1a; 1. 在Docker宿主机上配置Docker守护进程监听TCP端口 Docker守护进程默认只监听UNIX套接字&#xff0c;要实现远程访问&#xff0c;需要修改配置以监听TCP端口。 ‌方法一&#xff1a;修改Docker服务…

LuaForWindows_v5.1.5-52.exe

Releases rjpcomputing/luaforwindows GitHub #lua C:\Users\Administrator\Desktop\test.lua print("Hello lua&#xff01;") print("ZengWenFeng 13805029595")

antd table 自定义表头过滤表格内容

注意&#xff1a;该功能只能过滤可一次性返回全部数据的表格&#xff0c;通过接口分页查询的请自主按照需求改动哈~ 实现步骤&#xff1a; 1.在要过滤的列表表头增加过滤图标&#xff0c;点击图标显示浮窗 2.浮窗内显示整列可选选项&#xff0c;通过勾选单选或者全选、搜索框来…

【分页查询】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

CSP-J初赛不会备考咋办?

以下备考攻略仅供参考&#xff0c;如需资料请私信作者&#xff01;求支持&#xff01; 目录 一、编程语言基础 1.语法知识 -变量与数据类型 -运算符 -控制结构 -函数 2.标准库的使用 -输入输出流 -字符串处理 -容器类&#xff08;可选&#xff09; 二、算法与数据结构 1.基…

火语言RPA流程组件介绍--键盘按键

&#x1f6a9;【组件功能】&#xff1a;模拟键盘按键 配置预览 配置说明 按键 点击后,在弹出的软键盘上选择需要的按键 执行后等待时间(ms) 默认值300,执行该组件后等待300毫秒后执行下一个组件. 输入输出 输入类型 万能对象类型(System.Object)输出类型 万能对象类型…

springboot vue 开源 会员收银系统 (12)购物车关联服务人员 订单计算提成

前言 完整版演示 http://120.26.95.195/ 开发版演示 http://120.26.95.195:8889/ 在之前的开发进程中&#xff0c;我们完成订单的挂单和取单功能&#xff0c;今天我们完成购物车关联服务人员&#xff0c;用户计算门店服务人员的提成。 1.商品关联服务人员 服务人员可以选择 一…

JiaJia-CP-1,2,3的WP(1)

一.JiaJia-CP-1 这是ctfshow里电子取证里面的题&#xff0c;以下下是我做题时的WP 审题&#xff0c;最后提交格式要进行md5 加密&#xff0c;给各位CTFer们找了一个md5加密的网站&#xff08;加紧收藏哦&#xff09;&#xff1a; MD5 在线加密工具 | 菜鸟工具 1.拿到题目&am…

【C语言】关于 JavaScript 与 C语言在函数嵌套定义方面的差异探讨

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;JavaScript&#xff1a;允许函数嵌套定义与闭包的灵活性JavaScript 的闭包机制JavaScript 中嵌套函数的应用场景 &#x1f4af;C 语言&#xff1a;不允许函数嵌套定义的…

基于SpringBoot共享汽车管理系统【附源码】

基于SpringBoot共享汽车管理系统 效果如下&#xff1a; 系统注册页面 系统登陆页面 系统管理员主页面 用户信息管理页面 汽车投放管理页面 使用订单页面 汽车归还管理页面 研究背景 随着计算机技术和计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所。二十…

【天地图】HTML页面实现车辆轨迹、起始点标记和轨迹打点的完整功能

目录 一、功能演示 二、完整代码 三、参考文档 一、功能演示 运行以后完整的效果如下&#xff1a; 点击开始&#xff0c;小车会沿着轨迹进行移动&#xff0c;点击轨迹点会显示经纬度和时间&#xff1a; 二、完整代码 废话不多说&#xff0c;直接给完整代码&#xff0c;替换…

【Jenkins】docker 部署 Jenkins 踩坑笔记

文章目录 1. docker pull 超时2. 初始化找不到 initialAdminPassword 1. docker pull 超时 docker pull 命令拉不下来 docker pull jenkins/jenkins:lts-jdk17 Error response from daemon: Get "https://registry-1.docker.io/v2/": 编辑docker配置 sudo mkdir -…

Docker: 教程07 - ( 如何对 Docker 进行降级和升级)

如果我们使用 docker 来管理容器&#xff0c;那么保持 docker 引擎的更新将会是十分重要的&#xff0c;这一篇文章我们将会讨论如何对Docker 进行降级和升级。 准备工作 - docker 环境 我们需要拥有一个安装好 docker 的运行环境。 如果你需要了解如何安装 docker 可以通过如…

SOLID原则学习【目录篇】

文章目录 1. 前言2. ‘S’--单一职责原则&#xff08;Single Responsibility Principle&#xff09;3. ‘O’--开闭原则4. ‘L’--里氏替换原则5. ‘I’--接口隔离原则6. ‘D’--依赖倒置原则参考 1. 前言 SOLID原则是面向对象设计&#xff08;OOD&#xff09;中五个核心设计原…

Flink在Linux系统上的安装与入门

一、Flink的引入 这几年大数据的飞速发展&#xff0c;出现了很多热门的开源社区&#xff0c;其中著名的有Hadoop、Storm&#xff0c;以及后来的Spark&#xff0c;他们都有着各自专注的应用场景。Spark 掀开了内存计算的先河&#xff0c;也以内存为赌注&#xff0c;赢得了内存计…

【大数据学习 | Spark调优篇】Spark之JVM调优

1. Java虚拟机垃圾回收调优的背景 如果在持久化RDD的时候&#xff0c;持久化了大量的数据&#xff0c;那么Java虚拟机的垃圾回收就可能成为一个性能瓶颈。因为Java虚拟机会定期进行垃圾回收&#xff0c;此时就会追踪所有的java对象&#xff0c;并且在垃圾回收时&#xff0c;找…

SpringSecurity6

1.快速入门 2.SpringSecurity底层原理 使用的是委托过滤器,委托过滤器实际上就是 sevlet 过滤器 将自己放入Sevlet环境下 然后里面是一个 过滤器链代理 代理类下又是一个代理过滤器链的集合, 对于不同请求可以有不同的过滤器链, springsecurity有个默认的过滤器链 Defau…

电磁兼容(EMC):磁性材料(永磁、软磁、功能磁)详解

目录 一、磁性材料概述 二、常用磁性材料分类 1. 永磁材料 2. 软磁材料 3. 功能性磁材 三、软磁材料特点 一、磁性材料概述 磁性材料是指由过渡元素铁&#xff08;Fe&#xff09;、钴&#xff08;Co&#xff09;、镍&#xff08;Ni&#xff09;及其合金等组成的能够直接…