当前位置: 首页 > news >正文

【附C源码】从零实现C语言堆数据结构:原理、实现与应用

【附C源码】从零实现C语言堆数据结构:原理、实现与应用

堆(Heap)作为优先级队列的经典实现,在任务调度、图算法(如Dijkstra、Prim)、Top-K问题等场景中有着广泛应用。本文将深入探讨堆的核心原理,并基于C语言标准库实现一个完整的、可用于生产环境的最小堆与最大堆。

一、堆的本质与性质

堆是一种特殊的完全二叉树,其核心特性在于父节点与子节点之间的序关系:

  • 最小堆:任意节点的值不大于其子节点的值,堆顶为全局最小值
  • 最大堆:任意节点的值不小于其子节点的值,堆顶为全局最大值

由于完全二叉树的结构特性,堆可以高效地映射到数组中存储,避免了指针带来的额外内存开销。

数组映射关系

对于索引为iii的节点:

  • 父节点索引:parent(i)=⌊(i−1)/2⌋parent(i) = \lfloor (i-1)/2 \rfloorparent(i)=⌊(i1)/2
  • 左子节点索引:left(i)=2i+1left(i) = 2i + 1left(i)=2i+1
  • 右子节点索引:right(i)=2i+2right(i) = 2i + 2right(i)=2i+2

数组存储

索引: 0, 1, 2, 3, 4, 5, 6

值: 10, 20, 15, 30, 25, 18, 22

堆的树形结构

10

20

15

30

25

18

22

二、核心操作的时间复杂度分析

操作时间复杂度说明
插入元素O(log⁡n)O(\log n)O(logn)从叶节点向上调整
删除堆顶O(log⁡n)O(\log n)O(logn)将尾元素移至堆顶后向下调整
查看堆顶O(1)O(1)O(1)直接访问数组首元素
Floyd建堆O(n)O(n)O(n)自底向上批量建堆,优于逐元素插入

Floyd建堆的线性复杂度是一个常被忽视但极为重要的优化点。对于nnn个元素的建堆过程,时间复杂度可推导为:

T(n)=∑h=0⌊log⁡n⌋⌈n2h+1⌉⋅O(h)=O(n) T(n) = \sum_{h=0}^{\lfloor \log n \rfloor} \lceil \frac{n}{2^{h+1}} \rceil \cdot O(h) = O(n)T(n)=h=0logn2h+1nO(h)=O(n)

三、数据结构定义

