Chap10 优先级队列
返回:0300 数据结构总目录 · 上一章:0309 Chap9 词典 · 下一章:0311 Chap11 串
1、学习目标
优先级队列不是按进入时间出队,而是按优先级出队。普通队列关心“先来先服务”,优先级队列关心“当前最重要的是谁”。
| 模块 | 要掌握的内容 |
|---|---|
| ADT | insert、getMax、delMax |
| 完全二叉堆 | 数组表示、父子下标关系 |
| 上滤 | 插入后向上恢复堆序 |
| 下滤 | 删除堆顶后向下恢复堆序 |
| 批量建堆 | Floyd 自底向上建堆 |
| 堆排序 | 原地排序、复杂度、稳定性 |
| 拓展 | 左式堆、d 叉堆 |
2、资料范围
| PDF 课件 | 内容 |
|---|---|
10.Pq.A.Basic_Implementation.pdf |
优先级队列基本实现 |
10.Pq.B.Complete_Binary_Heap.pdf |
完全二叉堆 |
10.Pq.C.Tournamentsort.pdf |
锦标赛排序 |
10.Pq.D.Heapsort.pdf |
堆排序 |
10.Pq.XA.Leftist_Heap.pdf |
左式堆 |
10.Pq.XB.d-heap.pdf |
d 叉堆 |
3、优先级队列 ADT
以最大优先级队列为例:
| 操作 | 含义 |
|---|---|
insert(e) |
插入元素 |
getMax() |
查看最大元素 |
delMax() |
删除并返回最大元素 |
可以用无序向量、有序向量、列表、堆实现:
| 实现 | 插入 | 取最大 | 删除最大 |
|---|---|---|---|
| 无序向量 | O(1) |
O(n) |
O(n) |
| 有序向量 | O(n) |
O(1) |
O(1) |
| 完全二叉堆 | O(log n) |
O(1) |
O(log n) |
堆是折中方案:插入和删除都足够快,取堆顶非常快。
4、完全二叉堆
完全二叉堆同时满足两个性质:
- 结构性:形状是完全二叉树。
- 堆序性:父节点优先级不低于孩子。
数组下标关系,假设从 0 开始:
| 位置 | 下标 |
|---|---|
| 父节点 | (i - 1) / 2 |
| 左孩子 | 2 * i + 1 |
| 右孩子 | 2 * i + 2 |
堆不是 BST。堆只保证父子局部有序,不保证左子树都小于右子树,也不支持中序有序遍历。
5、插入与上滤
插入新元素时,先放到数组末尾,保持完全二叉树形状;再不断和父节点比较,若优先级更高就交换。
1 | void percolateUp(int i) { |
上滤最多走树高层,所以复杂度 O(log n)。
6、删除与下滤
删除最大元素时:
- 保存堆顶。
- 用末尾元素覆盖堆顶。
- 删除末尾。
- 从根开始下滤。
1 | void percolateDown(int i, int n) { |
下滤每层选择更大的孩子交换,保证局部最大值向上移动。
7、批量建堆
如果逐个插入 n 个元素,复杂度是 O(n log n)。Floyd 建堆从最后一个内部节点开始,自底向上做下滤:
1 | for (int i = n / 2 - 1; i >= 0; --i) { |
虽然单次下滤最坏 O(log n),但低层节点很多、可下滤高度很小,总复杂度为 O(n)。
复盘时要能说清楚:Floyd 建堆不是每个节点都走 log n 层,所以不能粗暴估成 O(n log n)。
8、堆排序
堆排序过程:
- 对原数组建最大堆。
- 交换堆顶和末尾。
- 缩小堆规模。
- 对堆顶下滤。
- 重复直到堆为空。
1 | heapify(a); |
特点:
| 项目 | 说明 |
|---|---|
| 时间复杂度 | O(n log n) |
| 空间复杂度 | 原地 O(1) |
| 稳定性 | 不稳定 |
| 优点 | 最坏情况有保障 |
| 缺点 | 缓存局部性和常数通常不如快排 |
9、左式堆
左式堆适合高效合并两个优先级队列。
核心概念是空节点路径长度 npl:
1 | npl(x) = x 到最近外部节点的距离 |
左式堆性质:
1 | npl(left) >= npl(right) |
这使得右侧路径较短,合并时沿右链递归,复杂度为 O(log n)。
合并思路:
- 选择堆顶更大的堆作为根。
- 递归合并它的右子堆和另一个堆。
- 若左子树
npl小于右子树,则交换左右孩子。 - 更新
npl。
10、d 叉堆
d 叉堆把每个节点的孩子数从 2 扩展到 d。
| 操作 | 影响 |
|---|---|
| 树高 | 约为 log_d n,比二叉堆低 |
| 上滤 | 父节点更少,可能更快 |
| 下滤 | 每层要从 d 个孩子中选最优 |
| 场景 | Dijkstra 等大量 decrease-key 或插入的场合 |
d 越大不一定越好,因为下滤每层比较次数会增加。工程上要看操作比例。
11、复杂度汇总
| 操作 | 完全二叉堆 | 左式堆 |
|---|---|---|
getMax |
O(1) |
O(1) |
insert |
O(log n) |
O(log n) |
delMax |
O(log n) |
O(log n) |
heapify |
O(n) |
- |
merge |
不擅长 | O(log n) |
| 堆排序 | O(n log n) |
- |
12、易错点
| 易错点 | 修正 |
|---|---|
| 把堆当成 BST | 堆只保证父子优先级,不保证中序有序 |
| 建堆写成逐个插入 | Floyd 建堆能做到 O(n) |
| 下滤只比较左孩子 | 要选左右孩子中更优的那个 |
| 认为堆排序稳定 | 堆排序通常不稳定 |
| 左式堆只看左右子树大小 | 左式堆看的是 npl,不是节点数 |
13、复盘清单
| 检查项 | 状态 |
|---|---|
| 能写出堆数组的父子下标公式 | 待复盘 |
| 能手写上滤和下滤 | 待复盘 |
能解释 Floyd 建堆为什么是 O(n) |
待复盘 |
| 能手动模拟堆排序前两轮 | 待复盘 |
| 能说明堆和 BST 的区别 | 待复盘 |
| 能解释左式堆适合合并的原因 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!







