【数据结构初阶】--- 堆

文章目录

    • 一、什么是堆?
      • 二叉树
      • 完全二叉树
      • 堆的分类
      • 堆的实现方法
    • 二、堆的操作
      • 堆的定义
      • 初始化
      • 插入数据(包含向上调整详细讲解)
      • 向上调整
      • 删除堆顶元素(包含向下调整详细讲解)
      • 向下调整
      • 返回堆顶元素
      • 判断堆是否为空
      • 销毁
    • 三、建堆
      • 向上调整建堆
      • 向下调整建堆

一、什么是堆?

堆其实就是个完全二叉树

先了解一下什么是树
就像链表中的节点,不过树中的节点至少指向一个或多个节点,但是,一个结点不能被多个节点指向
在这里插入图片描述

二叉树

堆是完全二叉树,那完全二叉树什么样呢?
先看看二叉树
像这样的,与普通的树不同在于二叉树中的每个结点只能指向两个不同的结点
在这里插入图片描述

完全二叉树

完全二叉树:

  • 最后一行的节点从左到右是连续的,最少可以是一个结点
  • 其余行必须是满的
  • 当最后一行满结点时又叫做满二叉树,这是完全二叉树的特殊情况

接下来就要看看一下完全二叉树的样子
在这里插入图片描述

堆的分类

堆分为大堆和小堆

  • 小堆:树任何一个父亲都小于等于孩子
    在这里插入图片描述
  • 大堆:树任何一个父亲都大于等于孩子
    在这里插入图片描述

堆的实现方法

上面用图描述的堆实际上是逻辑模型,而真实的存放这些数据是用数组来存放,这也叫物理模型
90存在数组下标为0的位置,75存在数组下标为1的位置,80存在数组下标为2的位置,以此类推
好像明明可以用链表存储,为什么非要用数组呢?
因为根据堆的特点,是可以对无序的数组进行排序,并且时间复杂度是O(N*logN)级别的,这样的速度是很优秀的。

二、堆的操作

//堆的初始化
void HeapInit(Heap* hp);
//堆的销毁
void HeapDestory(Heap* hp);

//向堆中插入数据
void HeapPush(Heap* hp, HDataType x);
//删除堆顶元素
void HeapPop(Heap* hp);
//返回堆顶数据
HDataType HeapTop(Heap* hp);
//判断堆是否为空
bool HeapEmpty(Heap* hp);

//向上调整
void AdjustUp(int* arr, int n);
//向下调整
void AdjustDown(int* arr, int n, int pos);//n是数组的个数,pos是开始调整的位置

堆的定义

堆虽然是个完全二叉树,但它是为了服务数组实现高效排序,因此我们就用数组来实现堆

typedef int HDataType;
typedef struct Heap
{HDataType* arr;int size;//数组的有效存储个数int capacity;//数组的容量
}Heap;

初始化

给堆初始化时我并没有给其开辟空间,所以后面插入数据时才会开辟,那么capacity和size自然也是0,给指针arr置空

void HeapInit(Heap* hp)
{assert(hp);hp->arr = NULL;hp->capacity = 0;hp->size = 0;
}

插入数据(包含向上调整详细讲解)

首先就是判断空间够不够,size如果等于capacity,那就表示已经满了,需要先扩容,这里是扩容,所以要用realloc,如果用malloc,那么之前的数据就丢失了,因为malloc的作用是重新开辟一块新的空间,而不是扩容,扩容是需要使原来的数据保存下来,并且有了新的空间区存放新的数据
接下来就是到了重要的时刻,向上调整AdjustUp,我用小堆来讲解
因为我们要在插入数据的同时要保证数组中的数据存储顺序与逻辑模型中的堆保持一致,新的数据是插在数组末尾的,现在整体看来已经不满足小堆了
在这里插入图片描述

  • 每插入一个数据时,只与自己的父亲比较,如果自己比父亲小,那就与父亲交换值,交换后,再与当前位置的值进行比较,直至比父亲大的时候或者已经到根节点的时候,停止比较
  • 为什么只与自己的父亲比较呢,因为当你插入时,此时的数据已经是个小堆了,那么你的到来只会影响你跟你父亲、你父亲的父亲…这些位置,所以每次跟这条线路的值比较就行
  • 无论这个孩子是左孩子还是右孩子,想要找到父亲的位置,只需parent = (child-1)/2,就可以找到父亲的下标