typedefenum{MIN_HEAP,// 父节点 <= 子节点MAX_HEAP// 父节点 >= 子节点}HeapType;typedefstruct{int*data;// 动态数组intsize;// 当前元素数intcapacity;// 数组容量HeapType type;// 堆类型标识}Heap;

采用函数指针或宏定义实现比较逻辑的抽象,可以在编译期确定堆类型,避免运行时的分支判断开销。

四、关键算法实现

4.1 向上调整(Heapify Up)

插入操作将新元素置于数组末尾,随后沿父节点方向逐级比较、交换,直至满足堆性质。

新元素插入末尾

当前节点优先级高于父节点?

交换两节点

索引移至父节点

调整完成

staticvoidheapifyUp(Heap*h,intindex){while(index>0){intparent=PARENT(index);if(hasHigherPriority(h,h->data[index],h->data[parent])){SWAP(h->data[index],h->data[parent]);index=parent;}else{break;}}}

4.2 向下调整(Heapify Down)

删除堆顶时,将末尾元素移至堆顶后,从根节点开始与子节点比较,与优先级较高的子节点交换,直至叶子节点。

staticvoidheapifyDown(Heap*h,intindex){while(true){intleft=LEFT(index);intright=RIGHT(index);intpriority=index;if(left<h->size&&hasHigherPriority(h,h->data[left],h->data[priority]))priority=left;if(right<h->size&&hasHigherPriority(h,h->data[right],h->data[priority]))priority=right;if(priority==index)break;SWAP(h->data[index],h->data[priority]);index=priority;}}

4.3 Floyd建堆算法

与逐个插入的O(nlog⁡n)O(n \log n)O(nlogn)复杂度相比,Floyd算法通过自底向上的批量调整,将建堆复杂度优化至线性。

Heap*heapBuild(int*arr,intn,HeapType type){Heap*h=heapCreate(n>4?n:4,type);memcpy(h->data,arr,sizeof(int)*n);h->size=n;// 从最后一个非叶子节点开始向下调整for(inti=PARENT(n-1);i>=0;i--){heapifyDown(h,i);}returnh;}

五、堆排序的实现

堆排序是一种原地、不稳定排序算法,核心思想是:

  1. 将待排序数组构建为最大堆(升序)或最小堆(降序)
  2. 反复将堆顶元素与末尾元素交换,堆规模减一
  3. 对新的堆顶执行向下调整
  4. 重复步骤2-3直至堆为空
voidheapSort(int*arr,intn,bool ascending){HeapType type=ascending?MAX_HEAP:MIN_HEAP;Heap*h=heapBuild(arr,n,type);for(inti=n-1;i>=0;i--){heapPop(h,&arr[i]);}heapDestroy(h);}

堆排序的时间复杂度恒为O(nlog⁡n)O(n \log n)O(nlogn),空间复杂度为O(1)O(1)O(1)(若采用原地建堆优化)。

六、工程实践要点

6.1 动态扩容策略

当容量不足时,采用倍增策略扩容,均摊插入复杂度仍为O(1)O(1)O(1)

staticboolheapExpand(Heap*h){intnewCapacity=h->capacity*2;int*newData=realloc(h->data,sizeof(int)*newCapacity);if(!newData)returnfalse;h->data=newData;h->capacity=newCapacity;returntrue;}

6.2 删除任意位置元素

删除非堆顶元素时,用末尾元素填充被删位置,随后同时执行向上和向下调整,确保堆性质恢复。

boolheapRemoveAt(Heap*h,intindex,int*outValue){*outValue=h->data[index];h->data[index]=h->data[--h->size];// 双方向调整,仅需其中一个会生效heapifyUp(h,index);heapifyDown(h,index);returntrue;}

七、应用场景与扩展

7.1 优先级队列

堆是实现优先级队列的理想数据结构,支持高效地获取最高优先级元素。

7.2 Top-K问题

维护一个大小为K的最小堆,遍历数据流时,当新元素大于堆顶则替换,最终堆中即为最大的K个元素。

7.3 图算法优化

Dijkstra最短路径算法和Prim最小生成树算法中,使用堆优化可将时间复杂度从O(V2)O(V^2)O(V2)降至O((V+E)log⁡V)O((V+E)\log V)O((V+E)logV)

八、总结

本文实现的堆数据结构具备以下特点:

  • 支持最小堆与最大堆双模式
  • 采用动态数组实现,自动扩容
  • Floyd建堆算法保证线性建堆复杂度
  • 完整的堆排序功能
  • 零第三方依赖,仅使用C标准库

完整代码已开源,可作为学习数据结构的参考实现,也可根据实际需求扩展为泛型版本(通过void*与比较函数指针)。


⚠️源码地址:https://github.com/anjuxi/C-heap

http://www.jsqmd.com/news/829022/

相关文章:

  • 模型广场功能如何帮助开发者快速选型与切换测试
  • 如何轻松实现专业级音频处理:5个AI场景完全指南
  • 解密Outfit字体:9种字重几何无衬线字体的实战秘籍
  • ShawzinBot终极指南:如何在Warframe中实现MIDI自动演奏
  • 小米手表表盘设计终极指南:用Mi-Create打造个性化表盘
  • ElevenLabs藏文语音生成上线仅72小时:开发者必须立即掌握的5个API调用避坑要点
  • 简单三步掌握OBS虚拟摄像头:让专业直播画面进入任何视频会议
  • 高性能Excel处理方案:解决大数据导入导出的痛点
  • React useWebSocket 社区贡献指南:如何参与开源项目开发
  • RISC-V开发踩坑实录:从编译错误‘csrr a5,mhartid’到GDB报错‘E14’的完整排错指南
  • 同向运算放大器实战指南:从理想模型到PCB布局的完整设计
  • B站缓存视频拯救指南:如何用m4s-converter快速解锁被封存的数字记忆
  • 通过Taotoken控制台审计日志追踪API Key使用情况与安全
  • 10分钟掌握终极笔记备份:evernote-backup工具完全指南
  • D2RML:暗黑破坏神2重制版终极多开指南 - 告别繁琐登录,实现一键多开
  • Verilog行为级描述:从语法到硬件映射的工程实践指南
  • Hermit-rs安全机制解析:Rust所有权模型如何保障unikernel安全
  • 通过curl命令直接测试Taotoken聊天补全接口的简易方法
  • 技能管理框架skill-mix:用YAML与声明式配置构建可量化技能体系
  • WarcraftHelper终极指南:3步解锁魔兽争霸3全部潜能
  • 窗口尺寸革命:如何用WindowResizer打破Windows应用程序的尺寸枷锁
  • 别再到处找安装包了!Windows系统下FreeCAD 0.18.4保姆级安装与汉化教程
  • WIN11下NFS21闪退终结指南:从黑屏到流畅狂飙的实战修复
  • ChanlunX缠论插件:5分钟实现通达信专业缠论分析的完整指南
  • MySQL 8.4 LTS版本模型解析与生产环境升级实战指南
  • Spectator:云原生可观测性数据采集库的设计与实战
  • TestableMock在Android项目中的应用:完整配置与最佳实践
  • openEuler aarch64 环境下 cephadm 离线部署 Ceph Reef:私有镜像仓库构建与全栈容器镜像预置指南
  • 告别OpenMV?Canmv K210+MaixHub在线训练,打造你的专属视觉识别方案
  • WinDirStat:3步快速上手Windows磁盘空间高效管理