【数据结构】堆的完整实现

堆的完整实现

  • 堆的完整实现
  • GitHub地址
  • 前言
  • 堆的核心功能实现
    • 重温堆的定义
    • 堆结构定义
    • 1. 堆初始化与销毁
    • 2. 元素交换函数
    • 3. 堆化操作
      • 向上调整(子→父)
      • 向下调整(父→子)
    • 4. 堆元素插入
    • 5. 堆元素删除
    • 6. 辅助功能函数
      • 堆的判空
      • 获取堆顶元素
      • 获取堆的大小
  • 结语

堆的完整实现

GitHub地址

有梦想的电信狗

前言

堆(Heap)是一种特殊的完全二叉树数据结构,常用于实现优先级队列。本文基于C语言实现大跟堆,包含核心操作:插入元素、删除堆顶元素、堆化操作等。以下是完整实现及详细解析。


堆的核心功能实现

重温堆的定义

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。

现实中我们通常把堆(一种特殊的二叉树)使用顺序结构的数组来存储。

在这里插入图片描述

需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

在这里插入图片描述

二叉树的顺序存储,是堆的前身

在顺序存储的二叉树上加一些限定条件,定义为堆

  • 小根堆 : 树中所有父结点都小于或等于子节点
  • 大根堆 树中所有父结点都大于或等于子节点
  • 堆可以用来排序,但堆并非是有序的!

堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

在这里插入图片描述

堆结构定义

//实现大堆   这里以实现大根堆为例子
typedef int HeapDataType;	//定义堆中存放的数据类型,方便修改
typedef struct HeapNode {HeapDataType* base;   // 堆存储数组基地址int size;              // 当前元素个数int capacity;          // 堆容量
}Heap;

结构说明

  • base:动态数组基地址,用于存储堆元素,用数组来顺序存储
  • size:当前堆中元素数量
  • capacity:数组总容量

数组存储的二叉树的下标特性

  • parent = (child - 1) / 2
  • lefichild = parent * 2 + 1
  • lefichild = parent * 2 + 2

功能一览

//堆初始化与销毁
void HeapInit(Heap* pheap);
void HeapDestroy(Heap* pheap);//堆插入和删除
void HeapPush(Heap* pheap, HeapDataType data);
void HeapPop(Heap* pheap);
//堆的向上 向下调整
void AdjustUp(HeapDataType* arr, int child);
void AdjustDown(HeapDataType* arr, int size, int parent);//交换堆中的元素
void Swap(HeapDataType* left, HeapDataType* right);
//获取堆顶元素
HeapDataType HeapTop(Heap* pheap);
//判断堆是否为空
bool HeapEmpty(Heap* pheap);
//获取堆的size
int HeapSize(Heap* pheap);

1. 堆初始化与销毁

初始化

//堆初始化
void HeapInit(Heap* pheap) {assert(pheap);	pheap->base = (HeapDataType*)malloc(sizeof(HeapDataType) * 4);if (pheap->base == NULL) {perror("malloc failed\n");return;}pheap->size = 0;pheap->capacity = 4;
}

实现思路

  • 断言指针有效性,堆结构指针必须存在
  • 为堆分配初始容量(暂设置为4个元素空间),申请空间失败时报错并返回
  • 初始化size0表示空堆,capacity初始化为申请的空间
  • 时间复杂度:O(1)

注意事项

  • 必须进行指针有效性断言检查
  • 初始容量不宜过小(建议为4的倍数)

销毁

//清理资源
void HeapDestroy(Heap* pheap) {assert(pheap);free(pheap->base);pheap->base = NULL;pheap->capacity = pheap->size = 0;
}

实现思路

  • assert断言堆结构指针不为空
  • free释放动态分配的存储空间
  • 将指针置NULL防止野指针
  • sizecapacity都置为0
  • 时间复杂度:O(1)

2. 元素交换函数

void Swap(HeapDataType* child, HeapDataType* parent) {HeapDataType temp = *child;*child = *parent;*parent = temp;
}

