返回算法课程
栈与队列进阶

堆与优先队列Heap

只维护父子之间的局部顺序,就能快速取得全局极值。

25 分钟前置:数组 · 完全二叉树

01先从一个问题出发

不断有新任务到来,每次总要选最紧急的任务。无需每次把全部任务排好序。

一个具体的例子

最大堆 [9,7,6,2,3]:根 9 最大;7 与 6 的子树各自满足父值不小于子值。

02最直接的办法,为什么慢?

每次取最大值都线性扫描剩余元素。

暴力 · O(n) / 次

反复取 k 次时,需要 O(kn) 次检查。

03一步步,推导出优化思路

1

发现可以复用的信息

只要求每个父节点不小于它的孩子,根自然就是最大值。

2

建立可维护的状态

用数组存完全二叉树,孩子下标为 2i+1 和 2i+2。

3

消除重复工作

从最后一个非叶子节点向前下沉,自底向上建堆。

贯穿算法的不变量

处理 start 时,它的左右子树已经是堆;下沉后以 start 为根的树也是堆。

04让每一步,都看得见

试着先预测下一步,再点击「下一步」验证。变量、动画与代码会同步变化。

Heap交互演示
STEP 01 / 16
35
0
18
1
52
2
27
3
43
4
12
5
60
6
31
7

数组表示完全二叉树:父节点 i 的孩子是 2i+1 与 2i+2。

当前处理已处理 / 已确定未处理
01

从左到右观察数组。橙色表示当前比较,绿色表示已确定的位置。

heap.py参考实现 · 当前第 2 行
正在载入代码编辑器…

动画展示参考实现的执行轨迹。切换语言时,当前操作会定位到对应代码行。

05复杂度,来自具体的工作量

时间复杂度
O(n)O(n)

这是自底向上的建堆复杂度:约 n/2 个节点无需下沉,n/4 个最多下沉 1 层,累加为 O(n)。单次入堆、出堆为 O(log n)。

辅助空间
O(1)O(1)

原地下沉只需几个下标。教学函数复制输入额外占 O(n);堆本身储存 n 个元素。

拖动 n,看看增长速度

06看到什么,应该想到它?

这些特征值得停下来想一想:

关键词只是线索,继续检查适用条件

07这些细节,容易踩坑

01

堆不是排序数组

兄弟节点之间没有大小保证。

02

区分大小顶堆

维护最大的 k 个值常用容量 k 的小顶堆,根是当前保留值中最小的。

08把想法带进真实题目

先说出模式和适用条件,再开始写代码。

09不用背模板,回答这几个问题

01最大堆保证什么?

02自底向上建堆为什么是 O(n)?

03取出堆顶后的修复成本?

你已经走完了这次推导。

能用自己的话解释复杂度和不变量,再标记为掌握。

把知识连起来