void HeapPush(Heap* hp, HDataType x)
{assert(hp);if (hp->size == hp->capacity){int new_capacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HDataType* p = (HDataType*)realloc(hp->arr, sizeof(HDataType) * new_capacity);if (p == NULL){perror("realloc fail");return;}hp->arr = p;hp->capacity = new_capacity;}hp->arr[hp->size] = x;hp->size++;AdjustUp(hp->arr, hp->size);//当前个数
}

向上调整

void AdjustUp(int* arr, int n)
{int child = n - 1;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;}}
}

删除堆顶元素(包含向下调整详细讲解)

  • 为什么是删除堆顶元素而不是删除堆的最后一个元素,毕竟插入的时候就是尾插
  • 因为堆顶的元素是整个数组中最小的元素,我们可以利用这一特性来寻找数组中前多少位小的数,就像奶茶店寻找评分最高的十家店,现在是小堆,那我们就利用小堆寻找评分最低的十家店。
  • 如果我们将现在的堆顶元素删除,那么现在没有了堆顶元素,谁来当堆顶元素呢,是像数组删除头元素,然后整体向前移一位吗?听起来好像有点意思,那就分析一下吧
    在这里插入图片描述
  • 左边是排好的小堆,右边是删除了20后,数组中每个数据向前移一位形成的“小堆”,此时,不难看出,这根本已经不是堆了,为什么?
  • 因为堆的性质,父亲节点要小于等于两个孩子,与兄弟节点的大小没有任何关系,现在你变成了第二张图,30,原本是25的兄弟,他俩的大小本身是没有关系的,但是经过刚才的操作,30变成了25的父亲,在小堆中,作为父亲,就要比孩子小,原本我们是兄弟没有大小关系,我可以比你大,可以比你小,现在变成了父子,就有了大小关系,自然就有可能就不符合小堆的条件,
  • 那么,还有没有挽回的余地呢,有的,将现在的数据重新排成小堆,但你要知道,删除一个堆顶元素,就要重新排一次全部数据,这样的代价是非常大的。有没有更好的方法,接下来我就会讲

向下调整

  • 想删除堆顶元素,那就与最后一个元素进行交换,然后size–,此时新的堆顶元素作为父亲与两个孩子中较小的一位比较大小,父亲比孩子大,那就交换,交换后的父亲继续与孩子比较,直至父亲比孩子小或者孩子的下标大于数组长度,到此停止,现在的数组存储数据的顺序,依旧是小堆。
  • 为什么是和两个孩子中较小的比较呢?假设一下吧,首先作为父亲的两个孩子,他俩的大小是没有直接关系的,谁都可以比对方大或小,假设一个孩子大一个孩子小,父亲要跟孩子比,分三种情况,
    1. 父亲最大,与大孩子比较,父亲大,所以与大孩子交换,交换后,大孩子在父亲的位置,那就要比两个孩子都小,但大孩子本身就比小孩子大,所以还要和小孩子交换,经历了两次交换。
    2. 父亲比大孩子小,比小孩子大,那么父亲需要和小孩子交换,交换后,小孩子比之前的父亲和大孩子都小,所以只交换一次。
    3. 父亲是最小的,因此不需要交换,但是父亲即使没有交换,也是比较了大小才确认的。

总结:
当父亲之和小孩子比较大小时,要么交换一次要么不交换;与大孩子比较时,交换两次,交换一次,和不交换;相比之下,如果只与小孩子比较就会节省交换两次的操作,因此父亲与小孩比较大小就行。

void HeapPop(Heap* hp)
{assert(hp);Swap(&hp->arr[0], &hp->arr[hp->size - 1]);hp->size--;AdjustDown(hp->arr, hp->size,0);//传的是当前的个数
}

向下调整

思路:

  • 先将末尾的值与堆顶元素交换,此时想要保持整个数组还是小堆,就让现在的交换后堆顶元素向下调整
  • 小堆的话,与两个孩子中较小的比较大小,如果比那个孩子小,那就交换,交换后重复这个过程,直至遇到较小的孩子都比自己大时或者自己的孩子的下标已经超出整个数组的大小,那就停止
void AdjustDown(int* arr, int n,int pos)
{int parent = pos;int child = parent * 2 + 1;while (child < n){if (((child + 1) < n) && (arr[child] > arr[child + 1])){child = child + 1;}if (arr[parent] > arr[child]){Swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}else{break;}
}

返回堆顶元素

HDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp));return hp->arr[0];
}

判断堆是否为空

bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}

销毁

void HeapDestory(Heap* hp)
{assert(hp);free(hp->arr);hp->arr = NULL;hp->capacity = 0;hp->size = 0;
}

三、建堆