功能:交换父子节点数值
使用场景:堆化(向上调整/向下调整)时的元素位置调整

交换函数功能较为简单,此处不过多赘述。


3. 堆化操作

向上调整 或向下调整的条件是,左右子树 必须是 大堆 或者 小堆

向上调整(子→父)

在这里插入图片描述

// 插入数据向上调整, 删除数据向下调整
//向上调整 或向下调整的条件是,左右子树 必须是大堆 或者 小堆
void AdjustUp(HeapDataType* arr, int child) {	//child是需要调整的节点的下标assert(arr);int parent = (child - 1) / 2;//while (parent >= 0) {		//	 个人建议while的循环条件内不要写太复杂的条件//写成  child > 0  会更好  因为 最坏时 child 为 0 ,此时parent = (child-1)/2  也为0//因此 实际上 parent 不会为 <= 0while (child > 0) {   // child 等于 0 或小于 0 时就不用再调整了if (arr[child] > arr[parent]) {Swap(&arr[child], &arr[parent]);child = parent;parent = (child - 1) / 2;}//child <= parent 时else	break;}
}

功能:将新插入堆的元素调整到合适位置 ,使其满足堆的性质

  • child是新插入元素的下标,一般是size-1(即通过尾插进入的最后一个元素的下标)
  • arr是待调整的数组指针,断言arr非空
  • child 为子节点,向上调整思路
    • 通过parent = (child - 1) / 2,计算待调整结点的父节点的下标
    • child下标为0时,代表最后一次交换(调整)已结束。因此循环结束条件为child == 0
    • 比较父子结点的大小,子节点大于父节点时,交换,满足大根堆的逻辑,同时下标childparent进行更新
    • 当前child结点 < parent结点时,代表以满足大根堆,直接结束循环即可。
  • 时间复杂度为 O(logN)

