数据结构学习笔记:堆(大顶堆 / 小顶堆)入门复盘与认知迭代

发布时间:2026/7/6 1:01:15
数据结构学习笔记:堆(大顶堆 / 小顶堆)入门复盘与认知迭代 数据结构学习笔记堆大顶堆 / 小顶堆入门复盘与认知迭代写完栈和队列的学习笔记后我先回头修正了队列入门时的一个认知偏差最开始觉得链表实现队列更直观高效深入理解循环队列后才明白工程里反而更常用数组实现的循环队列。链表队列内存分散、频繁申请释放的开销在实际工程里往往不可接受数组内存连续、访问速度快的优势才更贴合生产环境的性能需求链表队列只适合数据量极不确定、操作不频繁的小众场景。带着这种 “认知会随着理解深入不断迭代” 的感受我继续往下学了堆。趁热把这段学习过程、思考和踩坑记录下来算是对树形结构入门的一次阶段性整理。一、初识堆从二叉树体系里生长出来的结构我学习堆的路径很顺先认识普通二叉树再细化到满二叉树、完全二叉树最后过渡到堆。 具象理解上我会把堆想象成一棵 “倒挂的树”它的结构规则很清晰除了最后一层上面所有层都必须填满最后一层的节点只能靠左排布这也是它属于完全二叉树的原因。 区分大顶堆和小顶堆并不难顾名思义就够“大” 和 “小” 指的就是根节点在整个堆里的数值地位 —— 大顶堆的根是全局最大值小顶堆的根是全局最小值。最开始我对堆的预期是它能做完整的排序。学下去才发现堆并不追求整棵树的严格有序它只保证堆顶是最值兄弟节点之间没有大小约束。它的核心定位从来不是 “排序工具”而是 “优先级调度工具”—— 把优先级最高的元素永远放在最顶端随用随取。堆排序只是它特性的延伸应用而非设计的初衷。二、实现选型为什么树形结构反而用数组实现堆明明是树形结构但几乎所有实现都用数组而不是链式二叉树。对此我自己的理解是 堆的形态高度规整是严格的完全二叉树结构变化只发生在最后一层刚好完美适配数组连续存储的特性。数组内存连续、访问速度快还能通过下标公式直接计算父子节点位置不需要额外的指针开销。 反过来普通二叉树形态随机、结构灵活链表的不连续、可动态增减节点的特性反而更适配它的不确定性。一句话总结结构固定选数组形态灵活选链表。至于父子节点的下标计算公式孩子节点找父节点是(i-1)/2父节点找左右孩子可以通过公式反推。我的记忆方法没有什么技巧就是多写代码、随手找两个下标代入验算练得多了自然就记住了本质还是靠积累。三、核心操作上浮、下沉与建堆的效率选择堆的所有操作都围绕两个核心动作展开上浮和下沉。上浮天然适合入堆场景新元素加到末尾向上调整到合适位置。下沉适合删除堆顶、建堆场景把节点向下调整到合适位置。我自己更偏爱下沉操作核心原因是效率更高。直观上就能想通越往树的下层节点数量越多但下沉操作的层数越少反过来如果用上浮建堆越靠下的节点需要上浮的层数越多总开销自然更大。 也正因为如此建堆的两种方式里我更推荐从最后一个非叶子节点开始、逐个下沉的方案它的时间复杂度是 O (n)而逐个插入、逐个上浮的建堆方式时间复杂度是 O (nlogn)。数据量越大两者的效率差距越明显。还有一个很有意思的点大顶堆和小顶堆的代码复用度极高。 二者的核心差异只存在于上浮、下沉操作里的比较符号 —— 大于号换成小于号或者小于号换成大于号整个堆的性质就切换了调整逻辑、终止条件、下标计算完全通用。四、踩坑与边界细节里的易错点手写堆的过程里边界和细节问题比栈、队列要多一点有几个地方特别容易踩坑终止条件写错不管是上浮还是下沉循环终止的边界很容易差一位。我的验证方法是直接假设循环到了最后一步代入边界值走一遍逻辑就能快速发现问题。大小比较方向搞反大顶堆和小顶堆的比较方向很容易写反遇到拿不准的时候举一组小数代入跑一遍流程基本就不会出错。漏判右孩子和栈、队列的线性结构不同堆每个父节点对应两个孩子。调整节点的时候不能只判断左孩子是否存在右孩子的合法性必须同步判断这是很容易遗漏的点。五、刷题感悟堆排序与 TopK 问题让我对堆的价值感受最深的还是两道经典题目堆排序和 TopK 问题。 先说堆排序。之前学冒泡排序是 O (n²) 的复杂度数据量一大效率下降非常明显而堆排序的时间复杂度是 O (nlogn)大数据量下优势极其突出。它的思路也很巧妙要得到升序序列就建大顶堆每次把堆顶的最大值交换到数组末尾再对剩余元素重新调整成堆循环往复就能完成排序。再就是 TopK 问题这个场景让我真正感受到了堆的实用价值。从海量数据里找前 K 大的元素不需要把所有数据都加载进内存只需要维护一个大小为 K 的小顶堆堆顶就是当前的 “淘汰门槛”—— 新来的数据比堆顶大就替换堆顶并调整堆。这样只用 K 个元素的内存空间就能完成海量数据的筛选极大降低了内存压力。 不过目前我对这道题还停留在 “思路清晰代码生疏” 的阶段因为涉及文件读取的 fopen、fscanf 这类函数练习得不多后续还要针对性补练。至于大顶堆和小顶堆的选型我目前总结出的判断方法是先明确堆顶的最值是用来做什么的。如果直接取堆顶当结果就正选 —— 要最大值用大顶堆要最小值用小顶堆如果堆顶是用来做淘汰门槛的比如 TopK就反选 —— 找前 K 大用小顶堆找前 K 小用大顶堆。六、深度思考堆的核心价值学到这里也就明白了为什么实现优先级队列首选堆而不是有序数组、链表或者二叉搜索树。 堆做到了一个很好的平衡O (1) 时间获取最值O (logn) 时间完成插入和删除实现复杂度不高又完全适配 “动态维护优先级、随时取最高优先级” 的核心需求。它就是为优先级调度场景量身设计的结构。学完堆再回头看栈和队列对 “数据结构是为问题场景服务的” 这句话理解又深了一层。 没有万能的数据结构每一种结构都有它最贴合的问题语义栈对应嵌套、回溯、撤销队列对应顺序、排队、调度堆对应优先级、最值筛选。不是功能越全越好选最匹配问题本质的结构才是最优解。七、给刚学堆的新手一条建议最后和栈、队列对应给新手提一条最核心的建议 不用一开始就死磕所有操作、背所有公式先抓住堆最本质的核心 ——堆顶永远是当前优先级最高的元素。 抓住这个核心点再去理解上浮下沉、建堆排序、选型场景所有知识点都会顺着这个核心慢慢渗透开来学起来会顺畅很多。