意义:将乱序的数组调整成堆

向上调整建堆

虽然数组已经有了数据,但我们把建堆的过程看做插入数据,
在这里插入图片描述

向下调整建堆

  • 不论是向上调整还是向下调整,都有前提,除当前位置,剩余的位置已经是堆了,比如向上调整,这个数到来之前就已经是个堆了,只不过由于它的到来,更改了堆的结构,所以向上调整为新的堆
  • 那么向下调整也一样吗?首先想到的应该是从堆顶开始向下调整,关键是你是第一个元素,但其余的元素并不是堆,怎么办,要想向下调整,首先之前的结构要是堆。
  • 方法不太好想,我就来讲解一下,堆是完全二叉树,它的叶子结点实际也就是一个个堆,比如图中的65、10、70、15都是堆,那么我们倒着向下调整来组建堆,这四个数已经是堆了,所以从80开始
    在这里插入图片描述

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

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

相关文章

一个开源的快速准确地将 PDF 转换为 markdown工具

大家好&#xff0c;今天给大家分享的是一个开源的快速准确地将 PDF 转换为 markdown工具。 Marker是一款功能强大的PDF转换工具&#xff0c;它能够将PDF文件快速、准确地转换为Markdown格式。这款工具特别适合处理书籍和科学论文&#xff0c;支持所有语言的转换&#xff0c;并…

2024年最佳插电式混合动力电动汽车

对电动汽车充满好奇和环保意识的司机们还没有准备好跨入纯电动汽车&#xff0c;他们可以找到一个折衷方案&#xff0c;即插电式混合动力车。 在过去的16年里&#xff0c;我一直在把握汽车行业的脉搏。试驾数百辆汽车、电动汽车、插电式混合动力车&#xff0c;跟踪汽车行业的新闻…

DAY04 HTMLCSS

文章目录 一 表单(1) 数字控件(2) 颜色控件(3) 日期控件(4) 月份控件(5) 星期控件(6) 搜索控件(7) 范围控件 二 浮动框架三 结构化标签四 CSS1 CSS概述2 CSS的编写位置1. inline style 行内样式2. inner style 内部样式3. outer style 外部样式4. 小结 3 CSS选择器1. 通用选择器…

一个示例学习C语言到汇编层面

给出以下代码 #include<stdio.h> int main() {int x 0, y 0, z 0;while (1) {x 0;y 1;do {printf("%d\n", x);z x y;x y;y z;} while (x < 255);}return 0; }我们把这个程序编写成32位程序&#xff0c;然后我们放入IDA中进行分析 .text:0080187…

Mysql开启查询日志(General Log)

1、增加配置&#xff1a; /etc/my.cnf [mysqld] general_log1 general_log_file/var/log/mysql/query.log 2、增加目录和文件&#xff0c;并且授权 可以使用以下命令修改权限&#xff1a; 创建目录&#xff1a;sudo mkdir -p /var/log/mysql 更改目录所有者&#xff1a;sudo…

【日常记录】【插件】prisma 链接MySQL数据库 简单入门

文章目录 1、新建项目&#xff0c;使用prisma链接数据库1.1、先创建一个项目1.2、初始化 npm 配置文件及下载依赖1.3、初始化TS配置文件1.4、初始化 prisma1.5、更改 prisma/schema.prisma1.6 更改.env 文件1.7 编写 prisma/schema.prisma1.8 将编写的 prisma/schema.prisma 映…

OSPF被动接口配置(华为)

#交换设备 OSPF被动接口配置 一、基本概念 OSPF被动接口&#xff0c;也称为抑制接口&#xff0c;即将路由器某一接口配置为被动接口后&#xff0c;该接口不会再接受和发送OSPF报文 二、使用场景 在路由器与终端相近或者直接相连的一侧配置被动接口 因为OSPF会定期发送报文…

ensp防火墙web密码重置(前提通过console可以登录)

客户电脑是命令行没有用户名直接输入密码就可以登录了&#xff0c;但是web端不知道admin的密码 前两天遇到运维单位的一台防火墙web网页不知道用户名密码&#xff0c;默认的登录不了&#xff0c;但是通过console可以登录命令行&#xff0c;今天就记录下如何通过命令行修改web页…

海康视觉算法平台VisionMaster 4.3.0 C# 二次开发01 加载方案并获取结果

前言 第一次使用海康视觉算法平台VisionMaster 4.3.0&#xff0c;项目中要使用这个平台进行视觉处理并获取结果。 运行效果 开发环境 C#&#xff0c; WPF&#xff0c; vs2022, 海康视觉算法平台VisionMaster 4.3.0 基本概念 上图这些.sol为后缀的是vm的方案文件。 打开方案文…