终止条件

  • 以下条件满足其一,循环即可终止。
    • 子节点值 父节点值
    • 到达堆顶(child=0

向下调整(父→子)

在这里插入图片描述

// 向下调整 到叶子结点结束,叶子结点的左孩子 的下标  大于 size   size是数组的大小
void AdjustDown(HeapDataType* arr, int size, int parent) {assert(arr);assert(parent >= 0 && parent < size);	//parent非负 且 不能越界int child = parent * 2 + 1;while (child < size) {//检查 child+1 是否越界 以及 找出左右孩子中更大的那个//child + 1 >= size 时,表示当前父节点只有左孩子if (child + 1 < size && arr[child + 1] > arr[child])++child;if (arr[child] > arr[parent]) {Swap(&arr[parent], &arr[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}

实现思路

  • 断言arrparent >= 0 && parent < size,保证arr存在,parent非负且不越界
    • parent一般是0从堆顶元素开始调整,尽可能地保持了堆的结构。
  • child = parent * 2 + 1:计算左孩子的下标,右孩子的下标是左孩子下标+1
  • child >= size时,代表最后一个元素已完成交换,循环结束
  • 检查child+1是否越界以及 找出左右孩子中更大的那个,让较大者与parent结点进行比较。
  • child更大时,与parent结点进行交换,childparent接着移动。

实现要点

  1. 总是与较大的子节点比较
  2. 循环终止条件(满足其一即终止):
    • parent数据 ≥ 两个child节点数据,此时已符合大根堆的条件,向下调整完成
    • child > size时,所有的节点已完成交换,此时已符合大根堆的条件,向下调整完成

时间复杂度为 O(logN)

小根堆的调整:可与大根堆类比
在这里插入图片描述


4. 堆元素插入

  • 向上调整的过程

在这里插入图片描述

// 插入,不能指定位置插入。
// 因为新元素插入后要进行调整使其满足堆的结构,指定的位置不一定是最终调整后的位置
void HeapPush(Heap* pheap, HeapDataType data) {assert(pheap);	//空堆也可以push,但需保证结构体存在//插入检查是否需要扩容if (pheap->size == pheap->capacity) {HeapDataType* newSpace = (HeapDataType*)realloc(pheap->base, sizeof(HeapDataType) * pheap->capacity * 2);if (newSpace == NULL) {perror("realloc failed\n");return;}pheap->base = newSpace;pheap->capacity *= 2;}//更新pheap->base[pheap->size] = data;pheap->size++;//插入后需向上调整,保证插入后满足堆的特性AdjustUp(pheap->base, pheap->size - 1);	//size++ 后,size-1 是新插入元素的下标
}

实现思路

  1. 断言堆存在检查并扩容(2倍扩容策略)
    • 扩容:
      • 开空间,二倍扩容
      • 判断是否开辟成功
      • 更改指针和容积
  2. 将新元素插入数组末尾,更新size尽可能的保持原来堆的结构
  3. 执行向上调整操作维护堆结构

时间复杂度

  • 最优:O(logN)(不需要扩容)
  • 最差:O(N)(触发扩容)

5. 堆元素删除

  • 向下调整的过程

在这里插入图片描述

//堆的删除  应当删除堆顶的元素,删除堆尾的数据没有意义。
//删除最大的或最小的,可以选出第二大或第二小的
//挪动删除(直接删)的缺点:  1. 效率低下O(n)   2.  堆的父子关系全乱了
// 删堆顶的元素,将第一个元素和最后一个元素交换(最大限度的保持了原有的关系),再向下调整维持堆的大小关系
void HeapPop(Heap* pheap) {assert(pheap && pheap->size > 0);assert(!HeapEmpty(pheap));//删除堆顶元素   交换堆顶元素 和 堆尾元素Swap(&pheap->base[0], &pheap->base[pheap->size - 1]);pheap->size--;		//删除数据,让size-1   size--之后,可能会为0// 仅当堆非空时进行向下调整if (pheap->size > 0) {AdjustDown(pheap->base, pheap->size, 0);}
}

实现思路

  • 断言堆存在并且确保堆为非空。
  • 通过交换堆顶元素和堆尾元素,并更改size--来实现数组内元素的删除
  • 通过size--的方式删除元素,向下调整时,要确保size的值不为0

注意事项

  • 删除前必须检查堆是否为空
  • size减至0时无需向下调整操作

6. 辅助功能函数

堆的判空

//判断堆是否为空
bool HeapEmpty(Heap* pheap) {assert(pheap);return pheap->size == 0;
}

获取堆顶元素

//获取堆顶元素
HeapDataType HeapTop(Heap* pheap) {assert(pheap);assert(pheap->base);return pheap->base[0];
}
  • 0号元素就是堆顶元素

获取堆的大小

//获取堆的size
int HeapSize(Heap* pheap) {assert(pheap);return pheap->size;
}

功能说明

  • HeapTop:获取堆顶元素(极值)
    • 大根堆时是极大值
    • 小跟堆时是极小值
  • HeapEmpty:判断堆是否为空
  • HeapSize:获取当前元素个数

结语

本文完整实现了基于数组存储的大根堆结构,重点阐释了堆化过程中向上调整与向下调整的核心逻辑。通过动态数组管理、二倍扩容策略及父子节点下标计算,构建了插入元素时末尾上浮、删除堆顶时首尾交换后根节点下沉的高效操作,确保堆性质在O(logN)时间内得以维护。关键点在于理解完全二叉树顺序存储的特性,以及插入/删除时通过逐层比较交换维护父节点≥子节点的规则。实际应用中可调整比较逻辑切换大小堆,适用于优先队列、堆排序等场景,注意边界处理避免空堆删除和扩容失败问题。

分享到此结束啦
一键三连,好运连连!

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

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

相关文章

如何优化MySQL主从复制的性能?

优化MySQL主从复制的性能需要从硬件、配置、架构设计和运维策略等多方面入手。以下是详细的优化方案&#xff1a; 一、减少主库写入压力 1. ‌主库优化‌ 二进制日志&#xff08;binlog&#xff09;优化‌&#xff1a; 使用 binlog_formatROW 以获得更高效的复制和更少的数…

MySQL安装完全指南:从零开始到配置优化(附避坑指南)

&#x1f525; 前言&#xff1a;为什么你总是装不好MySQL&#xff1f; &#xff08;实话实说&#xff09;每次看到新手在MySQL安装环节疯狂踩坑&#xff0c;老司机都忍不住想摔键盘&#xff01;明明官网下载的安装包&#xff0c;怎么就会报错呢&#xff1f;为什么别人的环境变…

密码学_加密

目录 密码学 01 密码基础进制与计量 02 加解密基操 替换 移位 编码 编码 置换 移位 加解密强度 03 对称加密算法(私钥) 工作过程 缺陷 对称加密算法列举&#xff1f; DES DES算法架构 DES分组加密公式 DES中ECB-CBC两种加密方式 3DES 由于DES密钥太短&#xf…

轻量级RTSP服务模块:跨平台低延迟嵌入即用的流媒体引擎

在音视频流媒体系统中&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09;服务模块通常扮演着“视频分发中心”的角色&#xff0c;它将编码后的音视频内容转为标准的流媒体格式&#xff0c;供客户端&#xff08;播放器、云端平台、AI模块等&#xff09;拉流…

Nginx发布Vue(ElementPlus),与.NETCore对接(腾讯云)

案例资料链接&#xff1a;https://download.csdn.net/download/ly1h1/90745660 1.逻辑说明 1.1 逻辑示意图 # 前端请求处理逻辑图浏览器请求流程: 1. 浏览器发起请求├─ 开发环境(DEV)│ ├─ 请求URL: http://192.168.0.102:3000/api/xxx│ └─ 被Vite代理处理└─ 生产…

解析机器人 2.0.2 | 支持超过50种短视频平台的链接解析,无水印提取,多功能下载工具

解析机器人是一款功能强大的工具软件&#xff0c;登录即可解锁会员特权。它支持超过50种短视频平台的链接解析&#xff0c;包括抖音、快手、西瓜、bilibili等&#xff0c;并能实现无水印提取。此外&#xff0c;还提供P2P下载、磁力链等多种下载方式&#xff0c;确保用户能够快速…

C++ - 数据容器之 forward_list(创建与初始化、元素访问、容量判断、元素遍历、添加元素、删除元素)

一、创建与初始化 引入 <forward_list> 并使用 std 命名空间 #include <forward_list>using namespace std;创建一个空 forward_list forward_list<int> fl;创建一个包含 5 个元素&#xff0c;每个元素初始化为 0 的 forward_list forward_list<int&g…

Python爬虫实战:获取企信网指定公司基本工商数据并分析,为客户选择公司做参考

一、引言 在商业决策、市场调研等众多领域,企业的基本工商信息是至关重要的参考依据。企信网作为权威的企业信息查询平台,汇聚了海量企业的详细信息。借助 Python 的爬虫技术,能够自动从企信网获取指定公司的工商信息,再运用数据分析和机器学习方法对这些信息进行深入挖掘…

STM32部分:2-1、STM32CubeMX介绍

飞书文档https://x509p6c8to.feishu.cn/wiki/BTv4wW3O7ita1dkQGkrcBb9rnXg 资料手册 英文手册 https://www.stmcu.com.cn/Designresource/detail/user_manual/711316 中文手册 https://www.stmcu.com.cn/Designresource/detail/localization_document/710583 界面说明 首…

SVM实战:从理论到鸢尾花数据集的分类可视化

SVM实战&#xff1a;从理论到鸢尾花数据集的分类可视化 在机器学习的广阔领域中&#xff0c;支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;作为一种经典且强大的分类算法&#xff0c;备受瞩目。它凭借独特的思想和卓越的性能&#xff0c;在模式识…

陶瓷陶器缺陷检测VOC+YOLO格式938张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;938 标注数量(xml文件个数)&#xff1a;938 标注数量(txt文件个数)&#xff1a;938 标注…

通过Docker部署Prometheus + Grafana搭建监控平台【超详细版】

文章目录 前言一、Prometheus、Grafana1.1 Prometheus简介1.2 Grafana简介1.3 Prometheus的核心组件1.4 Prometheus优点1.5 Prometheus缺点 二、部署Docker三、主节点部署PrometheusGrafana3.1 部署Prometheus3.2 防火墙开放端口3.3 访问服务3.4 安装Grafana3.5 防火墙开放端口…

华为云Flexus+DeepSeek征文|DeepSeek-V3商用服务开通教程

目录 DeepSeek-V3/R1商用服务开通使用感受 DeepSeek-V3/R1商用服务开通 1、首先需要访问ModelArts Studio_MaaS_大模型即服务_华为云 2、在网站右上角登陆自己的华为云账号&#xff0c;如果没有华为云账号的话&#xff0c;则需要自己先注册一个。 3、接着点击ModelArts Stu…

ubuntu20.04修改默认网卡名称为eth*

在Ubuntu 20.04.6中&#xff0c;遵循可预测网络接口设备命名规则&#xff0c;网卡名称默认可能是以"enp*"、"ens*"等开头的格式&#xff0c;但是实际使用过程中&#xff0c;某些应用只能读取eth*的网卡&#xff0c;需要修改。 查看网卡名称 ip link show …

linux下抓包工具--tcpdump介绍

文章目录 1. 前言2. 命令介绍3. 常见选项3.1. 接口与基本控制3.2 输出控制3.3 文件操作3.4 高级调试 4. 过滤表达式4.1 协议类型4.2 方向与地址4.3 逻辑运算符 5. 典型使用场景5.1 网络故障排查5.2 安全分析与入侵检测5.3 性能分析与优化 linux下抓包工具--tcpdump介绍 1. 前言…

AI大模型-RAG到底能做些什么?

RAG常见的应用场景&#xff0c;有以下几个方面&#xff1a; 1.智能客服系统&#xff1a;比如电商领域&#xff0c;对客户提出的常见问题&#xff0c;进行自动回复。减少人力成本。 2.人力资源管理&#xff1a;一个新的员工&#xff0c;入职一家大型公司&#xff0c;公司中有各…

C++ unordered_set unordered_map

上篇文章我们讲解了哈希表的实现&#xff0c;这节尝试使用哈希表来封装unordered_set/map 1. unordered_set/map的框架 封装的过程实际上与set/map类似&#xff0c;在unordered_set/map层传递一个仿函数&#xff0c;用于取出key值 由于我们平常使用的都是unordered_set/map&…

REST API、FastAPI与Flask API的对比分析

以下是关于REST API、FastAPI与Flask API的对比分析&#xff0c;涵盖架构设计、性能表现、开发效率等核心维度&#xff1a; 一、核心定位与架构差异 REST API 本质&#xff1a;一种基于HTTP协议的架构风格&#xff0c;强调资源化操作&#xff08;通过URI定位资源&#xff09;、…

实战交易策略 篇二十二:情绪流龙头交易策略

文章目录 系列文章理论基础股市的本质资金与情绪题材龙头股龙头战法实战技法情绪流技术分析择时实操情绪流龙头战法要诀六大步骤九大术法买卖点量化标准系列文章 实战交易策略 篇一:奥利弗瓦莱士短线交易策略 实战交易策略 篇二:杰西利弗莫尔股票大作手操盘术策略 实战交易策…

用VNA进行天线阻抗匹配的实例大图

比如我这天线&#xff0c;在7Mhz时不谐振&#xff0c;我进行匹配 天线的阻抗很高&#xff0c;大约是在500-1400欧&#xff0c;而等效电容电感很小。 所以我考虑使用阻抗变压器降低阻抗。 1。测试天线阻抗&#xff0c;电阻相当高&#xff0c;等效电容很小。 2。通过磁环匹配到…