Skip to content

二叉堆

Binary heap

以近完全二叉树表示并满足父子堆序的优先队列结构。

条目类型
模型

形式陈述

二叉最小堆是同时满足两条不变量的结构。形状不变量:它是完全二叉树,除最末层外各层填满,最末层从左连续填充。堆序不变量:每个结点的键不大于其孩子的键——这只约束每条父子边,给出的是位置间的偏序而非全序,但沿根到结点的路径传递即知最小键必在根。完全形状使堆可用数组隐式存储:下标从 1 起时,结点 i 的父亲是 i/2,孩子是 2i2i+1,无须任何指针。读取最小值 O(1);插入把新键置于末尾后上浮(与父亲比较、逆序则交换),删除最小值把末元素移到根后下沉(与较小的孩子比较、逆序则交换),二者都只走一条根叶路径,为 O(logn)。把无序数组原地建堆时,Floyd 自底向上法对每个内部结点执行下沉,总时间

v 为内部结点O(height(v))h=1log2nn2h+1O(h)=O(nh0h2h)=O(n),

其中高度恰为 h 的结点数至多为 n/2h+1;有限求和再由收敛级数 h/2h 控制。叶结点无需下沉,故求和从内部结点的高度 1 开始。

直觉

若目标只是反复取出最小元,维护完整排序纯属浪费:排序确定所有元素对的相对次序,而我们每次只需要知道“谁最小”。堆的设计哲学是维护恰好够用的最少秩序——只约束父子、不约束兄弟,全局最小自动浮到顶端,其余次序悬而不决、按需再定。完全二叉树的形状则同时买到两件事:高度恰为 log2n,保证上浮与下沉的路径短;结点位置可由下标算术算出,省掉指针并获得连续内存的紧凑与局部性。

二叉堆上浮示意图
例子与边界

数组 [1,3,2,7,5,8] 是合法最小堆:逐对检查 1312373528 即可,而数组本身并未排序(3 排在 2 之前)。执行一次删除最小值:末元素 8 移到根得 [8,3,2,7,5]8 与较小的孩子 2 交换得 [2,3,8,7,5];此时 8 位于下标 3,孩子下标 6,7 超界,下沉结束,堆序恢复。

边界之一:堆不是搜索结构。兄弟子树之间没有任何次序关系,查找给定键最坏要看 O(n) 个结点;需要“定位并修改优先级”时得额外维护键到下标的映射。之二:两种建堆方式的复杂度确有差别——反复插入是最坏 Θ(nlogn)(按递减序插入时每个新键都上浮到根),Floyd 法是 O(n);对后者不能按“n 个结点各下沉 O(logn)”粗乘估界,逐层加权求和后大多数结点靠近叶、下沉距离很短,这正是上式收敛为线性的原因。之三:堆序方向只是参数,最大堆完全对称;同时应把堆与二叉搜索树的不变量分清,后者约束的是左右子树的键范围。

推论与应用

二叉堆是优先队列 ADT的一种实现,而非该接口本身:在数组 RAM 模型中,它给出最坏 O(1) find-min、最坏 O(logn) insertdelete-min 与已定位元素的 decrease-key。两个二叉堆若直接拼接后重建,meld 需要 O(n1+n2),所以它并不实现可合并优先队列所追求的快速合并成本。

它直接服务于 Dijkstra 算法Prim 算法的“取当前最小候选”主循环、Huffman 编码的反复合并最小权对,以及事件驱动模拟与任务调度。Fibonacci 堆用更复杂的延迟整理换取摊还 O(1)insertmelddecrease-key,但 delete-min 仍为摊还 O(logn);这张成本表不能覆盖回二叉堆。取最大二叉堆反复把根与堆尾交换、缩小堆区并下沉修复,即得原地堆排序:最坏 O(nlogn) 时间、O(1) 辅助空间。

参考资料
  • Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022,Ch. 6, heaps, priority queues, and linear-time build。
  • Robert Sedgewick and Kevin Wayne, Algorithms, 4th ed., Addison-Wesley, 2011,§2.4, priority queues and heaps。
关系图谱11 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系

实现的抽象