鸿蒙开发过程中出现很多.js或者.js.map怎么办

学习HarmonyOS应用开发已有几天了, 今天在打开DevEco Studio正常开发的过程中, 预览、修改、刷新, 然后就出现了大量的.js以及.js.map文件 这个虽然不影响开发&#xff0c;但是影响体验 问题原因&#xff1a; 编译/预览过程产生的缓存文件, 已反馈给鸿蒙的IDE团队 解决办法…

树莓派pico入坑笔记,快捷键键盘制作

使用usb_hid功能制作快捷键小键盘&#xff0c;定义了6个键&#xff0c;分别是 ctrlz ctrlv ctrlc ctrla ctrlw ctrln 对应引脚 board.GP4, board.GP8, board.GP13 board.GP28, board.GP20, board.GP17 需要用到的库&#xff0c;记得复制进单片机存储里面 然后是main主程…

3dmax2025能用云渲染吗?2025最新云渲染渲染100使用方法

3dmax2025还没用上云渲染&#xff1f;简单3步用上云渲染。 第一步&#xff0c;打开浏览器搜索渲染100&#xff0c;并进入下载客户端并安装 第二步&#xff0c;打开已安装的客户端进行安装&#xff0c;点击登录&#xff0c;未登录注册个账号即可&#xff08;注册账号时邀请码填…

Sermant标签路由能力在同城双活场景的应用

作者&#xff1a;聂子雄 华为云高级软件工程师 摘要&#xff1a;目前应用上云已成为趋势&#xff0c;用户也对应用在云上的高可靠方案有更高追求&#xff0c;目前同城双活场景作为应用高可靠方案中的一种常见实践方案&#xff0c;对微服务流量提出了数据中心亲和性的要求&…

浙江电信联合中兴通讯取得新突破,完成融合边缘商用验证

前不久&#xff0c;浙江电信联合中兴通讯在融合边缘方面取得新突破&#xff0c;在嘉兴完成了融合边缘的商用验证&#xff0c;并发布了商用版本。接下来&#xff0c;双方在融合边缘方面正式进入商用阶段&#xff0c;有效赋能新质生产力。    随着数字经济的快速发展&#xff0…

数据结构与算法笔记:基础篇 -字符串匹配(下):如何借助BM算法轻松理解KMP算法?

概述 上篇文章讲了 BM 算法&#xff0c;尽管他复杂&#xff0c;也不好理解&#xff0c;但确实工程中非常好用的一种高效字符串匹配算法。有统计说&#xff0c;它是最搞笑、最常用的字符串匹配算法。不过&#xff0c;在所有的字符串匹配算法里&#xff0c;要说最知名的一种的话…

【C++】认识STL

【C】认识STL STL的概念STL的版本STL的六大组件STL的三个境界STL的缺陷 STL的概念 SLT(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个保罗数据结构与算法的软件框架。 STL的版本 原…

便携式手持气象仪:低功耗设计

TH-LSZ05便携式手持气象仪是一款轻便、操作简便的气象监测工具&#xff0c;集成了风向、风速、大气压、温度、湿度五项气象要素的测量功能。这些设备通常设计为体积小、重量轻&#xff0c;以便于用户随时携带并使用。通过使用手持气象仪&#xff0c;用户可以实时获取关键的气象…

列表(list)(Python)

文章目录 一、定义二、列表常用操作 一、定义 list ["张三", "李四", "王五", "赵六"]二、列表常用操作 分类关键字/函数/方法说明增加列表.append(值)在列表末尾追加值列表.insert(索引&#xff0c; 值)在指定位置插入值&#xff…

安徽保安员精选模拟试题(含答案)

1、风险管理的三要素是()&#xff0c;风险评价和风险控制。 A、频率分析 B、风险分析 C、风险转移 D、后果估计 答案:B 2、治安保卫重要部位是指由()确定的、关系本单位生产业务全局的部位和生产环节。 A、企事业重点单位 B、地方政府 C、企事业单位保卫协会 D、公安机关 …

智能室内空气质量监测预警系统小程序设计说明书

智能室内空气质量监测预警系统小程序设计说明书 一、应用功能与系统设计 &#xff08;一&#xff09; 应用功能 该小程序设计的目的是为了配合环境监测吸顶灯,Mini空气监测仪等硬件设备实时数据展示与远程设备控制等功能&#xff0c;系统框架图如图1-1所示。用户可以从小程序…