数据结构 · ADS2026
约 120 条中英对照 + 考试英文句式

术语对照表

用途:A 册与 B 册共用。考试为英文试卷,故 English 列为答题时的标准写法,务必能默写。 规则:中文名(English)首次出现双写,之后可只用英文缩写。

一、树与平衡

中文 English(考试用) 一句话辨析
二叉搜索树 Binary Search Tree (BST) 左<根<右;操作 O(height),height 可退化 O(N)
平衡因子 Balance Factor, BF(node) = h_L − h_R AVL 要求 BF ∈ {−1,0,1}
高度平衡 Height Balanced 空树平衡;非空则左右子树平衡且 |h_L−h_R|≤1
单旋转 Single Rotation (LL / RR) 插入点在失衡点"外侧"
双旋转 Double Rotation (LR / RL) 插入点在失衡点"内侧",先转子节点再转失衡点
左旋 / 右旋 Left Rotation / Right Rotation 旋转后新根上移,原根下沉
伸展树 Splay Tree 每次访问把该节点旋转到根
之字形 zig-zig 同向两层,先转祖父再转父(不是两次单旋)
之字折 zig-zag 异向两层,等价于对该节点做双旋
摊还分析 Amortized Analysis 最坏操作序列的均摊保证,不是 average-case
聚合分析 Aggregate Analysis 总代价 ÷ 总操作数
记账法 Accounting Method 给操作"预付"信用,信用不得为负
势能法 Potential Method Φ 定义在数据结构状态上,摊还代价 = 实际 + ΔΦ
红黑树 Red-Black Tree (RB Tree) 5 条性质,弱平衡,h ≤ 2log(N+1)
黑高 Black-Height, bh(x) x 到叶子的路径上黑节点数(不含 x,含 NIL)
外部节点 / 空节点 External Node / NIL 必为黑色
B+ 树 B+ Tree 数据全在叶子;内部只存索引;叶子间有链
阶 Order M 每个节点最多 M 个孩子,最多 M−1 个键
分裂 Split 溢出(键数 > M−1)时中间键上移

二、算法设计范式

中文 English 辨析
回溯法 Backtracking 解空间树 + DFS + 剪枝
解空间树 State-Space Tree 子集树 / 排列树
约束函数 Constraint Function 排除不可行解(如皇后同列)
限界函数 Bounding Function 排除不可能优于当前最优的分支
极小极大 Minimax 双方零和博弈,层层交替取 max/min
α-β 剪枝 Alpha-Beta Pruning α=Max 已保证下界,β=Min 已保证上界;α≥β 则剪
评估函数 Evaluation Function 必须廉价,否则搜不深
分治法 Divide and Conquer Divide / Conquer / Combine
代入法 Substitution Method 先猜后用归纳法证
递归树法 Recursion Tree Method 画树求每层代价之和
主定理 Master Theorem / Master Method T(N)=aT(N/b)+f(N)
正则条件 Regularity Condition a·f(N/b) ≤ c·f(N), c<1(case 3 必需)
动态规划 Dynamic Programming (DP) "用表代替递归"
最优子结构 Optimal Substructure 最优解包含子问题的最优解
重叠子问题 Overlapping Subproblems 与分治的分水岭
状态转移方程 State Transition Equation 也称递推式 recurrence
松弛 Relaxation 图算法中更新距离估计
贪心算法 Greedy Algorithm 每步局部最优
贪心选择性质 Greedy-Choice Property 局部最优可扩展为全局最优
交换论证 Exchange Argument 把最优解逐步"换成"贪心解而不变差
安全选择 Safe Choice 存在某个最优解包含该选择
活动安排 Activity Selection / Interval Scheduling 按结束时间贪心
哈夫曼编码 Huffman Coding 频率最低两个先合并
加权外部路径长度 Weighted External Path Length (WPL) Σ(频率 × 深度)

三、复杂度与计算理论

中文 English 辨析
渐近上界/下界/紧确界 O / Ω / Θ 分别 ≤ / ≥ / = 常数倍
伪多项式 Pseudo-polynomial O(n·W),W 是数值非输入规模
判定问题 Decision Problem 答案 Yes/No
优化问题 Optimization Problem 求最大/最小
P 类 Class P 多项式时间可解
NP 类 Class NP 多项式时间可验证
NP 完全 NP-Complete (NPC) ∈NP 且所有 NP 问题可归约到它
NP 难 NP-hard 所有 NP 问题可归约到它,可不属于 NP
多项式归约 Polynomial-time Reduction, A ≤_p B A 不难于 B
可满足性问题 SAT / 3-SAT 第一个被证 NPC 的问题(Cook-Levin)
团问题 Clique Problem 找最大完全子图
顶点覆盖 Vertex Cover NPC
哈密顿回路 Hamiltonian Cycle NPC;欧拉回路则在 P 内
停机问题 Halting Problem 不可判定
近似比 Approximation Ratio ρ(n) max(C/C, C/C) ≤ ρ(n)
近似方案 Approximation Scheme 输入 ε>0,输出 (1+ε)-近似
多项式时间近似方案 PTAS 对固定 ε,时间 poly(n)
完全多项式时间近似方案 FPTAS 时间 poly(n, 1/ε)
启发式算法 Heuristic 无法证明解质量界
装箱问题 Bin Packing NPC
下次适应 Next Fit ≤2M−1
首次适应 First Fit ≤17/10 · M*
首次适应递减 First Fit Decreasing (FFD) ≤3/2 · M*,且 3/2 为最优界
K 中心问题 K-center Problem 2-近似

四、考试高频英文句式(可直接默写)

场景 标准英文表述
摊还结论 By the potential method with , the amortized cost of a splay operation is O(log n).
AVL 高度 The height of an AVL tree with n nodes is at most 1.44 log₂ n.
红黑树高度 A red-black tree with n internal nodes has height at most 2 log₂(n+1).
红黑树优势 The decisive advantage of red-black trees lies in deletion, which requires at most 3 rotations, whereas AVL deletion may need O(log n) rotations.
Floyd 顺序 The intermediate vertex k must be the outermost loop, so that d[i][j] at stage k only uses paths through vertices {1,…,k−1}.
K 路归并 With a min-heap, K-way merge runs in O(N log K); total O(N log K · log_K N) = O(N log N), independent of K.
主定理 Since f(n) = Θ(n^{log_b a}), Case 2 of the Master Theorem gives T(n) = Θ(n^{log_b a} log n).
近似比 The algorithm achieves an approximation ratio of ρ(n) if max(C/C, C/C) ≤ ρ(n) for every instance.
NPC 证明 To prove X is NP-complete: (1) show X ∈ NP; (2) pick a known NP-complete problem Y; (3) give a polynomial-time reduction Y ≤_p X.
归约方向 If A ≤_p B, then B is at least as hard as A; an algorithm for B solves A.
FFD 下界 Unless P = NP, no approximation algorithm for bin packing can have ratio < 3/2.
全 8 讲压缩版,95 考点全覆盖

A0 速查册

配合 A1_详解册_*.md 使用。每考点只保留"能救命"的 6 行。 标记:★ = 必考/易错(源自老师原话或正答率 <30%)


1 上 · AVL / Splay / 摊还

1 上AVL · Splay · 摊还1 下红黑树 · B+ 树2 上回溯 · 博弈剪枝2 下分治 · 主定理3 上动态规划3 下贪心4 上NP 完全性4 下近似算法
八讲主题全景:先结构(第 1 讲),再设计范式(第 2–3 讲),最后计算极限(第 4 讲)

1.1.1 BST 退化

  • 本质:查找 O(height),但 height 可退化为 O(N)
  • 有序插入 → 链表形态;平均查找时间 3.5 → 6.5(PPT 月份例)
  • 不要靠"挑个好插入顺序"(老师称之为"好学生陷阱")

1.1.2 ★ AVL 定义

  • 平衡因子 BF = h_L − h_R ∈ {−1, 0, 1}
  • 高度平衡:左右子树都平衡 + |BF| ≤ 1
  • 空树高度定义为 −1

1.1.3 AVL 高度界

  • h ≤ 1.44 log₂ N
  • 最少节点 N(h) = N(h−1) + N(h−2) + 1,N(0)=1, N(1)=2
  • 含义:AVL 比最优平衡树只差约 44%

1.1.4 ★ 四种旋转

情形 判定 操作
LL 插入点在失衡点左子树的左子树 对失衡点单次右旋
RR 右子树的右子树 单次左旋
LR 左子树的右子树 先对左子节点左旋,再对失衡点右旋
RL 右子树的左子树 先对右子节点右旋,再对失衡点左旋
- 考试动作:插入后沿路径回溯找第一个 BF =2 的节点

1.1.5 删除

  • 删除可能需沿路径多次旋转(插入只需一次修复)
  • 删除后继/前驱替换后,问题下移到子树

1.1.6 Splay 基础

  • 每次访问(查/插/删)后把该节点旋转到根
  • 不维护平衡因子,无额外空间开销

1.1.7 ★ 三种 splay 情形

情形 形态 操作
zig x 的父是根 单旋一次
zig-zig x 与父同向(都是左孩子/都是右孩子) 先旋祖父,再旋父
zig-zag x 与父异向 对 x 连做双旋
- ★ 最大坑:zig-zig 不是连续两次单旋(那叫"单旋上提"),必须先转祖父

1.1.8 ★ 为何必须双旋

  • 单旋上提:访问长链最深处节点后,链仍是链,下次访问同链仍 O(N)
  • 双旋:路径上节点深度大致减半 → 摊还 O(log N)
  • 只有双旋才能保证摊还界;单旋没有摊还保证

1.1.9 ★ 摊还三法

方法 思路
聚合分析 算 M 次操作总代价上界 ÷ M
记账法 每个操作预付信用,任意前缀信用 ≥ 0
势能法 摊还 = 实际 + ΔΦ,Φ ≥ Φ₀

1.1.10 Splay 势能法

  • Φ = Σ_x log(size(x)),size 为子树节点数
  • 结论:单次 splay 摊还 O(log N);M 次共 O(M log N)
  • 英文句:By the potential method with Φ = Σ log|subtree|, the amortized cost of a splay is O(log n).

1.1.11 MultiPop 栈

  • 普通 push/pop O(1),MultiPop(k) 单次最坏 O(N)
  • 但每个元素最多被弹出一次 → 摊还 O(1)
  • ★ 若"一次 push 可引发一次 pop"建模,则 push 摊还 O(1)、pop 摊还 O(1),整体 O(1)

1.1.12 ★ 摊还 ≠ 平均

  • 摊还:最坏序列的均摊,是确定性保证
  • 平均:需对输入分布做概率假设
  • 判断题见"摊还等于平均"→ 错

1 下 · 红黑树 / B+ 树

1.2.1 ★ 五条性质

  1. 每个节点非红即黑
  2. 根是黑
  3. 每个叶子(NIL)是黑
  4. 红节点的两个孩子都是黑(不能红红相连)
  5. 每个节点到其所有后代叶子的路径上黑节点数相同 - ★ 注意:内部"叶子"(无孩子的实节点)可以是红的;"叶子"指 NIL

1.2.2 黑高

  • bh(x) = 从 x(不含 x)到叶子的任意简单路径上的黑节点数
  • bh(Tree) = bh(root);NIL 算黑并计入

1.2.3 引理

  • size(x) ≥ 2^bh(x) − 1(对 bh 归纳)
  • 归纳步:size(x) = 1 + 2·size(child) ≥ 1 + 2(2^{bh(x)−1} − 1) = 2^bh(x) − 1

1.2.4 ★ 树高上界(完整两步)

  1. 由 1.2.3 与 N = size(root):N ≥ 2^bh − 1
  2. 红节点不相邻 ⇒ 任一简单路径上黑节点 ≥ 一半 ⇒ bh ≥ h/2
  3. 合并:N ≥ 2^{h/2} − 1 ⇒ h ≤ 2 log₂(N+1)

1.2.5 ★ 插入三情形

  • 新节点染红 → 只在"父也是红"时出问题
  • Case 1:叔节点红 → 父与叔变黑、祖父变红,问题上移(祖父不可能是根则继续)
  • Case 2:叔节点黑,且呈"之字形"(x 与父异向)→ 对 x 旋转,转为 Case 3
  • Case 3:叔节点黑,且呈"一字形" → 父变黑、祖父变红,对祖父旋转
  • 最后:根强制为黑

1.2.6 ★ 口诀

  • 老师原话:"swap colors and rotate"——换颜色、转一下;哪两个颜色换、怎么转,是关键

1.2.7 ★ 删除

  • 红黑树删除最多 3 次旋转
  • AVL 删除最坏 O(log N) 次旋转(一路转上去)
  • ★ 这是红黑树相对 AVL 的绝对优势所在

1.2.8 ★ RB vs AVL

维度 AVL RB
平衡强度 严格 弱
树高 ≤1.44log n ≤2log(n+1)
插入旋转 ≤2 ≤2
删除旋转 O(log n) ≤3
额外空间 平衡因子 parent 指针 + 颜色
纯随机数据实测 稍慢 稍快

1.2.9 工程取舍

  • 大型系统常不做删除 → 红黑树优势消失,此时用 AVL 也可
  • 需要频繁删改 → 红黑树

1.2.10 ★ B+ 树结构

  • M 阶:每节点最多 M 个孩子、M−1 个键
  • 数据全部在叶子;内部节点只存索引(子节点的最小值)
  • 叶子之间有指针链(支持范围查询)
  • 所有叶子在同一层(完美平衡)

1.2.11 ★ 键数界(最易算错)

  • 根:1 ~ M−1 个键(非叶根至少 2 个孩子)
  • 非根内部节点:⌈M/2⌉ − 1 ~ M−1 个键
  • 叶子:⌈M/2⌉ − 1 ~ M−1 个键(本校 PPT 的 M=4 图上存在 4 键叶,故上限按 M−1;若题目另行约定按题目)
  • ★ 记法:孩子数上限 M,键数上限 M−1;下限都是"一半取上整"

1.2.12 插入分裂

  • 键数 > M−1(溢出)→ 分裂为左右两半
  • 中间键上移到父节点;父若溢出则继续上传
  • 若传到根 → 新根,树高 +1(B+ 树自底向上生长)

1.2.13 磁盘 I/O

  • 多叉 → 树高极矮 → 磁盘 I/O 次数少
  • 分裂比"旋转/合并"更符合块读写特性,开销可接受

1.2.14 NIL 为何黑

  • 使"叶子黑高"定义统一,保证性质 5 在所有空子树成立
  • 内部实节点(无孩子的)可以是红的

2 上 · 回溯

2.1.1 回溯框架

  • 解空间树 + DFS + 剪枝
  • 递归模板:if 到达叶 → 记录解; else for 每个候选 { if 可行 { 标记; 递归; 撤销 } }

2.1.2 解空间树

  • 子集树:每层选/不选 → 2^n 叶子
  • 排列树:每层选一个未用元素 → n! 叶子

2.1.3 两类剪枝函数

类型 作用
约束函数 Constraint 剔除不可行解(如皇后冲突)
限界函数 Bounding 剔除不可能更优的分支(如当前价值+剩余上界 ≤ 最优)

2.1.4 N 皇后

  • 约束:不同列、不同主对角(r−c 不同)、不同副对角(r+c 不同)
  • 4 皇后:仅 2 个解

2.1.5 ★ 搜索顺序优化

  • 最大距离优先:距离问题中最大距离只对应少数确定位置(端点),一旦失败可砍掉半个搜索空间
  • 最小距离随机性大 → 剪枝效率低
  • 结论:剪枝效率同时取决于剪枝函数与搜索顺序

2.1.6 Minimax

  • Max 层取最大、Min 层取最小,交替
  • 博弈树深度受限 → 需评估函数截断

2.1.7 ★ Alpha-Beta

  • α = Max 层目前已能保证的下界(初始 −∞)
  • β = Min 层目前已能保证的上界(初始 +∞)
  • Max 节点:值 ≥ β ⇒ 剪枝;Min 节点:值 ≤ α ⇒ 剪枝
  • 等价记忆:α ≥ β 时剪枝
  • ★ 剪枝不改最坏复杂度(仍 O(b^d)),但最好情况降到 O(b^{d/2})

2.1.8 评估函数

  • 必须足够廉价:有限时间内能评估更多局面 → 搜得更深
  • 质量与速度的权衡

2.1.9 ★ 剪枝的性质(判断题)

  • 剪枝只减少实际运行时间,不改变最坏情况复杂度
  • "剪枝把复杂度从 O(2^n) 降到 O(n^2)" → 错

2.1.10 Turnpike 重构

  • 由点对距离集合重构点集;最大距离必为两端点

2.1.11 AlphaGo

  • 深度网络(策略/价值)+ MCTS 替代穷举,非纯 alpha-beta

2 下 · 分治

2.2.1 三步

  • Divide(划分子问题)→ Conquer(递归求解)→ Combine(合并)

2.2.2 ★ 适用条件

  • 子问题相互独立(不重叠)
  • 子问题解能合并成原问题最优解
  • 反例:加权活动安排——能分,但子问题最优解合不成全局最优(需 DP)

2.2.3 侧重点差异

  • 归并排序:Divide 平凡,代价在 Combine
  • 快速排序:Combine 平凡,代价在 Divide(选主元)

2.2.4 ★ 分治 ≠ O(N log N)

  • tree traversal 也是分治,但为 O(N)
  • 最近点对:若 Combine 为 O(N²) 则整体退化
  • ★ 老师原话:"不要一看到分治,马上就想到 N log N"

2.2.5 代入法

  • 先猜上界 → 归纳证明 → 求使不等式成立的常数
  • ★ 归纳时不能引入多余项(如猜 O(N) 却证出 N·log N)

2.2.6 ★ 非完美递归树

  • T(N) = T(N/3) + T(2N/3) + cN
  • 树不平衡,右侧路径最长 log_{3/2} N,左侧最短 log₃ N
  • 每层代价 ≤ cN,深度 ≤ log_{3/2} N ⇒ 猜 O(N log N)
  • 代入证明:需 d ≥ c/(log₂3 − 2/3)

2.2.7 ★ 主定理三情形

  • T(N) = aT(N/b) + f(N),比较 f(N) 与 N^{log_b a} | 情形 | 条件 | 结论 | |---|---|---| | 1 | f(N) = O(N^{log_b a − ε}) | Θ(N^{log_b a}) | | 2 | f(N) = Θ(N^{log_b a}) | Θ(N^{log_b a} log N) | | 3 | f(N) = Ω(N^{log_b a + ε}) 且 a·f(N/b) ≤ c·f(N) | Θ(f(N)) |
  • ★ Case 3 的正则条件最易漏;Case 2 常考

2.2.8 主定理另一形式

  • T(N) = aT(N/b) + Θ(N^p log^k N)
  • 比较 p 与 log_b a:p<→情形1;p=→乘 log^{k+1};p>→Θ(N^p log^k N)

2.2.9 ★ 主定理的适用形状

  • 只能处理 f(N) 形如 N^p log^k N 的递归式
  • ★ 老师原话:"它和函数的形状太特殊了,一定要这种形式才能套结论"

2.2.10 ★★ K 路归并(正答率最低题)

场景 复杂度
K 为常数 O(N log N),与 K 无关(log_K N 差常数因子)
K 为变量,线性扫描找最小 O(KN log_K N)
K 为变量,最小堆优化 O(N log K · log_K N) = O(N log N) ← K 被抵消
★ 无堆,K=N 退化为选择排序 O(N²)
★ 有堆,K=N 退化为堆排序 O(N log N)

2.2.11 堆优化推导

  • 每次合并从 K 个头取最小:堆 O(log K)
  • 总合并次数 N-1,分治层数 log_K N
  • 总 = N log K · log_K N = N log K · (log N / log K) = N log N

2.2.12 退化

  • K 太大且无堆 → 复杂度急剧上升
  • 实践建议 K 取 2~3 即可(再多无意义)

2.2.13 最近点对

  • 分治好环的唯一条件是 Combine(跨中线的最短距离检查)做到 O(N)
  • 做法:按 y 排序后每个点只查常数个后继点

2.2.14 课程定位

  • 分治重点不是实现(两个递归而已),重点是复杂度分析 + 可合并性证明

3 上 · 动态规划

3.1.1 ★ 本质

  • "Use a table instead of recursion"——用表代替递归
  • 存子问题解,避免重复计算:指数 → 多项式

3.1.2 ★ 三要素

  1. 最优子结构(最优解含子问题最优解)
  2. 重叠子问题(子问题被反复求解)← 与分治的分界
  3. 无后效性(当前状态包含决策所需全部历史)

3.1.3 矩阵链乘

  • c(i,j) = min_{i≤k<j} { c(i,k) + c(k+1,j) + p_{i−1}p_k p_j }
  • 填表按链长递增(len = 2..n)

3.1.4 OBST

  • T_ij = min_{i≤k≤j} { T_{i,k−1} + T_{k+1,j} + Σw }
  • r_ij 记录最优根,用于重构

3.1.5 ★★ Floyd 顺序(正答率 ~20%)

c
for (k = 0; k < n; k++)        // ← 必须最外层
  for (i = 0; i < n; i++)
    for (j = 0; j < n; j++)
      if (d[i][k] + d[k][j] < d[i][j])
        d[i][j] = d[i][k] + d[k][j];
  • k 代表 DP 阶段:第 k 轮结束时,d[i][j] 是"只允许经过 {0..k} 中结点"的最短路
  • 英文句:k must be the outermost loop, since d[i][j] at stage k may only use intermediate vertices {1,…,k−1}.

3.1.6 ★ k 放内层的反例

  • 构造:若 k 在内层,某些 d[i][j] 在"某些中间点尚未就绪"时就被用来更新别人
  • 结果:存在图使最终矩阵不是最短路径(可举 4~5 节点具体反例)
  • ★ 方向:先想一条需要经过两个以上中间点的路径,再看更新次序

3.1.7 前驱矩阵

  • next[i][j] 的要求与 d 完全相同 → k 同样必须在外层
  • 否则重建出的路径断裂

3.1.8 0-1 背包 DP

  • f[i][w] = max(f[i−1][w], f[i−1][w−w_i] + v_i)
  • 一维优化:w 必须从大到小遍历(否则同一物品被用多次)
  • ★ 复杂度 O(nW) 中 W 是数值,故为伪多项式

3.1.9 ★ 参数陷阱

  • O(n·Pmax)、O(n·W) 这类"与数值成正比"的算法:
  • 输入用二进制编码时,W 的位数是 log W
  • 故真实复杂度是指数级的
  • 结论:背包 DP 不是多项式算法 → 这也是 0-1 背包是 NPC 而又有 DP 解的原因

3.1.10 ★ 加权活动安排

  • 加权重后贪心失效,只能 DP
  • ★ 老师原话:"这是个坑,这个公式其实是错的,这个地方得是权重"——递推式中要乘的是该活动的权重,不是 1
  • 递推:f[j] = max(f[j−1], f[p(j)] + w_j)

3 下 · 贪心

3.2.1 贪心框架

  • 每步取局部最优,必须证明能导出全局最优
  • 两个条件:贪心选择性质 + 最优子结构

3.2.2 ★ 两种证明方法

方法 要点
交换论证 取任一最优解,与贪心解逐项比对,把第一个不同处"换掉",证明不变差
安全选择 归纳证明:每一步的选择都存在于某个最优解中

3.2.3 活动安排(区间调度)

  • 贪心:按结束时间升序,能放就放
  • 结论:能安排最多活动(最大相容子集)
  • ★ 若按开始时间或持续时间贪心 → 反例

3.2.4 ★★ 最少教室

  • 问题:若干区间,最少多少间教室让所有区间各占一间?
  • 答案:教室数 = 时间轴上的最大重叠数
  • 贪心:按开始时间排序,每次尝试放进"最早空出来且已空的教室",放不下则开新教室
  • 英文句:The minimum number of classrooms equals the maximum overlap of intervals.

3.2.5 ★ 反例:复用轮子

  • 错误策略:"先把一间教室排满,再处理剩下的"
  • 反例:某些活动组合会让"排满"占用了本可共享的时段 → 结果多于最大重叠数

3.2.6 ★ Huffman 两个引理

  1. 频率最低的两个字符可以放在最底层(且为兄弟)
  2. 一旦合并,替换不破坏最优性(把最优树中该两叶换成最低频两个,代价不增)

3.2.7 Huffman 证明(交换论证)

  • ★ 老师原话:"算法超简单,代码不长,难在证明上"
  • 关键两步即 3.2.6 的两条引理

3.2.8 Huffman 构造与 WPL

  • 每次取两个最小频率合并,新节点(频率和)回插
  • WPL = Σ(频率 × 码长);编码前缀无关(左 0 右 1)

3.2.9 ★ 贪心 vs DP

  • 有"权重/非等值"→ 通常贪心失效 → 用 DP
  • 判断:贪心选择后,剩余问题是否仍是同类型的同规模问题

4 上 · NP 完全性

4.1.1 ★ 四个类

类 定义
P 多项式时间可判定
NP 多项式时间可验证(给出解能快速验证)
NPC ∈ NP 且所有 NP 问题 ≤_p 它
NP-hard 所有 NP 问题 ≤_p 它(不要求∈ NP)
- 包含关系:P ⊆ NP;NPC = NP ∩ NP-hard

4.1.2 ★ NP-hard 未必在 NP

  • 停机问题:NP-hard 但不可判定 → 不在 NP
  • 判断题见"NP-hard 必属 NP" → 错

4.1.3 ★★ 归约方向

  • A ≤_p B 读作"A 可多项式归约到 B"
  • 含义:B 至少和 A 一样难;解决了 B 就解决了 A
  • ★ 证明 X 是 NPC:取已知 NPC 的 Y,证 Y ≤_p X(不是 X ≤_p Y!)
  • ★ 这是历年高频错点

4.1.4 归约的形式定义

  • 存在多项式时间函数 f:A 的实例 x → B 的实例 f(x),使 x∈A ⟺ f(x)∈B

4.1.5 ★ 优化 → 判定

  • 例:"最长简单回路" → "是否存在长度 ≥ K 的简单回路"
  • 引入参数 K(或 ε),把最值问题变成 Yes/No

4.1.6 ★ 归约的层面

  • 难度比较与归约必须在判定问题层面进行
  • 原因:优化问题的"等价性"难以定义(不同解集),Yes/No 才能逻辑等价

4.1.7 停机问题

  • 不可判定(图灵);NP-hard 但不在 NP

4.1.8 哈密顿回路

  • NPC;判定"是否存在经过每个顶点一次的回路"
  • ★ 欧拉回路(每条边一次)则在 P 内(度数条件)

4.1.9 SAT → 3-SAT

  • 每个子句写成合取范式,拆分成每句 ≤3 个字面量
  • 3-SAT 是后续所有归约的"根"

4.1.10 团问题

  • 最大完全子图;NPC
  • 典型归约:3-SAT ≤_p Clique

4.1.11 最短 vs 最长路径

  • 最短路径(无负环):Dijkstra/Bellman-Ford,P
  • 最长简单路径:NPC
  • ★ 差异根源:"不能重复顶点"这个全局约束

4.1.12 ★★ NPC 证明三步法

  1. X ∈ NP:给出证书与多项式验证算法
  2. 选 Y:已知 NPC 的问题(SAT/3-SAT/Clique/VertexCover/HamCycle…)
  3. Y ≤_p X:构造多项式归约,并双向证明(Y 有解 ⇒ X 有解;X 有解 ⇒ Y 有解)

4 下 · 近似算法

4.2.1 ★ 近似比定义

  • max(C/C, C/C) ≤ ρ(n)(对最小化问题即 C/C* ≤ ρ)
  • 又称 ρ(n)-近似算法
  • ★ 注意 max 里的双向比值(既管"不太大"也管"不太小")

4.2.2 ★ 近似 vs 启发式

  • 近似算法:能证明 ρ 的上界
  • 启发式:无法分析解质量("随便试试")
  • ★ 判断题高频:不能证明界的算法不叫近似算法

4.2.3 ★ PTAS vs FPTAS

  • 近似方案:输入 ε>0,输出 (1+ε)-近似
  • PTAS:固定 ε 时,时间 poly(n)(对 1/ε 可指数,如 O(n^{1/ε}))
  • FPTAS:时间 poly(n, 1/ε)(如 O(n³/ε²))
  • ★ 记法:F = Fully,连 1/ε 也管住

4.2.4 ★ 参数陷阱

  • 若复杂度形如 O(n · 2^{1/ε}) → 对固定 ε 是 poly(n),是 PTAS 但不是 FPTAS
  • 若形如 O(n^{1/ε}) → PTAS 但非 FPTAS
  • 若形如 O((1/ε)² n³) → FPTAS

4.2.5 ★ Next Fit

  • 规则:只看当前箱,装不下就开新箱(不回看旧箱)
  • 结论:NF ≤ 2M − 1,且存在序列达到 2M−1(紧界)
  • 证明要点:NF 用 2M 箱 ⇒ 最优至少 M+1 箱;相邻箱大小之和 > 1

4.2.6 ★ Si < 1/2 时的界

  • 设所有物品 ≤ α < 1/2
  • 每个箱中至多 1 个物品是"未配对"的 → 相邻两箱之和 > 1
  • 结论:ρ ≤ 1/(1−α)
  • 例:α=1/3 → ρ ≤ 3/2

4.2.7 ★★ α = 1/2 是分界点

  • 1/(1−α) 在 α=1/2 时 = 2,与常数界 2 重合
  • α<1/2 → 1/(1−α) < 2(更紧)
  • ★ 经典错误选项:设 ρ = 2α,当 α<0.5 得 ρ<1(近似解比最优还好)→ 逻辑不可能 → 该选项错

4.2.8 ★★ FFD = 3/2

  • FFD:先按大小降序排序,再做 First Fit
  • 两个不同的界,不可混写:
  • 绝对上界(本校教材口径):,对所有 成立
  • 渐近上界(更紧,大 时才显现):,即
  • ⚠️ 与 不是同一个式子,不要用等号连接。考试按课堂口径答 3/2;若题目问"渐近最优比"再答 11/9
  • 证明思路:分类讨论不同大小物品的箱数占比

📌 为什么两个界不矛盾:3/2 刻画"最坏实例的绝对倍数"(由 很小的构造达到);11/9 刻画" 时的趋近倍数"。二者描述不同 区间上的行为。

4.2.9 ★★ 3/2 的最优性

  • 反证:若存在 ρ < 3/2 的多项式近似算法
  • 则可用它判定"能否用 2 个箱子装下"这一 NPC 问题
  • ⇒ P = NP,矛盾
  • 故 3/2 是理论最优上界

4.2.10 First Fit

  • 规则:放入第一个装得下的箱
  • 结论:FF ≤ 17/10 · M*

4.2.11 K-center

  • 选 K 个中心最小化最大半径;2-近似(贪心最远点)

4.2.12 近似下界定理

  • 除非 P=NP,某些问题不存在低于某 ρ 的近似算法
  • 例:一般 TSP 无常数比近似(除非 P=NP)

4.2.13 ★★ 算法设计三目标(课程总结题)

目标 含义 代价
全实例最优解 对所有输入都最优 可能 NP-hard,无多项式算法
特殊实例最优解 只对部分实例多项式求最优 覆盖不全
全实例快速解 对所有实例都快,且有质量保证 即近似算法
1.1.1 – 1.1.12

详解 01 · AVL 与 Splay

配套 A0_速查册.md 的「1 上」部分。速查册给结论,本册给推导、图示与手算过程。 证据约定:[PPT:1上:n] = PPT 文本行号;[稿:n] = 课堂录音文稿/1-逐字稿.md 行号。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

BST  →  高度可退化到 O(N)                    (1.1.1 问题)
     →  AVL:用 BF ∈ {−1,0,1} 当"报警指标"    (1.1.2 指标)
        → 坏了用 O(1) 旋转就地修好            (1.1.4 修复)
        → 高度界 h ≤ 1.44 log₂N 保证代价      (1.1.3 证明)
     →  Splay:不修结构,只把访问点转到根      (1.1.6/1.1.7)
        → 必须 zig-zig/zig-zag,否则退化      (1.1.8)
        → 代价用摊还分析兑现 O(M log N)       (1.1.9–1.1.12)

两条设计哲学的对立([稿:278]):

  • AVL 是「特别拘谨」的树——要求每一个操作都是 log N,随时盯着结构;
  • Splay 是「胆大妄为」的树——不管形状,碰谁就把谁转到根,用摊还把总账拉平。

1.1.1 BST 退化问题 【B 级】

1. 一句话本质

二分查找快是因为它对应的树「很平」;但 BST 的形状由插入顺序决定,有序输入直接退化成一条链,查找从 log N 变成 N。

2. 定义与性质

二叉搜索树(Binary Search Tree, BST):对任意节点 x,左子树所有键 < x.key < 右子树所有键,且左右子树递归地是 BST。

查找代价(PPT 原文):

Problem : Although Tp = O( height ), but the height can be as bad as O( N ).
                                              [PPT:1上:20-21]

即

下界对应完全二叉树,上界对应链。二分查找(binary search)不能用于动态场景:它要求数组有序且不变,插入/删除后重排是 O(N)([稿:92])。

平均查找时间(average search time, AST)= 各键查找比较次数之和 ÷ 键数:

其中 为键 的深度(根深度 0)。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《二叉搜索树》 · https://oi-wiki.org/ds/bst/ OI Wiki 把 BST 拆成四条递归条款:空树是 BST;若左子树非空,则左子树上所有点的附加权值均小于根节点的值;若右子树非空,则右子树上所有点的附加权值均大于根节点的值;左右子树本身也是 BST。量词落在整棵子树上而不是孩子节点上,这是定义能站住的原因:只比较父子会放过「孙子比祖父大」的树。条款一给基例,条款四给归纳步,缺一条定义就不封闭。附加权值(auxiliary weight)一词把键与卫星数据分开,旋转只搬指针不搬键,于是该定义在旋转下自动保持。

2.2 OI Wiki 校订|基本性质

  1. 中序单调(in-order monotonicity):中序遍历的权值序列非降。为什么成立:对结构归纳,拼接顺序为「左段 + 根 + 右段」,两段整体小于(大于)根。
  2. 操作代价正比于高度:查找、插入、删除都只沿一条根到叶的路径下行,每层常数次比较,故 。
  3. 高度的两端:下界由完全二叉树给出 ,上界由链给出 。取 :下界 ,上界 ,相差 4 层。
  4. 随机树的期望高度 :随机指 个键的 种到达顺序等可能,期望是对排列取的,这一步已经引入了概率假设。
  5. ASL 的上界表述:OI Wiki 只说平均查找长度不超过 ,不规定成功/失败查找的分母;要写出 这类具体值,必须自己声明分母。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 显式允许重复键,用 count 域记录出现次数,于是中序只保证非降;本课程按 Weiss 口径要求键互异,中序严格递增,插入到相等键属非法输入。OI Wiki 把平衡性单独放在「平衡性的定义」一节,写成「每个结点的左右子树高度差最多为 1」,本课程把递归条件与空树高度 一并写进定义,两者在判定上等价。另有一处同名不同义:OI Wiki 在 BST 页把右旋记作 zig、左旋记作 zag,而 Splay 页的 zig 指「 上移一步」;写 zig 必须限定语境,否则与 1.1.7 冲突。

3. 图示

PPT 月份例(12 个月按英文名字母序插入):[PPT:1上:28-44]

按 Jan→Dec 顺序插入(无序输入,运气好)          按下标顺序/字母序插入(最坏)
        Jan                                        Jan
      /     \                                        \
   Feb       Mar                                     Feb
   /  \      /  \                                      \
 Apr  June May  ...                                     Mar
 /  \    \      /  \                                     ...
Aug July  ... Sept  Oct                                   Dec

   AST = 3.5   (14 个节点的平衡树 AST = 3.1)      AST = 6.5   (纯右斜树)

验算 :斜树深度依次为 ,比较次数为 ,

验算 (PPT 第一棵树):根 Jan 深 0;Feb、Mar 深 1;Apr、June、May 深 2;Aug、July、Sept 深 3;Dec、Oct、Nov 深 4。
PPT 标 3.5,是四舍五入/口径差异(有的教材把失败查找也算入分母)。考试按公式算,得到 41/12 就写 41/12。

4. 复杂度表

操作 平均(随机输入) 最坏 摊还
Search / Insert / Delete(普通 BST) 无保证
上述操作的纯代价(节点访问次数)

5. 典型例题

Insert the keys 1, 2, 3, 4, 5, 6, 7 in this order into an initially empty BST. Compute the average successful search time.

中文逐步:

  1. 每次插入的键都比已有一切键大 → 一律挂到最右下 → 得到右斜链 。
  2. 深度:。
  3. 比较次数:,总和 。
  4. 。
  5. 对照:7 个键的完全二叉树高度为 ,最优 可以做到 。退化让平均代价几乎翻倍。

6. 易错点 + 老师原话

  • 易错:「有序输入才会退化」——不完全对。任何让键单调或近似单调到达的序列都会退化;且退化与否取决于到达顺序,而动态系统无法控制到达顺序。
  • 易错:以为可以「挑一个好插入顺序」解决。老师称之为好学生陷阱(good-student trap):

「大家要警惕一个思维陷阱,当你遇到一个问题的时候,第一反应是我要给完美的解决方案。」[稿:119-122]

「你要找一个好顺序,这个前提是你知道你要插入的数据是什么……我们在处理的是个动态问题,动态问题的意思你根本不知道下一秒会发生什么。」[稿:128]

  • 老师原话(正解):

「工程师思维的一个特点我从来承认这个世界不完美……我知道这棵树会变坏……坏了修了。」[稿:131]

7. 考试英文答题模板

A plain BST supports search in , but can degenerate to when keys arrive in sorted order, making every operation in the worst case. Since the arrival order of keys in a dynamic system cannot be controlled, the fix is to keep the tree locally balanced by an on-the-fly repair of cost, rather than to choose a good insertion order.


1.1.2 AVL 定义与平衡因子 【S 级】

1. 一句话本质

别跟 log N 比树高(你连 N 都不知道),改看「左右子树高度差」这个随手能算的局部指标;差超过 1 就报警。

2. 定义与性质

PPT 原文两条定义 [PPT:1上:49-58]:

【 Definition 】 An empty binary tree is height balanced. If T is a
 nonempty binary tree with TL and TR as its left and right subtrees,
 then T is height balanced iff
 (1) TL and TR are height balanced,  and
 (2) | hL − hR | ≤ 1
 where hL and hR are the heights of TL and TR, respectively.
 The height of an empty tree is defined to be –1.

【 Definition 】 The balance factor BF( node ) = hL − hR .
 In an AVL tree, BF( node ) = −1, 0, or 1.

中文解释:

  • 高度平衡(height balanced)是递归定义:左右子树各自平衡 且 高度差绝对值 ≤ 1。
  • 空树高度 = −1。这条不是装饰——它让「叶子高度 = 0」「单节点 BF = 0」自洽:叶子左右皆空,,(这里用的是 )。
  • 平衡因子(balance factor) ,左减右,符号必须全文一致([稿:158]:「你要愿意定义成右边减左边也随便,你只要一致就行」)。

由此推出三条等价刻画(考试常用):

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《AVL 树》 · https://oi-wiki.org/ds/avl/ OI Wiki 不写「定义」而直接列三条:空二叉树是 AVL 树;若 是 AVL 树,则其左右子树也是 AVL 树,且 ;树高为 。前两条是递归定义,第三条是定理,混在同一个列表里容易被读成第三个条件。递归基是空树,归纳步要求「左右子树各自合法」与「高度差不超过 1」同时成立:前者保证性质沿树向下传,后者是唯一与形状有关的约束。OI Wiki 全篇没有指定空树高度的具体数值,因为它只用到差值——把整棵树的高度统一加一个常数,任何 都不变,所以基例取 (叶子为 )或 (叶子为 )判定结果完全相同。

2.2 OI Wiki 校订|基本性质

  1. 双符号等价(sign symmetry): 与 互为相反数,而集合 关于 对称,故「是否 AVL」的判定不受符号方向影响。例:左子树高 3、右子树高 1,OI 口径 ,PPT 口径 ,都判非法,但落在 LL 还是 LR 的符号正好相反。
  2. 与高度差同一:,所以「」与「高度差绝对值 」是同一句话的两种写法。
  3. 失衡幅度至多为 2:一次插入或删除只改变一条路径上每个祖先的一侧子树高度 1, 相对合法值 至多偏移 1,故被破坏的节点满足 。若算出 ,说明此前已非法或一次改动了多处。
  4. 单旋的判据是高度比较:设失衡点 较高一侧孩子为 , 的两棵子树为 ,则 时单旋, 时双旋。用高度而非「插入点位置」判据,对删除同样成立。
  5. 高度 :见 1.1.3 的计数论证。

2.3 OI Wiki 校订|与课堂口径的差异

平衡因子方向相反是首要差异。OI Wiki 页首写「平衡因子:右子树高度 - 左子树高度」,本课程 PPT 写 ,两者互为相反数;考试按 PPT 的左减右。随之而来的是判定的符号:PPT 里 LL(左重)报 、RR(右重)报 ;若照 OI Wiki 页首的方向,LL 报 、RR 报 ,而旋转动作不变。OI Wiki 自身还有一处不一致:同页「平衡的维护」小节写 ( 是左孩子)与伪代码 if h(l)-h(r)=2,用的都是左减右,与页首那句相反。另外 OI Wiki 的高度从层数起算、未定义空树高度,本课程定义空树高度 ,两者只差一个整体平移,判定等价但算 时必须换算(见 1.1.3)。

3. 图示

PPT 三棵树辨析 [PPT:1上:59-63],加 BF 标注(括号内为 BF):

 (a) 不是 AVL                       (b) 是 AVL                    (c) 不是 AVL
        4 (0)                          5 (0)                        7 (2)
       / \                            / \                          / \
   (-1)3  5(0)                  (-1)2   8(0)                  (0)2   8(0)
     /     \                       / \   /                     / \   /
 (-1)2    6(0)               (-2)1 4 7  (0)               (-1)1 4  6
   /            \                /                          /    /
(-1)1          7(0)          (-1)3                      (-1)3  5

 根 BF=0 但子树 3 的 BF=−2          全部节点 |BF|≤1              根 BF=2 > 1
 左右子树都不平衡                    虽有多条路径长短差 2         且 7→2 的 BF=0 但 7→8 的
                                    仍合法                     左子树挂 4、4 挂 5 使 2 的
                                                               右子树高度加 1

判定口诀:从根往下一路检查,任意一个节点 即非法。

4. 复杂度表

指标 值 说明
合法 BF 取值 共 3 种
维护 BF 的额外空间 每节点 2 bit(或 1 个 int) 也可只存 height,用 现算
判断是否失衡 / 节点 比较两子树高度

5. 典型例题

Which of the following are AVL trees? (i) root 4 with left subtree {3,2,1} as a left chain and right child 5 with right child 6 and 6's right child 7; (ii) root 5 with left {2,1,3,4} and right {8}; (iii) root 7 with left {2,1,3,4,5} and right {8,6}.

中文逐步:

  1. 逐节点算高度(空子树高度 −1)。
  2. (i) 节点 3:左链高度 1(3→2→1 取 3 的左子树高度),右子树空 , → 非法。
  3. (ii) 节点 2:左高 0(只 1),右高 1(3→4), 合法;节点 5:左高 2,右高 0, → 非法(PPT 图中根 5 的 BF=0 是因为它左右子树高度都是 2,但左子树内部已坏)。
  4. (iii) 节点 7:左子树 {2,1,3,…} 高 3,右子树 {8,6} 高 1, → 非法。
  5. 结论:三棵全非法。第 3 棵正是老师课上说的「看上去子树也是平的,但根节点就不平了,差两层」[稿:161-164]。

6. 易错点 + 老师原话

  • 易错:把「根 BF = 0」当成合法的充分条件。(ii)就是反例:根 BF=0,但左子树内部 BF=2。AVL 是递归性质,必须验全部节点。
  • 易错:空树高度写 0。写成 0 则叶子高度为 1,整条链的 BF 值全错位。
  • 易错:BF 符号方向中途换。PPT 用左减右;若中途改右减左,LL/RR 的判定会正好互换。
  • 老师原话:

「他定义空树的树高定义成 −1 的。」[稿:157-158]

「它定义了一个 bf 不是 boyfriend。是 balance factor……定义成左边减右边,你要愿意定义成右边减左边也随便,你只要一致就行,它是定义成左减右。」[稿:158]

「如果是 avl tree 合法的 avl tree,它每一个节点的 bf 就只有三种可能 −1 零或者正一。除了这三个数字以外,别的都要报警。」[稿:158-160]

7. 考试英文答题模板

An AVL tree is a BST that is height balanced: the empty tree is height balanced (height ), and a nonempty tree with subtrees is height balanced iff both and are height balanced and . The balance factor is defined as , and is an AVL tree iff for every node .


1.1.3 高度上界 【A 级】

1. 一句话本质

要让树最「瘦高」,每层左右子树高度只能差 1,于是最少节点数服从斐波那契递推;反解出 就是 ,底数 1.618 换成 2 就是 1.44。

2. 定义与性质

定义 = 高度恰为 的 AVL 树的最少节点数(PPT 原文:minimum number of nodes in a height balanced tree of height h,[PPT:1上:245-246])。

关键观察:最瘦的 AVL 树,根的两棵子树高度必为 与 (差 1 已是极限,再少就违反 AVL;再多就不是最瘦),且各自也是最瘦的。于是

基例核对: 空树 ; 单节点 ; 根 + 一个孩子 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《AVL 树》 · https://oi-wiki.org/ds/avl/ OI Wiki 把同一问题写成 = 高度为 的 AVL 树所包含的最少节点数,其中高度按层数从 1 起算,空树不进入下标:

形式化地,。两个基例不能合并成 ,因为高度为 1 时根没有孩子仍算 1 个节点,而「空树」在层数口径下没有高度。递推的理由是:根的较高子树高必为 (否则整树高不到 ),较矮子树高必为 (差 1 已是用满的额度,差 2 非法,差 1 以下则不是最少),两棵子树各自取最少才使总数最少。

2.2 OI Wiki 校订|基本性质

  1. 闭式 :以 归纳, 满足斐波那契递推,故 ,数列为 。
  2. 与本课程 是同一数列的平移:。核对:,,,。换算规则是 。
  3. 高度上界: 对一切 成立( 时 ),取 得

    右侧严格不等式来自 ,故 。代 验证:,而 。

  4. 常数 1.44 与 1.5 的关系: 是紧界, 是为好写而放松的界,二者都对,考试要写 并给出 的来历。

  5. 反解: 个节点的 AVL 树满足 ,即 。
  6. 与完全二叉树的差距可手算:高度 4(5 层)的完全二叉树要 个节点,AVL 只要 个,即本课程的 ;反过来,12 个节点的完全二叉树高度只有 ,AVL 却能到 4。把这个差距写成公式就是 对最优的 ,比值 。

2.3 OI Wiki 校订|与课堂口径的差异

三处必须换算。其一,计数起点:OI Wiki 令高度为 1 时最少 1 个节点(),本课程令空树高度 、叶子高度 (),关系是 ,把 OI 的 直接当 用会整体错一位。其二,上界形式:OI Wiki 给 ,本课程给 ; 与 是同一量的紧、松两个界,考试按 。其三,OI Wiki 的通项

与它自己的递推式差一个下标:代 得 ,而 ;该式实际算的是 ,正确写法应把 换成 。考试一律以 (等价地 )为准。

3. 图示

        N(h) 的形状(两条可能,互为镜像)
              x                          x
            /   \                      /   \
      N(h-2)    N(h-1)          N(h-1)    N(h-2)
      较矮一侧   较高一侧          较高一侧   较矮一侧
                              [PPT:1上:248-253]

  具体展开(h = 3,最少 7 个节点):
                4
              /   \
             2     6
            / \   / \
           1  3  5   7      ← 完全树,7 节点

  再瘦一点(h = 4,最少 12 个节点):
                5
              /   \
             3     7
            / \   / \
           2  4  6   9
          /          / \
         1          8  10
                        \
                        11
   左子树高 2(3 个节点:2,1 / 4),右子树高 3(5,6,9,8,10,11),差 1。
  递归下去左子树自己也是 N(2)=4 最瘦形、右子树是 N(3)=7 最瘦形。

4. 复杂度表

量 值
1
2
闭式
反解

5. 典型例题(完整推导,共 6 步)

Prove that the height of an AVL tree with nodes satisfies .

Step 1|递推式从何而来

设 是高度 、节点数最少的 AVL 树,根为 。 两棵子树高度记为 ,则 。

  • 由 AVL 条件 ;
  • 为让节点总数最少, 应尽可能小 → ;
  • 每棵子树也必须是同高度的最瘦 AVL 树,否则替换成更瘦的会得到更少节点(矛盾于最小性)。

故 。∎(这一步的「为什么」: 是根本身; 是两子树节点数;「最少」保证我们取的是最坏形状。)

Step 2|对比斐波那契

斐波那契 [PPT:1上:257-258]。列出两列:

0 1 2 4 7 12 20 33
0 1 2 4 7 12 20 33

Step 3|归纳证明闭式

  • 基例 : ✓;: ✓。
  • 归纳:设对 成立,则
    ✓。注意那个 与两个 恰好抵消——这就是老师说的「它比 fibonacci number 其实更坏一点,它加一了……减一」[稿:233] 的确切含义。

Step 4|斐波那契的闭式(Binet 公式)

第二项 , 大时忽略。PPT 原文即此式 [PPT:1上:260-266]。

Step 5|从「最少节点」反解「最大高度」

固定 个节点,高度最大对应最瘦的树,即 。故

取对数:

Step 6|常数 1.44 的来历

所以
含义:AVL 相对理论最优的完全二叉树只差约 44% 的高度,相当于查找多走四成的路——这个代价换来的是插入/删除只需 旋转。

6. 易错点 + 老师原话

  • 易错:把 当成「高度为 的树的节点数」(那是 )。 是最少节点数。
  • 易错:递推写成 (漏 +1)→ 闭式会变成 ,全部数值错位。
  • 易错:基例用 却说 。正确是 。
  • 易错:只写「由斐波那契增长指数级,故 」而不给 1.44。题目若问常数,必须给 。
  • 老师原话:

「你拍脑袋想一个算法并不难,难的是你要证明算法是有效的。」[稿:227]

「Nh 就是什么,它左边子树的个数加右边子树的个数加一……我们看递推式就很眼熟,很像我们 fibonacci number。」[稿:233]

「总的来说,它是 1.几的 H 次方减一,你就可以推出来……只不过,这个常数比 binary search tree 一个完美的 binary search tree 来说,它那个 log 是以二为底的,而它,不是以 2 为底的,它是 1.几为底的。」[稿:236]

7. 考试英文答题模板

Let be the minimum number of nodes in an AVL tree of height . Since the two subtrees of the root must themselves be AVL trees of heights and , we have with . By induction, , and Binet's formula gives with . Hence , so . Therefore the height of an AVL tree with nodes is , bounded by .


1.1.4 四种旋转 LL / RR / LR / RL 【S 级|画树大题必考】

1. 一句话本质

看「闯祸的节点」(trouble maker)相对于「报警的节点」(trouble finder)走了哪两步:外侧一步 → 单旋;内侧折返 → 双旋,而且双旋顺序是先转孩子、再转失衡点。

2. 定义与性质

PPT 原文的命名逻辑 [PPT:1上:77-79]:

The trouble maker Nov is in the right subtree's right subtree
of the trouble finder Mar. Hence it is called an RR rotation.
  • trouble finder:从插入点向上回溯时第一个 的节点,记作 。
  • trouble maker:新插入的节点。
  • 记 的孩子为 ( 较高的一侧), 的孩子为 (新节点所在方向)。
  • 判定表:
情形 判定(插入点相对 的方向) 操作 旋转次数
LL 左子树的左子树 对 右旋(right rotation) 1
RR 右子树的右子树 对 左旋(left rotation) 1
LR 左子树的右子树 先对 左旋,再对 右旋 2
RL 右子树的左子树 先对 右旋,再对 左旋 2

关键性质(AVL 最精妙之处,[稿:185-188]):旋转前后这棵子树的高度不变。

  • 旋转前高 ;旋转后新根 的两子树高度为 与原 的新高度,仍等于原高度。
  • 因此对整棵树其他部分零影响,只需沿回溯路径更新 BF。这就是「局部问题,局部解决」[稿:185]。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《二叉搜索树》 · https://oi-wiki.org/ds/bst/ 对节点 的右旋(right rotation,OI Wiki 亦称 zig、LL 平衡旋转)定义为:把 的左孩子 向右上旋转代替 成为子树的根, 向右下旋转成为 的右孩子, 原来的右子树 改挂为 的左子树。左旋(left rotation,zag、RR 平衡旋转)与之镜像。两种操作都只重排三组父子边,因此实现时要先暂存新根 ,再按「 的左孩子指向 → 的右孩子指向 → 的父亲指向 」的顺序更新,顺序颠倒会丢指针。四种破坏情形的定义按过长的那条路径给出:LL 是 的左孩子的左子树过长,RR 是右孩子的右子树过长,LR 是左孩子的右子树过长,RL 是右孩子的左子树过长;前两者一次单旋,后两者先转孩子再转自身。

2.2 OI Wiki 校订|基本性质

  1. 中序不变(in-order invariance):旋转前后中序遍历相同,故 BST 序保持。为什么成立: 的键集恰为 这段开区间,正是 左子树应有的取值范围。例:,旋转后 4 挂到 5 的左边,仍满足 。
  2. 代价 :只改三组关联,与树高、树规模无关。
  3. 四情形完备:失衡只可能来自「较高的一侧」(2 种)再叠上「该侧孩子的较高一侧」(2 种),共 4 种,没有第五种。
  4. 双旋等于两次单旋:LR 先对 左旋化归为 LL 再对 右旋;RL 先右旋 化归为 RR 再左旋 。故只需实现一个左旋、一个右旋。
  5. 旋转后高度可复算:令失衡点 的另一侧子树高 ,则较高侧孩子 满足 , 的两棵子树满足 、。取 :、,右旋后 ,与 之差为 或 ,合法。

2.3 OI Wiki 校订|与课堂口径的差异

命名层次不同:OI Wiki 把右旋直接叫「LL 平衡旋转」,操作名与失衡类型名合一;本课程把 LL 当失衡类型,把动作叫「右旋」,答题时写 "LL 型失衡,对 做一次右旋(right rotation)" 最清楚。节点标号也不同:OI Wiki 的 AVL 页用 表示失衡点、 表示较高侧孩子、 表示 的两棵子树、 表示另一侧,本课程用 当 trouble finder、 当较高孩子、 当孙子,字母含义正好错位一层,读 OI Wiki 的证明时不要混用。判据也不同:OI Wiki 用 判单旋,本课程用「插入点在外侧还是内侧」判,插入场景下两者等价,删除场景下只有高度判据成立。另需注意 BST 页的 zig/zag 指右旋/左旋,与 Splay 页的 zig 不是同一概念。

3. 图示(四种,旋转前后 + BF 变化)

RR:单次左旋

  BEFORE(插入前是 AVL)        插入 Nov → 报警                  AFTER
        A(+1)                       A(+2)                        B(0)
       / \                         / \                          /   \
     AL   B(0)                   AL   B(−1)                  A(0)    BR(0)
         / \                          / \                    /  \      \
       BL  BR(−1)                  BL   BR(0)             AL   BL     (原BR位置)
                                         \
                                        Nov(0)

  旋转动作(3 步指针改写):B 上移,A 下移成 B 的左孩子,B 原来的左子树 BL 挂到 A 的右边
      A.right = B.left
      B.left  = A
      (A.parent 的孩子指针改为 B)

  顺序合法性(必须会证):
    BL 原在 B 的左子树 → BL < B
    A 原在 B 的左子树 → A < B
    BL 原在 A 的右子树 → BL > A
    ⇒ A < BL < B,所以 BL 挂在 A 的右子树完全合法,BST 序未破坏。
                                    [稿:173-182]

BF 变化规律:插入前 、(否则不会在 RR 插入后变 −2);插入后 、;旋转后 。

LL:单次右旋(RR 的镜像)

  BEFORE                   插入 → 报警                 AFTER
      A(−1)                    A(+2)? 见注              B(0)
     / \                      / \                     /   \
   B(0) AR                  B(+1) AR              BL(0)   A(0)
   / \                      / \                           /  \
 BL  BR                   BL  BR                        BR    AR

  注:AVL 里 LL 的 BF(A) 用左减右是 +2,上图标 (−1)→(+2) 是示意"失衡方向反转";
  严格取值:插入前 BF(A)=−1,插入后 BF(A)=+2。
  旋转动作:B 上移,A 下移成 B 的右孩子,B 原右子树 BR 挂到 A 的左边。
      A.left = B.right ; B.right = A
  合法性:BR 原在 B 右子树 ⇒ BR > B;BR 原在 A 左子树 ⇒ BR < A
          ⇒ B < BR < A ✓

LR:双旋(先孩子后失衡点)

BEFORE插入→报警AFTERA(+2)A(+2)C(0)/\/\/\B(+1)ARB(+1)ARB(0)A(0)/\/\/\/\BLC(0)BLC(−1)BLCLCRAR/\/\CLCRCLCR/Jan(0)──第1步:对B左旋(把C提上来,仍然是普通BST单旋)──A(+2)/\C(−1)AR/\B(0)CR/\BLCL──第2步:对A右旋──C(0)/\B(0)A(0)/\/\BLCLCRAR⚠顺序不能反:先对A旋转不能消失衡(A的左子树仍比右子树高2),必须先把"折线"B–C–插入点掰成"直线"C–B与C–A。

PPT 原例 [PPT:1上:124-155]:插入 Mar, May, Nov, Aug, Apr, Jan 后 May 的 BF 变 +2,Jan 落在 May 左子树(Apr)的右子树 → LR,把 Jan 提为新子树根,Aug 与 Mar 分列两侧;Jan 的左右孩子按序重新分配(左归 Dec,右归 Mar)[稿:221]。

RL:双旋(LR 的镜像)

  BEFORE               插入 → 报警                AFTER
    A(−2)                 A(−2)                  C(0)
    /  \                  /  \                  /    \
  AL   B(−1)            AL   B(+1)          A(0)     B(0)
      /  \                  /  \            /  \     /  \
    C(0) BR               C(+1) BR        AL   CL   CR   BR
    / \                   / \
  CL  CR                CL  CR
                        /
                     Feb(0)
  第 1 步:对 B 右旋;第 2 步:对 A 左旋。

PPT 原例 [PPT:1上:158-193]:插入 Dec, July, Feb 后 Aug 失衡,Feb 落在 Aug 右子树(Dec)的左子树 → RL,把 Dec 提上去。

四个情形的 BF 汇总

情形 旋转前 旋转前 旋转后各 BF
RR 0
LL 0
LR 0
RL 0

一般情形( 或 本来带高度差)下,PPT 给出「 or 」「 or 」的分支 [PPT:1上:148-152, 186-190],考试只在插入场景下遇到(此时新节点使高度只加 1,故 BF 必然归零)。

4. 复杂度表

操作 平均 最坏 摊还
单次旋转(指针改写) (3 次指针赋值) —
插入总旋转次数 次(单旋或双旋,最多 2 次基础旋转) —
删除总旋转次数 — (沿路径一路转上去) —
插入总时间 —

插入后只需修复第一个报警点,且修复后子树高度不变 → 上层绝不会再失衡 → 最多 1 处旋转(单旋 1 次,双旋 2 次基础旋转)。

5. 典型例题(完整手算)

Insert Mar, May, Nov, Aug, Apr, Jan, Dec, July, Feb, June, Oct, Sept successively into an initially empty AVL tree. Draw the tree after each insertion.

步 插入 结果树 说明
1 Mar Mar 根
2 May Mar(−1)←May May > Mar 右挂
3 Nov May(0);左 Mar、右 Nov Mar BF 变 −2 → RR,左旋
4 Aug May(−1);左 Mar、右 Nov(+1)←Aug 无需旋转
5 Apr May;左 Mar(左 Apr)、右 Nov 无需旋转
6 Jan 见下 LL(左的左)在 Mar 处报警
7 Dec — RL(右的左)在 Aug 处报警
8 July — 无旋转

第 6 步细节(LL 的关键:报警点是 Mar 还是 May?):

插入 Apr 后若 May 报 −2,再插 Jan 会同时让 May(−2) 和 Mar(+2) 看似可疑。老师明确说过:

「我其实图画的不太对,我故意画错了一点,你会发现,它有两个节点都报警。以谁报警为准?……最下面的。」[稿:194-197]

「因为你的插入是递归的,从上往下走走到最下面插好了,然后递归回退是从下往上走的……从下往上碰到的第一个他报警就停住了,不会再到上面去报警了。」[稿:197]

理由:Mar 修复后子树高度恢复到插入前,May 的 BF 自动复原,那个「二的报警」是不存在的([稿:212]:「这是障眼法,二是不存在的」)。

第 6 步最终形态(PPT [PPT:1上:95-108] / [稿:201-206]):

        May(−1)
        /      \
   Mar ∩      Nov(0)
    /  \
  Apr  Jan     ← 若按 LL 修完 Mar 处,Apr 与 Jan 同侧

按 PPT 图(插入 Jan 后)实际形状为:

          Mar
         /   \
       Aug    May
       / \    / \
     Apr Jan (Dec) Nov

考试中只要严格遵守「最深的第一个报警点」+ 四情形判定,画出来的树一定与标准答案同构。

6. 易错点 + 老师原话

  • 易错 ★:LR/RL 的旋转顺序。先转子节点,再转失衡点。反过来转不消失衡。
  • 易错 ★:把「最上面的报警点」当修复点。必须是最深的(递归回溯遇到的第一个)。
  • 易错:双旋转后忘了重新分配 的左右子树。 的左子树归 (当 成为 的左孩子时),右子树归 。老师原话:

「重要的是 january 它左边右边孩子都有,左右孩子归谁?左边的归 december,右边的归 march。」[稿:221]

  • 易错:只更新报警点的 BF。即使不旋转,沿途 BF 也必须更新——PPT 原文 Several bf's might be changed even if we don't need to reconstruct the tree. [PPT:1上:236-239],老师原话:

「即使我们不需要重构这棵树,我们沿途的这些 bf 还都是要 update,都是要改一下的。」[稿:224]

  • 易错:以为 一定是根。PPT 原文 A is not necessarily the root of the tree [PPT:1上:80-81]。
  • 老师原话(局部性):

「局部的问题,局部解决……旋转是什么?旋转就相当于是把 B 的左指针跟 a 的右指针换了一下?三步……它是 O 一时间复杂度的,它不是 on。」[稿:185]

7. 考试英文答题模板

After an insertion, let be the first (deepest) node on the path from the new node to the root with , and let be 's taller child, the child of on the insertion path. If the insertion lies in 's outer subtree (LL when 's left-left, RR when 's right-right), a single rotation at restores balance; if it lies in the inner subtree (LR / RL), a double rotation is required, rotating first and then . In both cases the subtree's height is restored to its pre-insertion value, so no further rotation is needed above , and each rotation is .


1.1.5 插入与删除的差异 【A 级】

1. 一句话本质

插入是「长高了」→ 修一次就够,因为旋转把高度还回去;删除是「变矮了」→ 高度可能真的降低,一路往上连锁,可能转 次。

2. 定义与性质

维度 插入(insertion) 删除(deletion)
触发原因 某子树高度 +1 某子树高度 −1
回溯中遇到的失衡点 只可能 1 个(第一个) 可能 多个(连锁)
修复后子树高度 恢复原值 → 上层不受影响 可能减少 1 → 上层可能失衡
旋转次数 处(≤2 次基础旋转) 最坏 次
能否提前终止 修复完即结束 只有当子树高度未变时才能停
被删节点 — 度为 2 时先用后继/前驱替换,问题下移到叶或度 1 节点

PPT 在「1 上」未展开删除细节,老师的原话解释了原因:

「普通的你 avl 数的删除,为什么我们没讲 avl 数的删除,因为太烦了因为太烦了。」[稿:293]

「删除的话,它有可能牵一发动全身。但是,删除的牵一发动全身也是 log n 数量级的……你整个的旋转调整都是沿着子树的根节点一路向上走,最多也就是走 log N 步走到根节点调平了就完了。」[稿:368-371]

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《AVL 树》 · https://oi-wiki.org/ds/avl/ OI Wiki 把删除定义为:先按 BST 的做法找到节点,度为 2 时与后继交换再删除,于是真正被摘下的位置必为叶或只有一个孩子;随后「删除会导致树高以及平衡因子变化,这时需要沿着被删除结点到根的路径来调整这种变化」。形式化地,维护过程是一个自底向上的循环:输入是 AVL 树 与键 ,输出 需满足(i) 是 AVL 树,(ii)中序序列等于原序列删去 ,(iii)每个节点的高度域与实际一致。循环的每一次迭代取路径上下一个祖先 ,重算 ,若 就在 处按 1.1.4 的判据旋转;终止条件是 已到根,或 所在子树的高度与删除前相同。

2.2 OI Wiki 校订|基本性质

  1. 失衡幅度至多为 2:一次插入或删除只使路径上每个节点的一侧子树高度变化 1,故被破坏的节点满足 ,不需要考虑 的分支。
  2. 插入时的重构至多一次:若破坏由插入引起,旋转后子树高度回到插入前的值,其上所有祖先的 自动复原,循环第一次迭代后即终止。
  3. 删除时不保证一次:若旋转后新子树的高度比删除前少 1,父节点的 仍会偏离,循环必须继续,最坏走到根,故旋转次数 。这是插入与删除的唯一结构性差异。
  4. 问题下移的不变式:与后继交换后,被摘下的位置只有一个孩子或没有孩子,所以「变矮」的起点唯一,向上调整的路径唯一。
  5. 单次旋转与单次重算都是 :旋转改三组边,重算高度取两次 max。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 只说「沿路径调整」,没有给出重构次数的上界;本课程的分野——插入 次基础旋转、删除最坏 次——来自「1 下」的 AVL 与红黑树对比表,是评分的落点,答题必须写出来。替换节点也有一处选择差异:OI Wiki 写与后继交换,不少教材用前驱,两者得到的树不同但都合法,PPT 未指定,答任一并说明「问题下移到叶或度 1 节点」即可。空树高度同样按 1.1.2 的约定处理:OI Wiki 不定义,本课程取 ,删除到空树时 是 1.1.3 递推的基例。

3. 图示

插入:旋转后高度复原,链条断开h+2──────────►修好──────────►h+1(原高度)AA↑上面所有祖先的BF自动复原,不再检查删除:旋转后高度可能再降1,链条继续根──►...──►A──►子树高度h−1→h−2│├─旋转修复A├─若新子树高度=h−2(仍是变矮了)→继续向上检查A的父亲└─若新子树高度=h−1(复原)→停止最坏路径长logN⇒O(logN)次旋转

4. 复杂度表

操作 平均 最坏 摊还
AVL 查找 —
AVL 插入(含旋转) ,≤2 次旋转 —
AVL 删除(含旋转) , 次旋转 —
单次旋转 —

对照(「1 下」PPT [PPT:1下:161-166]):

旋转次数 AVL Red-Black Tree
Insertion
Deletion

这张表就是考点 1.2.7 / 1.2.8 的伏笔。

5. 典型例题

Build an AVL tree with keys by inserting them in order. Then delete key 4 and count the rotations.

中文逐步:

  1. 插入 1,2,3 → 在 1 处 RR → 树 2(1,3)。
  2. 插入 4 → 2(1, 3(−,4)),无处失衡(2 的 BF=−1)。
  3. 插入 5 → 3 处 RR → 树:
        2
       / \
      1   4
         / \
        3   5
  1. 删除 4:4 有两个孩子 → 用后继 5 替换(5 是叶子),删掉 5。
        2
       / \
      1   4      ← 4 的 BF = +1,2 的 BF = −1,均合法
  1. 本例旋转 0 次,但这个「子树真的变矮了 1」正是删除需要继续向上检查的原因。若树更高,2 上方可能接着失衡。

6. 易错点 + 老师原话

  • 易错:以为删除也「最多修一次」。删除没有「高度复原」保证。
  • 易错:度为 2 节点删除时不做后继替换,直接改结构 → 破坏 BST 序。
  • 易错:删除后忘记把「问题节点」下移。替身节点被删后,失衡检测必须从替身原来所在位置向上走。
  • 老师原话:

「删除另外一回事了。删除的话,它有可能牵一发动全身。」[稿:368]

「通常的大型的数据存储里头,我们一般也不删除,因为删除太麻烦了,不划算。又复杂。」[稿:371]

最后一句是「1 下」里「大型系统不做删除 → 红黑树优势消失」[稿:470] 的直接源头。

7. 考试英文答题模板

Insertion into an AVL tree needs at most one restructuring (a single or double rotation), because the rotation restores the height of the affected subtree to its pre-insertion value, so no ancestor can become unbalanced. Deletion, in contrast, may decrease the height of the restored subtree, so the imbalance can propagate upward and up to rotations may be required along the path to the root. This asymmetry is the decisive disadvantage of AVL trees relative to red-black trees, whose deletion needs at most 3 rotations.


1.1.6 Splay 基础 【A 级】

1. 一句话本质

不盯结构、不留平衡因子,只做一件事:碰到谁,就把谁转到根上。这次最坏的节点,转过之后就不再是最坏的。

2. 定义与性质

目标(PPT 原文 [PPT:1上:273-275]):

Target : Any M consecutive tree operations starting from an empty
         tree take at most O(M log N) time.

理念(PPT 原文 [PPT:1上:305-306]):

Idea : After a node is accessed, it is pushed to the root
       by a series of AVL tree rotations.

三条必须分清的事实(PPT [PPT:1上:283-300]):

命题 是否成立
每个操作都 ? 否。单次可
M 次操作共 ? 是(摊还界)
存在「坏输入序列」? 否 —— There are no bad input sequences.

术语链:

  • splay(伸展):把节点 用一系列旋转推到根的过程。
  • zig / zig-zig / zig-zag:splay 的三种步型(见 1.1.7)。
  • 摊还时间(amortized time): 次操作总代价 ÷ 。

Splay 树不存 BF / height / color,节点只有 key + 左右指针 +(可选)父指针:

  • 空间省(对比 AVL 每节点 2 bit、RB 树每节点 1 bit + parent 指针);
  • 形状不可预测(老师称「乱七八糟」[稿:278]);
  • 删除极简(见下)。

删除算法(PPT [PPT:1上:423-434],比 AVL 简单得多):

Step 1: Find X  →  X 已在根(splay 的结果)
Step 2: Remove X →  得到 TL(左)与 TR(右)两棵子树
Step 3: FindMax( TL ) → 最大值被 splay 到 TL 的根,且它没有右孩子
Step 4: Make TR the right child of the root of TL

老师原话:

「你一旦 find max,max 就被推成了根,这个根节点没有右子树……把原来的右子树贴到新的左子树的右孩子这边。掉了两次 find,一贴就好了,deletion 很简单。」[稿:293]

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Splay 树》 · https://oi-wiki.org/ds/splay/ OI Wiki 的表述是:Splay 树(伸展树)是一种平衡二叉查找树,它通过伸展(splay)操作不断将某个节点旋转到根节点,使得整棵树仍满足 BST 性质,并能在均摊 时间内完成插入、查找和删除;由 Sleator 与 Tarjan 于 1985 年提出。形式化地,一个访问原语写作 :先按 BST 规则从根下行比较 ,若命中则记 为该节点,若落空则记 为最后访问的节点;随后执行 ,把 沿路径逐级上旋至根。不变量只有两条:中序序列始终等于建树时的序列;除子树大小等辅助域外不维护任何平衡信息。「平衡二叉查找树」在这里是结论不是条件——形状不受约束,界靠均摊兑现。

2.2 OI Wiki 校订|基本性质

  1. 中序不变(in-order invariance):每次旋转保中序,故 splay 保中序,BST 序不会被破坏。
  2. 单次最坏 :OI Wiki 的脚注明确写「甚至可能在一系列操作后退化为链」,因此不能给出逐操作的 保证。
  3. 均摊 : 次伸展的实际总代价为 ,摊到每次是 。
  4. 界不含概率:均摊界对所有序列成立,不假设输入分布,因此不存在「坏输入序列」。
  5. 删除 = 删根 + 合并:合并两棵 Splay 要求 树最大值小于 树最小值;做法是把 树的最小值旋到根(此时它无左孩子),再把 整棵挂成它的左孩子。
  6. 辅助空间 /节点:除 val、左右孩子外,OI Wiki 的实现另存 fa、cnt、sz,这些是导航与排名用的辅助域,不是平衡域。

2.3 OI Wiki 校订|与课堂口径的差异

删除的拼接方向相反:OI Wiki 在右子树里取最小值上移,再把左子树挂成它的左孩子;本课程在左子树里 FindMax 上移,再把右子树挂成它的右孩子。两种做法互为镜像,结果树不同但都合法,考试按 PPT 的 FindMax 写法。实现方向也有两套:OI Wiki 的代码用 fa 指针、先向下找到目标再一次次上旋,属自底向上;Sleator–Tarjan 原文另给自顶向下版本,向下走的同时用两棵临时树 拼装路径,不需要父指针、常数更小,但 PPT 未涉及,答题写自底向上。另外 PPT 说 splay「不存 BF/height/color」,这句只针对平衡信息;OI Wiki 的实现仍存 sz,若要支持按排名访问就必须存,两者不矛盾。

3. 图示

基本原则:从下往上逐层把 往根推。

访问 X(深在叶下),沿路径一路旋转到根:

      r                    X 上到根
     / \                  /     \
    a   ...    ────►    ...     ...
        /                        \
       X                         (原路径两侧子树重新挂好)
       ↑ 从 X 开始,每两步为一级(zig/zig-zig/zig-zag)

删除:

Step1-2:splayX到根,删XXTLTR/\────►(左)(右)TLTRStep3:在TL里FindMax→M成为TL的根,M.right=nullTLM/\────►/\...M...(无右孩子)(M是最大)Step4:TR挂到M的右边M/\...TR

4. 复杂度表(三种必须分列)

操作 平均 最坏(单次) 摊还(amortized)
Search / Insert / Delete —(不做分布假设)
连续 次操作 — —
splay 一次的旋转次数 —
空间 —
额外每节点空间 — (无需 BF / color) —

注意:「平均」一列对 splay 无定义——它不依赖输入分布,只依赖序列长度。这是 1.1.12 的核心。

5. 典型例题

Insert 1,2,3,4,5 into an empty splay tree (splaying after every insertion), then search for 1. Draw the tree at each step.

中文逐步(老师课上原例 [稿:281-284]):

  1. 插 1:树 = 1。
  2. 插 2:2 挂在 1 右边 → splay(2) → 2 左挂 1。
  3. 插 3:3 挂在 2 右边 → splay(3) → 3 到根,2 左挂 1。
  4. 插 4:→ 4 根,左 3(2(1))。
  5. 插 5:→ 5 根,左 4(3(2(1)))。
      5
       \
       (无右)
       |
       5
      /
     4
    /
   3
  /
 2
/
1

关键点:插入序列 1..N 后,树仍是一条左链(因为每次被 splay 的节点都是刚插入的叶子,只需一次 zig)。这就是老师说的「我就顺序的插入 123,然后我最后就得到了一棵偏树」[稿:281]。

但单次访问 1 后,树形会大幅改观(见 1.1.7/1.1.8 的实测图示)。

6. 易错点 + 老师原话

  • 易错:以为 splay 树保持平衡。它不保证任何时刻的平衡性,只保证序列总代价。
  • 易错:以为 splay 需要额外的平衡信息。它不需要。
  • 易错:以为「M 次 O(M log N)」推出「每次 O(log N)」。这是 1.1.12 的陷阱。
  • 老师原话:

「abl tree 是特别拘谨的一棵树……splay tree 是一个胆大妄为的树,它根本不管你树的结构是什么……我只要你只要让我碰了一个节点,我就把这个节点转到根上去。」[稿:278]

「如果这一次他是最坏的,下一次他最好就是最……如果一次他是最坏的,下一次他最好的,然后平均一下他还行。」[稿:272-275]

7. 考试英文答题模板

A splay tree is a BST with no balance information. After every access (search, insert, or delete) the accessed node is pushed to the root by a series of rotations called splaying. A single operation may cost , but for any sequence of operations starting from an empty tree the total cost is — an amortized bound, with no probability assumption and no bad input sequence. Because the accessed node is always at the root, deletion reduces to deleting the root and joining the two remaining subtrees by splaying the maximum of the left subtree.


1.1.7 zig / zig-zig / zig-zag 【S 级|画树必考】

1. 一句话本质

看 、父 、祖父 三代的关系:没有祖父 → zig;同向两层 → zig-zig(先旋祖父再旋父);异向两层 → zig-zag(对 做 AVL 双旋)。

2. 定义与性质

PPT 原文 [PPT:1上:357-375]:

Try again -- For any nonroot node X, denote its parent by P
and grandparent by G:

  Case 1: P is the root            Rotate X and P
  Case 2: P is not the root
    Zig-zag     G                     X
                   P          D          P        G
            A         X   ──►       A     B    C     D
                B     C          (Double rotation)
    Zig-zig     G                     X
              P              D     A        P
          X                 ──►           B       G
       A    B                                C   D
                                     (Single rotation)

三种情形的精确定义:

情形 条件 旋转次数 动作
zig 是根( 不存在) 1 对 旋转一次, 到根
zig-zig 与 同向(都是左孩子,或都是右孩子) 2 先对 旋转,再对 旋转
zig-zag 与 异向( 是左孩子而 是右孩子,或反之) 2 先对 旋转,再对 旋转(等价于对 的 AVL 双旋)

核心性质(PPT [PPT:1上:380-382]):

Splaying not only moves the accessed node to the root,
but also roughly halves the depth of most nodes on the path.

「roughly halves」(大致减半)——不是精确的,但对摊还界足够。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Splay 树》 · https://oi-wiki.org/ds/splay/ 设 为刚访问的节点, 为其父节点, 为祖父节点。伸展步骤按 的位置与同侧关系分三种,每种都是「对某条边做一次旋转」,即把该边下端的节点上移一层:

  1. zig: 是根节点时使用,按 与 之间的边旋转,把 上移一层。OI Wiki 补了一条 PPT 没有的条件:zig 只在 于伸展开始时深度为奇数的情况下,作为整个伸展的最后一步执行。
  2. zig-zig: 不是根,且 与 同为左子或同为右子。先按 与 之间的边旋转( 上移一层),再按 与 之间的边旋转。
  3. zig-zag: 不是根,且 与 一左一右。先按 与 之间的边旋转,再按旋转后新生成的 与 之间的边旋转。

2.2 OI Wiki 校订|基本性质

  1. 奇偶性(parity):zig-zig 与 zig-zag 每步都把 上移两层,zig 只上移一层。因此 的初始深度 为偶数时全部用双步、不需收尾; 为奇数时最后必须补一次 zig。例: 是两次双步加一次 zig, 是两次双步。
  2. zig 至多一次:因为只有「 是根」时才允许 zig,而一旦 是根,下一步 就到根、过程结束。这条保证均摊式里的常数 只出现一次。
  3. 路径折半:一次双步之后,原来路径上的节点被分到 的两侧,其深度约减半,故下一个访问目标不再深陷。
  4. 旋转总数:。
  5. 与 AVL 的对应:zig-zag 就是 LR/RL 双旋(对 的双旋),zig-zig 是同向的两次单旋。

2.3 OI Wiki 校订|与课堂口径的差异

描述单位不同,读法要换算。OI Wiki 说「按 与 之间的边旋转」,意思是把该边下端的 上移、即对 旋转;PPT 直接说「先对 旋转」,动作完全一致,但把「按边旋转」误读成「旋转 的子树」会得到相反的中间态。三种步型的名字、条件、次序,OI Wiki 与 PPT 完全一致,无需换算;差别只在 PPT 未写 zig 的奇偶性条件——那一句解释了为什么深度的奇偶决定最后一步,也是「一次伸展至多一次 zig」的来源。符号上 OI Wiki 用 ,PPT 用 , 与 同指被访问节点。

3. 图示:三方对比(这是本节最关键的图)

设链式结构,取标准情形( 与 都是右孩子):

════════情形zig-zig(同向)════════初始:第1步:对G左旋第2步:对P左旋(终态)GPX/\/\/\AP──►GX──►PD←D原来挂在这/\/\/\/\BXABCDGC/\/\CDAB════════情形zig-zag(异向)════════初始:第1步:对P右旋第2步:对G左旋(终态)GGX/\/\/\AP──►AX──►PG/\/\/\/\XDBPABCD/\/\BCCD★终态完全同形(X在上,P与G分列两侧,四棵子树ABCD顺序固定)★中间态不同:zig-zig第1步后P在上(G–P–X同向),zig-zag第1步后X在上(G–X–P异向)

老师的原话把这一区分说得很清楚:

「我们有两种可能,一个是 zigzag 就是英文里头叫我中文翻译成叫之字形的,一左一右的,一左一右的情况,我们做 double rotation……还有一种,叫 zigzag zig zig 朝一个方向去 zig zig 的。」[稿:287]

三方对比总表(单层上提 是被否掉的方案,见 1.1.8):

方案 一步涉及节点 旋转次数 每步 上升层数 路径深度变化 终态
单层上提(被否) 1 1 路径几乎不变 在根,其余仍成链
zig 1 1 降 1( 是根,无更深影响) 同
zig-zig 2 2 路径总长约减半 上, 两侧
zig-zag 2 2 路径总长约减半 与 zig-zig 同形

关键辨析:

  1. zig-zig 与 zig-zag 终态同形(都是 带 、 两孩子)。所以「用最终树形区分」是错的。
  2. 区别在中间态:zig-zig 先动祖父( 下沉、 上移),zig-zag 先动父亲( 下沉、 上移)。
  3. 真正被否掉的是「单层上提」——每步只提一层, 虽然到了根,但路径上其余节点的深度几乎没变,下一个访问还是深。1.1.8 给数据。

4. 复杂度表

量 值
zig 旋转次数 1
zig-zig 旋转次数 2
zig-zag 旋转次数 2
一次完整 splay 的总旋转数 (每步 2 次,共 步)
与 AVL 的联系 zig-zag 就是 AVL 的 LR/RL 双旋;zig-zig 是两次同向单旋

5. 典型例题(比照 1.1.8 的实测)

Splay node 9 in the chain 1←2←3←4←5←6←7←8←9 (9 is the root, everything on the left spine). Show each rotation.

逐步( 是 的左孩子 → zig-zig 镜像情形,先对 右旋):

  1. 读: 是根 → 不需要 splay(已在根)。这是本题的陷阱;改从 1 出发。

改问:Splay 3 in the same chain(3 在深度 6):

初始(9 为根,左链 8-7-6-5-4-3-2-1):
        9
       /
      8
     /
    7
   /    ...  3 ← X,P=4, G=5
 /
1

步 1:X=3 与 P=4 都左孩子 → zig-zig,先对 G=5 右旋
        9
       /
      8
     /
    7
   /
  3          ← 3 升到 5 的位置
 / \
2   4
   /
  ...(原 4 的左边 3 的右边,为空)

旋转之后 上升到 的位置(深度 −2),继续对新的 判断。

6. 易错点 + 老师原话

  • 易错 ★★:把 zig-zig 理解成「先转 与 ,再转 与 」(即两次以 为目标的上提)。这不是 zig-zig 的定义。PPT 明确写 Zig-zig ... Single rotation 且图中箭头是 整块翻。
  • 易错 ★:用终态形状区分 zig-zig 与 zig-zag。终态同形。
  • 易错:以为「roughly halves」是精确的。老师原话:

「只能说 roughly roughly,他没有精确的证明,差不多被减半了。」[稿:290]

  • 老师原话:

「正确的解决方法我在考虑 access 这个 node X 的时候,我不光要看 parent,还要看他的 grandparents,我要看两层,然后做双旋。」[稿:287]

「k1 上去 k4 和 k5 拽下来……这个时候,你看树就浅多了。比刚才那棵树要浅。」[稿:287]

7. 考试英文答题模板

Let be the accessed node, its parent, its grandparent. (1) zig: if is the root, rotate with once. (2) zig-zig: if and are both left children or both right children, rotate first and then (two single rotations in the same direction). (3) zig-zag: if and are in opposite directions, rotate first and then (a double rotation, exactly the AVL LR/RL rotation). Every step lifts by two levels and roughly halves the depth of the nodes on the access path, which is what makes the amortized bound possible.


1.1.8 为何必须双旋(含单旋退化的构造性反例) 【S 级】

1. 一句话本质

单层上提只把「这一次的目标」挪到根,路径上其余节点还在原深度;从深端反复访问,每次都收 ,累计 。zig-zig 每轮把路径折半,总账才是 。

2. 定义与性质

先厘清三个常见但错误的说法(本册实测修正,详见文末「与考点总表的不一致」):

流行说法 是否成立 实测结论
①「用单旋插入 1..N 会得到一条链」 不成立 两种方式(单旋/zig-zig)插入 1..N 后都是左链,因为被 splay 的节点永远是叶子,只触发 zig(两种方案重合)
②「访问链最深处节点后,链仍然是链」 不成立 单个操作后 已在根;链虽保留但被「劈」成两段(见下)
③「单旋退化到 」 成立,但需两个条件叠加 必须 ①从深端开始 + ②反复访问,缺一不成立

正确的反例结构:

  • 初始为一棵平衡的 BST(高度 );
  • 访问序列为 (从最深的键开始,逐个变浅);
  • 用 单层上提(naive zig):每次只把目标与父亲转一次, 上移 1 层;
  • 用 zig-zig:每步 2 次旋转,路径长度折半。

为什么单层上提在这个序列上必然是 :

  • 单层上提后, 到了根,但原路径上其余 个节点被压到 的一侧,形成一条长 的链;
  • 下一个访问目标(次深的键)恰好在这条链的最深端附近;
  • 于是每次访问都要走 ,共 次访问 → 。

为什么 zig-zig 是 :

  • 每步 2 次旋转把访问路径的长度折半(splay 的势能分析给出每次 摊还);
  • 次访问 × = 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Splay 树》 · https://oi-wiki.org/ds/splay/ 被比较的两个算法可以写得很干净。记访问序列为 ,第 次访问的代价 为路径长度(边数加一)。单层上提(naive rotate-to-root):while 不是根,对 的父亲做一次单旋,使 上移一层。标准伸展:while 不是根,按 zig / zig-zig / zig-zag 之一执行,每步上移两层(末步可能一层)。两者对同一次访问的目标相同(都把 送到根),区别只在每次推进的层数与中间态,因此总代价 可以逐序列比较。

2.2 OI Wiki 校订|基本性质

  1. 单层上提就是连续 zig:其每步均摊式含常数 (),望远镜求和后常数项累积成「步数」,即路径长度。OI Wiki 的原话是「zig 操作的均摊复杂度中的常数项 会不断累积,造成最终的均摊复杂度达到 级别」。
  2. 累积的后果是序列级而非单次级:单次访问无论用哪种算法都是 ;差别只在大 的总账上,单层上提退到 量级。
  3. 双步吃掉常数:zig-zig 的界是 ,两次旋转的代价 被势能差吸收,且一步走两层,所以常数项不会随深度累加。
  4. 至多一次 zig:常数 在整次伸展里只出现一次,总额外项是 而不是 。
  5. 几何等价说法:一次双步把原路径节点摊到 两侧,路径长度约减半,于是 次访问总代价 。本册实测在 时为 对 。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 只给定性结论(朴素反复旋转会到 量级)与根因(常数项累积),不给常数、不给实测;本册把常数补成 并列出 256 的实测值,两者不冲突。机制解释的口径不同:PPT 的讲法是几何账——单旋后「路径上其余节点深度几乎没变」,下一个目标仍深;OI Wiki 的讲法是势能账——每步的常数 累加。两种说法结论一致,答题时两句话都写最稳。术语上 OI Wiki 称这一做法为「朴素的再平衡思路」,与 PPT 的「单层上提 / naive rotation」同义,没有第三种叫法。

3. 图示(构造性反例)

初始平衡树(以 为例,用「先访问 1 一次」把链掰成平衡形):

        5
       / \
      2   7
     / \ / \
    1  3 6  8
      (4)   (9)

方案 A:单层上提,序列 9,8,7,...,1

访问 9(深度 4)→ 单层上提 4 次 → 9 到根,8 7 6 仍成链挂在 9 左侧
     9
    /
   8
  /
 7
/ ...          ← 其余节点几乎没变浅

访问 8(深度 ~7)→ 又是 O(N)
访问 7(深度 ~7)→ 又是 O(N)
...

方案 B:zig-zig,同序列

访问 9 → 每步 2 层,路径折半:9 到根,同时 7,8 被 "摊平" 成 9 的两个孩子
        9
       / \
      5   8
     / \  /
    2  6 7
   / \ / \
  1 3 4
之后访问 8(深度 2)、7(深度 2)…… 路径长度持续减半

实测数据( 个节点的平衡树,访问序列 ,统计总搜索深度):

单层上提总代价 zig-zig 总代价 理论对照
32 527 120 ;
64 2079 256 ;
128 8255 534 ;
256 32895 1096 ;

读法:单层上提严格贴合 (比值 1.00–1.03);zig-zig 严格 (甚至比 的常数界还小)。 时差距 30 倍,且随 增大差距按 继续拉大。

另一个反例(更容易手画): 的平衡树,访问 1,2,...,9 再 9,8,...,1(往返遍历):

方案 总代价
单层上提 52
zig-zig 32

4. 复杂度表

方案 单次最坏 次总代价(病态序列) 摊还
单层上提(naive zig) 无 保证
zig-zig / zig-zag
AVL(对照) (且是每步保证)

5. 典型例题

Consider a balanced BST with keys. Access sequence is . Compute the total search cost under (a) naive zig (rotate the accessed node with its parent only), (b) standard splaying.

中文逐步(实测值):

  1. 初始树:4(2(1,3), 6(5,7)),高度 2。
  2. (a) 单层上提: - 访问 7:深度 2,上提 2 次 → 7 到根,6 与 5 成链挂左。 - 访问 6:深度 1;上提 1 次。 - 访问 5:深度 2;…… - 依次代价: → 总 ( 时数据较小,,吻合)。
  3. (b) zig-zig: - 访问 7:深度 2 → 2 次旋转把 7 提到根,同时 2、6 被摊平。 - 后续代价显著下降。 - 总代价 。
  4. 结论: 越大差距越大( 时 32895 vs 1096)。

6. 易错点 + 老师原话

  • 易错 ★★:用「插入 1..N 单旋得链」当反例。这个反例不成立(见上):两种方案插入结果相同。考试若这样写,会被扣分。
  • 易错 ★★:只访问一次就说「链还是链」。单个访问后目标已在根,链被劈开。
  • 易错 ★:以为「反复访问同一节点」就能退化。恰恰相反——splay 会把热点节点长期留在根附近,反复访问同一节点是 splay 的最好情形。
  • 老师原话:

「我 k1 跟 k2 旋一下,k1 跟 k3 再旋一下,k1 跟 c4 旋一下,然后一点一点的旋上去,k1 变成根节点了停住……如果下一次找 k3?就变差了,原来 k3 在很靠上的地方,结果你这一转,把 k3 给 push 到很深的下面去了。这好像不太行。」[稿:281]

「单选是不 work 的?单选是不行的,那怎么行?那得靠双旋吧,靠双旋。」[稿:284]

「我要不断的在 find1 find2 find3 的话,那整体时间复杂度还有 N log N 吗?没有了,它就变成 N 平方了。」[稿:284]

7. 考试英文答题模板

A naked single rotation (lifting the accessed node one level at a time) is not sufficient. After such a rotation the accessed node reaches the root, but all other nodes on the access path remain at essentially the same depth, so the path is merely re-rooted, not shortened. Consequently, a sequence that repeatedly accesses keys from the deepest end of a long path costs per operation and in total (measured: , ). With zig-zig / zig-zag, each step performs two rotations and roughly halves the length of the access path, giving for the same sequence (measured: , ). Hence the double-rotation rule is precisely what makes the amortized bound attainable.


1.1.9 摊还分析三法 【S 级|证明题】

1. 一句话本质

三种算法:聚合算总账再除以 ;记账给便宜操作预收「信用」、贵操作花掉;势能把信用写成状态函数,用差分算。

2. 定义与性质

目标(PPT [PPT:1上:445-447]):

Target : Any M consecutive operations take at most O(M log N) time.
         -- Amortized time bound

三种界的强弱(PPT [PPT:1上:450-453]):

worst-case bound  ⊇  amortized bound  ⊇  average-case bound
                    Probability is not involved

方法一:聚合分析(aggregate analysis)

思想(PPT [PPT:1上:462-475]):不区分操作类型,直接证明 个操作任意组合的总代价 ,再取 。

公式:

适用:代价结构简单、能直接算总账(如 MultiPop 栈)。

方法二:记账法(accounting method)

思想(PPT [PPT:1上:491-509]):给每个操作类型 指定摊还代价 ,使其满足

不变式:任意前缀上,累计信用 :

要点:不同操作的摊还代价可以不同(PPT [PPT:1上:520-522];[稿:341]:「amortized costs for the operations may differ from each other」)。

方法三:势能法(potential method)

思想(PPT [PPT:1上:536-556]):把「信用」写成数据结构状态的函数 ,定义

求和望远镜相消:

要求:

  1. 对所有 (通常 且 ),才能保证 ;
  2. 应在初始状态取最小值(PPT [PPT:1上:554-556]:a good potential function should always assume its minimum at the start of the sequence)。

三种方法的比较:

方法 难点 灵活性 能否区分操作类型
聚合 必须能精确算出总账 低 不能
记账 靠直觉分配 中 能
势能 需构造 ,但最通用 高 能

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《均摊复杂度》 · https://oi-wiki.org/basic/amortized-analysis/ OI Wiki 的定位是:均摊分析(amortized analysis)不只关注单次成本,而是通过评估一系列操作的成本给出整体性能,「不涉及概率,且只能确保最坏情况性能的每次操作耗费的平均时间,并不能确认系统的平均性能」。形式化写法:设状态序列 ,第 次操作的实际代价 ,势函数 ,则第 次的均摊成本

合法性的充要条件是对一切可达状态 有 ,即 在初态取最小值。OI Wiki 明确写 「实际上是不必要的」——因为 只依赖差分,给 整体加一个常数不改变任何 ,所以总可以平移使初值为 。这就是两套口径能同时成立的原因。

2.2 OI Wiki 校订|基本性质

  1. 三法可互推:给定任意满足前缀不变式的 序列,令 就得到一个合法势函数;反之势能法给出的一族 也能摊到具体对象上。聚合与记账的差别只是「信用写在谁名下」。
  2. 聚合蕴含记账:若已证任意序列总代价 ,取常数 即满足每一个前缀。
  3. 界的强弱:,中间那一层不需要分布。
  4. 动态数组的例子:容量倍增时总复制成本 ,故总代价 、摊还 ;同一结论可由 或 得到。
  5. 上界方向:只要对每个 有 ,则 ,而这一步用到的是「初值最小」,不是「非负」。

2.3 OI Wiki 校订|与课堂口径的差异

两套要求不等价但相容:OI Wiki 只要求 ,并举例说明初值可以取负——动态数组取 ,空表容量 1 时 ,可达状态里 ,于是 就是最小值。PPT 要求 且 ,这是同一条件下的规范形式(整体平移 后 ,所有 不变)。考试按 PPT 写「、初值取最小」,若被问「 是否必要」应答不必要,并给出上述平移论证或动态数组反例。例题也不同:OI Wiki 用 vector 扩容,PPT 用 MultiPop,后者的势函数 同时满足两套要求。

3. 图示

聚合:┌────────────────────────────┐│n个操作,不分类型││总代价T(n)=...│└────────────┬───────────────┘↓÷namortized=T(n)/n记账:便宜操作贵操作c=1,ĉ=2→存1块→c=N,ĉ=0→花掉N−1块└────信用池(≥0不变式)────┘势能:D₀──op₁──►D₁──op₂──►D₂──op₃──►D₃Φ(D₀)Φ(D₁)Φ(D₂)Φ(D₃)ĉ₁=c₁+Φ(D₁)−Φ(D₀)ĉ₂=c₂+Φ(D₂)−Φ(D₁)←望远镜相消────────────────────────Σĉ=Σc+Φ(D₃)−Φ(D₀)└──≥0──┘(若Φ≥Φ₀)

4. 复杂度表(方法本身的代价)

方法 证明长度 常见误用
聚合 短 算不出精确总账时硬凑
记账 中 信用变负(不满足不变式)
势能 长 取了负值或初值不是最小值

5. 典型例题(MultiPop,三法齐做)

问题:栈支持 Push、Pop、MultiPop(k)(弹出 个元素)。连续 次操作,从空栈开始。求摊还代价。

(a) 聚合:每个元素最多被弹出一次([PPT:1上:476-477];[稿:320]:「Push 一次才能 pop 有效的 pop 一次」)。

(b) 记账:ĉ(Push)=2, ĉ(Pop)=0, ĉ(MultiPop)=0(PPT [PPT:1上:517])。

  • 每次 Push 存 1 个 credit(贴在被压入的那个元素上);
  • 每次 Pop / MultiPop 花 1 个 credit / 被弹元素;
  • 每个元素最多被 Push 一次、被 Pop 一次 → 信用永不为负。
  • 。✓

(c) 势能:(PPT [PPT:1上:562])。

操作
Push 1 2
Pop 1 0
MultiPop() 0

( 是实际弹出个数。)验证:、 ✓。故 。

三法结论一致:。

6. 易错点 + 老师原话

  • 易错 ★:把摊还代价写成「平均值」。摊还不涉及概率(PPT [PPT:1上:452-453])。
  • 易错:势能法忘记验证 是最小值(导致取反方向)。
  • 易错:记账法允许信用在中途变负(那等价于用了未来的钱,不成立)。
  • 老师原话:

「aggregate 我们翻译成叫聚合分析……我眉毛胡子一把抓,我不知道这 M 个操作系统操作是什么,但是,我可以算总和。我算总账,我有一种方法能把总账算出来,总账算出来以后除以 M 我就得到了这个 amortized 这个上界。」[稿:311]

「Accounting 就是银行柜台算账,你存钱,他把整个事情把所有的操作当成有便宜的操作,有贵的操作。」[稿:311]

「当你有用到便宜的操作的时候,你给自己攒点钱,多出来钱,你攒着贵的操作出现的时候,你就把那钱花出去。」[稿:311]

「a good potential function should always assume it's minimum at the start of sequence。」[稿:347]

「一个好的 potential function……最好是大于等于零的,因为这是个上界,你变成负的话,你说明你定义错了。」[稿:347]

7. 考试英文答题模板

Aggregate analysis: show that any sequence of operations takes in total in the worst case; the amortized cost per operation is . Accounting method: assign each operation an amortized cost such that is credited to specific objects, and prove for every prefix (no object's credit ever becomes negative); then bounds the amortized cost. Potential method: define with and set ; telescoping gives . All three establish the same amortized bound and none uses a probability assumption.


1.1.10 势能法 证明 splay 摊还 【A 级|证明题】

1. 一句话本质

把每个节点的「子树大小」取 log 当存款;zig-zig 每步要让势能增加不超过 倍的 rank 差,而 rank 最大到 ,加总就是 。

2. 定义与性质

PPT 原文 [PPT:1上:581-590]:

〖 Example 〗 Splay Trees: Tamortized = O( log N )
Di = the root of the resulting tree
Φ( Di ) = must increase by at most O( log N ) over n steps,
          AND will also cancel out the number of rotations
          (zig:1; zig-zag:2; zig-zig:2).
Φ(T) = Σ_{i∈T} log S(i),  where S(i) is the number of descendants of i (i included).
              Rank of the subtree ≈ Height of the tree

定义:

  • = 以 为根的子树节点数(含 自己);
  • ,称为 的 rank(秩);
  • 。

为什么不用 height(PPT [PPT:1上:593-594] 的设问 / [稿:290]):height 在旋转前后的变化不连续、不可控(旋转可能让 height 掉 2 或不变,无法统一界住);而 单调、且与「子树里有多少节点被搬动」直接对应。老师原话:

「Why not simply use the heights of the trees?」[PPT:1上:593-594]

关键结论(PPT 【Theorem】[PPT:1上:625-626]):

The amortized time to splay a tree with root T at node X
is at most  3( R(T) − R(X) ) + 1  = O( log N ).

代入 、:

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Splay 树》 · https://oi-wiki.org/ds/splay/ OI Wiki 的证明框架:单个节点的势能 ,其中 是以 为根的子树大小;整棵树的势能 ,初始势能满足 ;第 次操作的均摊成本 , 为实际代价。三条前提性质是证明的骨架:(1) 是 的父亲则 ;(2)根节点的子树大小在操作前后不变,故根的势能不变;(3)若 ,则 。

2.2 OI Wiki 校订|基本性质

  1. 性质 3 的证明:由均值不等式

    例:、 时左端为 ,恰好取等;这解释了下界为什么是 而不是 。

  2. zig 的界: 且 ,故 ,常数 来自那一次旋转。

  3. zig-zig 的界:,且由四棵子树 的计数

    (严格大于是因为左端多计了 自己),代性质 3 得 。

  4. zig-zag 的界:,故 ,比 zig-zig 更松的要求。

  5. 单次伸展:; 次总实际代价 ,即 。
  6. 数值核对:取 ,,即一次伸展的摊还代价不超过 31 次旋转,而单次实际代价可以到 1024 层。差两个数量级的原因是势能项的望远镜抵消:中间步的 全部相消,只留首末两项,故界与路径长度无关,只与 的 rank 增量有关。

2.3 OI Wiki 校订|与课堂口径的差异

初始势能一项不同。OI Wiki 从一棵已有 个节点的树出发,只知 ,于是总代价保留 ,结论写成 ;PPT 的目标是「从空树开始的任意 次连续操作」,空树没有节点、,于是界收紧为 。两者不矛盾,是同一不等式在不同初值下的取值,考试按 PPT 的空树前提写。底数也不同:OI Wiki 的 不写底数(换底只差常数因子),PPT 固定 以便把常数 写实;只要全文一致即可。式子的形式完全对应:PPT 的 就是 OI Wiki 的 。

3. 图示(三种步型的势能变化)

zig(P是根)zig-zag(异向)PXGX/\/\/\/\AX──►PC◄──AP──►PG/\/\/\/\/\BCABXDABCD/\BCĉ≤1+R₂(X)−R₁(X)ĉ≤2(R₂(X)−R₁(X))zig-zig(同向)旋转前后子树关系:GX所有节点都留在X的新子树里/\/\⇒只有X,P,G的rank变化AP──►PD/\/\R(X)只增不减⇒Δ可被"最大"项吸收XDGB/\/\BCACĉ≤3(R₂(X)−R₁(X))

4. 复杂度表(每次 splay 的旋转次数与摊还代价)

步型 旋转次数 实际代价 摊还代价 上界
zig 1 1
zig-zag 2 2
zig-zig 2 2
整次 splay
次操作 — —

5. 典型例题(完整推导,逐步给出「为什么」)

Prove that the amortized cost of a splay is using with .

Step 1|总摊还代价的望远镜结构

一次 splay 由若干步组成,第 步的势能函数记 (, 为步数)。则

即只需逐个界住每一步的 。

Step 2|zig( 是根)

  • 实际代价 (1 次旋转)。
  • 旋转前后只有 与 的 rank 变化( 三棵子树的根不变,且它们的子树内容不变)。
  • ( 的新子树包含原 的整棵子树) → ( 严格增)。
  • ,故 ,可以丢掉:

Step 3|zig-zag(异向双旋)

  • 。
  • 旋转前有三个相关节点 ;旋转后 都成为 的孩子。
  • 两个减号项可以合并:由 (凹性,见 Step 5):

  • 更精细的界可用 时 ()进一步压到 。

Step 4|zig-zig(同向双旋,最难)

  • 。
  • 旋转前 在上、 中、 下;旋转后 上、 与 分列两侧。
  • 注意 ( 占了 原来的位置,子树范围相同)→ ,这一等式是把 倍系数精确压出来的关键。
  • 展开:

  • 抵消(由 ):

  • 再用凹性放缩:,故

  • 最终化简得

Step 5|所用不等式(凹性)

对 :

(几何平均 ≤ 算术平均)取 得
这就是把「两个 rank 的和」压成「一个 rank」的工具。

Step 6|求和

  • zig 至多 1 次(只发生在最后一步);
  • 每个 zig-zig / zig-zag 贡献 ;
  • rank 是望远镜求和:,中间项全部相消。

故

Step 7|为什么这是

  • ( 是根,子树大小 );
  • ;
  • 故每次 splay 摊还 , 次操作总计 。∎

关键:为什么 初值最小

根在初始时是「某个」节点;splay 结束后 可能上升( 的子树变大)。但我们要的是 :

单次 splay 中 不一定单调——但在最终求和里, 的初值必须是「最小值」,这样 。对空树开始(所有 ,)成立。

6. 易错点 + 老师原话

  • 易错 ★★:把 写成 。这是 size,不是 size 的对数——用 size 得到的界是 而非 。
  • 易错 ★:漏掉 zig-zig 中 这一步,导致系数写成 而非 (结论仍 ,但常数非最优)。
  • 易错 ★:忘记「zig 至多 1 次」,使 变成 的加法项——虽然结论不变,但推导不严谨。
  • 易错: 用 的底数写成 2 但 用自然对数,导致常数不自洽。底数一致即可(底数只差常数因子)。
  • 老师原话(PPT 已给定理,老师省略了推导):

「splay tree 到底它的 amortize 的时间是不是 M log N,具体的证明……没时间,大家自己去 B 站上看,那 B 站上视频里头有很详细的证明过程。」[稿:299-302]

「理论证明还是有点烦的。」[稿:290]

这说明考试若要证明,必须自己写完整——老师不会在课上补。

7. 考试英文答题模板

Let be the number of nodes in the subtree rooted at and ; take . For a zig, and . For a zig-zag, and . For a zig-zig, and, since , the -term cancels, giving . Summing over all steps telescopes to

Hence a splay costs amortized, and operations cost .


1.1.11 MultiPop 栈的摊还分析 【A 级】

1. 一句话本质

MultiPop(k) 单次最坏 ,但每个元素一辈子只被弹出一次,所以总量被 Push 次数卡死: 次操作总代价 ,摊还 。

2. 定义与性质

算法(PPT [PPT:1上:478-484]):

Algorithm { 
while ( !IsEmpty(S) && k>0 ) { 
   Pop(S); 
   k - -; 
} /* end while-loop */ 
}
  • 实际代价:;
  • 单次最坏: 且栈满 → ;
  • 摊还:。

核心论证:每个对象入栈后最多被弹出一次([PPT:1上:476-477])。所以

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《均摊复杂度》 · https://oi-wiki.org/basic/amortized-analysis/ OI Wiki 的堆栈模型写得更严:栈 从空开始,支持 push(x)(代价 )、pop()(代价 ,仅用于非空栈)、multi-pop(k)(最多弹出栈顶 个元素,代价 ,含一次调用的常数检查)。问题形式化为:对任意长度 的操作序列,给出 的上界,再除以 得均摊成本。三条边界的意义:「从空栈开始」使势能初值可取到最小值;「pop 仅用于非空栈」把空栈情形排除在定义外,避免负代价;「最多 个」使单次代价受 约束,而不是常数 。

2.2 OI Wiki 校订|基本性质

  1. 一次性配额(one-shot quota):每个元素入栈一次、出栈至多一次,故任意前缀内被弹出的元素数不超过 push 次数,。
  2. 聚合:,摊还 。
  3. 记账:、。1 单位付当下,另 1 单位贴在被压入的元素上;不变式是「任意前缀的累计信用 = 栈内元素数 」。
  4. 势能:。 且非负,故同时满足两套口径; 分别为 ,得 。
  5. 对抗序列的手算: 次 push 后用 次 multi-pop(n),总代价 ,与 同阶,说明 这个界是紧的常数倍。

2.3 OI Wiki 校订|与课堂口径的差异

代价口径差在「是否计入调用开销」。OI Wiki 把 multi-pop(k) 写成 并明说「即使另计每次调用的常数检查开销,总成本仍为 」,于是它给「按元素计费 、计入调用则 」两种写法;PPT 的表直接写 ,只数弹出次数。考试若写 ,须补一句「不计调用本身的常数」。空栈约定也不同:OI Wiki 规定 pop 只用于非空栈,PPT 的 MultiPop 自带 !IsEmpty 保护,空栈调用仍付 ,所以 1.1.11 例题里末尾那些空弹不是零代价。结论(摊还 )两处一致,术语「均摊」与「摊还」同指。

3. 图示

Push  ●  ← 每次 push 一个元素,最多被 pop 一次
Push  ●
Push  ●  ← 每个元素的"一生":push 一次、pop 一次
       │
       └─ MultiPop(3) 弹出 3 个:消耗 3 个元素的"一次性配额"

累计配额:n 次 push → 最多 n 次有效 pop
         ⇒ 总代价 ≤ 2n ⇒ 摊还 = 2 = O(1)

4. 复杂度表(严格三列)

操作 平均 最坏 摊还
Push (ĉ=2)
Pop (ĉ=0)
MultiPop() — (ĉ=0)
次操作总计 —

⚠ 最坏一列的 是「每次 MultiPop 都弹 个」的假设在总量层面的上界,实际上不可能:因为必须先 Push 才能 Pop。老师的原话:

「一个很宽泛的上界是 N 平方……上界太烂了,就太大了……Multipop 能够执行 N 次。它堆栈里面一定已经有 N 个元素在了……没有 push 哪来的 pop?」[稿:317-320]

5. 典型例题

A stack starts empty. It receives operations from {Push, Pop, MultiPop(k)}. Show .

中文逐步( Push 后接 次 MultiPop 的对抗序列):

  1. 前 次全 Push → 栈中 个元素,代价 。
  2. 第 次 MultiPop(N) → 弹空 个,代价 。
  3. 余下 次 MultiPop(N) → 栈空,每次 检查即返回,代价 。
  4. 总代价:。
  5. 若用「每次最坏都是 」估 → 得 ,这是过松的上界。
  6. 摊还:。

6. 易错点 + 老师原话

  • 易错 ★:写「MultiPop 摊还 」。它摊还 ,因为弹出的元素已被 push 过。
  • 易错:把「总代价 」写成「每次 」。单次仍可以是 。
  • 易错:记账法给 Pop 也分配 ĉ=1(这样总账是 ,结论对但非最小;PPT 给的 ĉ=0 才是标准答案)。
  • 老师原话:见 1.1.9 的引用 [稿:311-341];核心一句:

「Push 一次才能 pop 有效的 pop 一次,否则,pop 不会执行。」[稿:320]

7. 考试英文答题模板

For the stack with Push, Pop, and MultiPop(k), each element that has been pushed can be popped at most once, so over any sequence of operations the total number of pops is at most . Hence the total cost is at most and the amortized cost per operation is , even though a single MultiPop may cost in the worst case. Equivalently, with , , or with , the same bound follows.


1.1.12 摊还 ≠ 平均 【S 级|判断题高频陷阱】

1. 一句话本质

摊还是「最坏序列的总账 ÷ 操作数」,是确定性保证;平均是「假设输入服从某分布后的期望」,带概率。前者更强。

2. 定义与性质

三种界的包含关系(PPT [PPT:1上:450-453];[稿:305-308]):

  • :若每个操作都 ,则任意序列总额 ,除 仍是 。AVL 就是这种情况。
  • :Splay 单次可 ,摊还却是 。
  • :若对所有最坏序列都能证摊还 ,则对任意输入分布(其期望是这些序列的加权平均)也 。
  • :平均只对特定分布成立;换个分布就可能不成立。

用「最坏」的语言重述(这是把两个概念焊死的最好方式):

「摊还」的中文翻译争议:PPT 与老师都用「摊还」(也有译「摊销」「分摊」,老师课上口误成「瘫痪」)。关键是理解成「好运气攒着,坏运气时花掉」[稿:266]。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《均摊复杂度》 · https://oi-wiki.org/basic/amortized-analysis/ OI Wiki 用一个句子把两者分开:均摊分析「不涉及概率,且只能确保最坏情况性能的每次操作耗费的平均时间,并不能确认系统的平均性能」。形式化后差别全在量词顺序。记 为结构规模、 为长度 的操作序列、 为第 次操作的实际代价,则

  • 摊还上界 :,序列由对手挑选, 与分布无关;
  • 平均上界 具体序列(按 抽样):,分布 必须先给定。

前者是「对最坏序列的除法」,后者是「对某个分布的期望」;前者强,因为后者要对每个 都成立才能由前者推出。

2.2 OI Wiki 校订|基本性质

  1. 无概率假设:摊还界是确定性命题,不含随机变量,也不假设输入分布。
  2. 只保总量:单项可超过 ,只要同序列中其余项便宜到把账补回。
  3. worst amortized:若每步 ,逐项相加得 ,再除 。AVL 属于这种情形。
  4. amortized average:对任意分布 ,,故摊还界对一切分布都成立。
  5. average amortized:平均界只对某一个 成立,换分布即失效,而摊还要求对全部序列成立。
  6. 具体反例:在长链上访问最深键单次 ,但 次操作总额仍 ——这正是「摊还成立、最坏不成立」的实例。

2.3 OI Wiki 校订|与课堂口径的差异

术语不同:OI Wiki 用「均摊(复杂度)」,PPT 用「摊还」,另有「摊销」「分摊」等译法,指同一概念。措辞上有一个坑:OI Wiki 说「评估一系列操作的平均成本」,字面容易被读成 average-case,但同一页紧接着声明「不涉及概率」,可见此「平均」是算术平均而非期望——与老师说的「真平均 / 带概率的假平均」是同一点。三个界的包含关系 OI Wiki 未写成包含式,那是 PPT 的表述,答题时写

并各配一例(AVL / Splay / 随机输入下的普通 BST)即可。评分依据仍是 PPT 的口径:摊还 平均,probability is not involved。

3. 图示

三种界的包含关系(越靠内越强)┌───────────────────────────────────────────────────┐│worst-case(最坏保证,逐操作)││┌─────────────────────────────────────────────┐│││amortized(摊还,序列总账/操作数)││││┌───────────────────────────────────────┐│││││average-case(平均,需概率假设)││││││P(输入=x)必须先给定│││││└───────────────────────────────────────┘│││└─────────────────────────────────────────────┘│└───────────────────────────────────────────────────┘摊还=对所有序列取max⇒与分布无关平均=对某个分布取E⇒依赖分布

老师课上用的「收入」比喻([稿:266]):

好年景(便宜操作)→ 攒钱
坏年景(贵的操作)→ 花存款
一生平均生活水平稳定 = amortized

4. 复杂度表(三者对比)

维度 Worst-case Amortized Average-case
定义对象 单个操作 次操作的总和 单次操作在分布 下的期望
是否需概率假设 否 否 是
强弱 最强 中 最弱
AVL —
Splay (单次) 不做
MultiPop (单次) —

5. 典型例题

True or False: "The amortized cost of an operation in a splay tree is ; therefore every single operation takes time." Answer: False.

中文逐步:

  1. 摊还界只说「 次操作总额 」。
  2. 允许某一次花 ,只要其他次便宜到把账补回来。
  3. 反例:在链 1..N 上访问 1,单次就 。
  4. 所以从摊还界不能推出最坏界。正确说法是:Splay 单次最坏 ,摊还 。
  5. 反过来,若已知 worst-case (如 AVL),则可推出 amortized 。

6. 易错点 + 老师原话

  • 易错 ★★★(最高频判断题):把「摊销(amortized)」当「平均(average)」。排除。
  • 易错 ★:认为「摊还意味着输入随机」。恰恰相反——probability is not involved。
  • 易错:把「摊还 」当成「每次 」。
  • 老师原话:

「amortize time 是什么?是真平均?我就是把 M 个时间复杂度全部都加起来,除以 m。」[稿:260]

「这是个真平均,不是带概率的假平均,但是,因为平均这个词已经被带概率的假平均给用掉了,我们在这就换了一个词叫 amortize。」[稿:263]

「Average case 是带了概率假设的……amortize 也是一种平均时间,平均时间是绝对平均,他是完全没有任何概率假设的。」[稿:305-308]

「Worst case 肯定是强于 amortize……如果 worst case 对的话,amortize 肯定对。」[稿:305]

「强就强在这个 probability is not involved 没有概率假设的问题。」[稿:308]

7. 考试英文答题模板

Amortized and average-case are different. An amortized bound averages over the worst possible sequence of operations: means every sequence of operations costs at most in total, with no probability assumption whatsoever. An average-case bound, in contrast, requires a probability distribution over the inputs and bounds the expectation. The correct inclusion is

Hence an amortized bound for splay trees does not imply that every single operation is — a single access may be — nor does it rely on any randomization of the input.


附录 A:本讲全部公式速查

编号 结论 出处
1.1.2 ,空树 [PPT:1上:49-58]
1.1.3 , [PPT:1上:250-260]
1.1.3 , [PPT:1上:265-267]
1.1.4 LL/RR 单旋(1 次);LR/RL 双旋(先子后父,2 次) [PPT:1上:77-193]
1.1.5 插入 次旋转;删除 次 [PPT:1下:161-166]
1.1.6 次操作 ;单次可 [PPT:1上:273-300]
1.1.7 zig 1 次;zig-zig 2 次(先 G);zig-zag 2 次(先 P) [PPT:1上:357-375]
1.1.8 单层上提 vs zig-zig (实测 n=256: 32895 vs 1096) 本册实测
1.1.9 ; [PPT:1上:536-556]
1.1.10 ; [PPT:1上:581-626]
1.1.11 MultiPop 摊还 ; [PPT:1上:478-486]
1.1.12 worst amortized average [PPT:1上:450-453]

附录 B:与「考点总表」的不一致之处(3 点修正)

本册在写作中对 02_考点分析/考点总表.md 与 A0_速查册.md 的表述做了实测校验,发现以下 3 处需要修正。考试若照原文写会被扣分。

修正 1|1.1.8 的「插入 1..N 单旋得链」不成立

  • 原文(A0_速查册.md:51):「单旋上提:访问长链最深处节点后,链仍是链,下次访问同链仍 O(N)」。
  • 实测:用单旋插入 1..N,被 splay 的永远是刚插入的叶子,只触发 zig(单层上提与 zig-zig 在「目标是叶子」时重合),所以两种方案插入后都得到左链。这不是区分点。
  • 修正:真正的区分必须让「 与父同向两层」这个条件成立——即从深端开始访问已存在的长链。

修正 2|「访问一次后链仍然是链」不成立

  • 原文(A0_速查册.md:51):「访问长链最深处节点后,链仍然是链」。
  • 实测:在左链 1←2←…←7 上访问 7(单旋一次),结果 7 到根,链被劈成 7 ← 1 ← 2 ← … ← 6——链形式上还在,但 7 已在根,且下一次访问的深度并不等于 。
  • 修正:单次访问不足以说明问题。必须反复从深端访问才能累计出 。

修正 3|1.1.8 的量化结论需要补上「深端 + 反复」两个前提

  • 原文(02_考点分析/考点总表.md:31):「为何必须双旋:单旋退化为 O(N)」。
  • 更准确的说法:单次操作不退化(单次都是 ,与 splay 一样);退化发生在序列层面——从深端开始的 次访问累计 ,而 zig-zig 是 。实测数据(平衡树,序列 ):
单层上提 zig-zig
64 2079 256
128 8255 534
256 32895 1096
  • 修正:考试中应写「使用单层上提(naive rotation)会让从深端反复访问的序列累计到 ;zig-zig 每轮路径折半,故 」。

补充:1.1.7 的「zig-zig ≠ 两次单旋」需要更精确的表述

  • 原文(A0_速查册.md:48):「zig-zig 不是连续两次单旋(那叫『单旋上提』),必须先转祖父」。
  • 问题:从最终树形看,zig-zig(先 G 后 P)与「先 P 后 G」恰好得到同样的三个节点、四棵子树布局。真正被否掉的是每一步只上提一层的做法,而不是「两次以 为目标的上提」。
  • 修正:准确的对比维度是「路径长度变化」:zig-zig 一步走 2 层、路径折半;单层上提一步走 1 层、路径几乎不变。终态同形、中间态不同,不能作为判据。
1.2.1 – 1.2.14

详解 02 · 红黑树与 B+ 树

对应考点:1.2.1 – 1.2.14(共 14 个)。术语一律「中文(English)」双写,考试作答以 English 列为准。 证据约定:[PPT:1下:N] = 01_原始素材/PPT_文本/ADS/第一讲(下)Red-Black Trees and B+ Trees.txt 行号;[稿:1-逐字稿:N] = 课堂录音文稿/1-逐字稿.md 行号。 本文所有图示的树均用脚本复算过 bh 与红−红约束;PPT slide 4 的插入走查、10,5,15,3,7,11,17,6,8、41,38,31,12,19,8、M=3 的 18,1,19,28,70 与 M=5 的分裂均逐步复算通过。


0. 本讲地图

Red-BlackTree(弱平衡BST,h≤2log₂(N+1))├─1.2.1五条性质←定义"什么叫红黑树"├─1.2.2black-height←黑高,平衡的度量├─1.2.3size(x)≥2^bh(x)−1←引理(归纳)├─1.2.4h≤2log₂(N+1)←引理+性质(4)⇒树高上界├─1.2.5插入Case1/2/3←目标:不改变黑高├─1.2.6口诀swapcolorsandrotate├─1.2.7删除≤3次旋转├─1.2.8RBvsAVL←绝对优势在deletion└─1.2.9不做删除时的取舍B+Tree(绝对平衡多叉树,磁盘I/O导向)├─1.2.10M阶结构├─1.2.11键数界⌈M/2⌉−1~M−1├─1.2.12插入分裂与树高增长└─1.2.13磁盘I/O权衡横切:1.2.14为何NIL是黑的

1.2.1 红黑树五条性质 【S 级】

1. 一句话本质

红黑树还是 BST,只是换了个"平衡"的定义——不盯左右子树高度差,盯每条路径上的黑节点个数。老师叫它"黑高平衡":AVL 是纯高度平衡(两棵子树高度差不能差一),红黑树是用黑节点个数来控制平衡,而且一点都不能差。

2. 定义与性质

【Definition】[PPT:1下:15-22]

A red-black tree is a binary search tree that satisfies the following red-black properties: (1) Every node is either red or black. (2) The root is black. (3) Every leaf (NIL) is black. (4) If a node is red, then both its children are black. (5) For each node, all simple paths from the node to descendant leaves contain the same number of black nodes.

# 英文原文 中文 违反后的症状
(1) Every node is either red or black 每节点非红即黑(实现上 1 bit 足够) 颜色无法表示
(2) The root is black 根必黑 插入变色可能把根染红
(3) Every leaf (NIL) is black 每个空指针(NIL)是黑 见 1.2.14
(4) If a node is red, then both its children are black 红节点的两个孩子都黑(禁止红−红相连) 插入唯一的报错源
(5) All simple paths contain the same number of black nodes 黑高相等(黑高平衡) 删除唯一需要修复的原因

节点结构(PPT 右上角框):parent | color | key | left | right,NULL = NIL。

最关键的一处术语区分:

  • 内部节点(Internal node):存 key 的实节点,可以是红的;
  • 外部节点(External node)= 叶子(leaf)= NIL:空指针,必黑。

性质 (3) 中的 "leaf" 专指 NIL,不是"没有孩子的实节点"。这一点在 1.2.14 会展开,也是判断题的高危点。

形式化写法(设 为以 为根的子树高度,空树取 或 按教材约定):

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 把红黑树(red-black tree)定义为一棵每个结点额外存一个颜色字段(RED 或 BLACK)的二叉搜索树,并要求四条性质同时成立:每个结点非红即黑;NIL 结点(空叶子结点)为黑色;红结点的两个子结点都是黑色;从任一结点到每个 NIL 的简单路径上黑结点数目相同。这里的 NIL 是空指针所指的外部结点,不是"没有孩子的实结点";把 NIL 当作实体之后,"到叶子的路径"才有统一终点,最后一条性质才是一个关于有限对象的命题。OI Wiki 明确不把"根为黑"列入必需条件:根由红改黑不改变任何路径的黑结点计数,该操作可以推迟到删除时执行,写进定义反而会干扰递归的修复过程。

2.2 OI Wiki 校订|基本性质

  1. 二色性(two-colouring):每个结点恰为红或黑。为什么成立:颜色是 1 bit 附加信息,二值划分保证红、黑结点数之和等于结点总数,后续所有计数论证才有意义。例:单结点树染黑合法;在 OI 的四条性质下染红也合法,说明根的颜色不是结构约束。
  2. NIL 为黑(black external node):每个空指针指向一个黑外部结点。为什么成立:每个实结点的两个孩子因此都存在,路径终点统一,黑高平衡无需附加例外。例:单黑结点的两条路径各含 1 个黑 NIL,计数相等。
  3. 红结点的孩子必黑(no red-red edge):若 ,则 。为什么成立:它禁止路径上出现相邻红结点,从而红结点数不超过黑结点数,最长路径不超过最短路径的二倍。例: 合法;把 1 改成红就出现 ,违反本条。
  4. 黑结点计数相等(black-height balance):任一结点到其所有后代 NIL 的路径含相同数目的黑结点。为什么成立:这是唯一的全局约束,插入染红与删除补黑都在维持它;它使"黑高"成为良定义的结点函数。例:以 7 为根的路径 与 都含两个实黑结点与一个黑 NIL,计数相同。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的四条与 PPT 的五条一一对应:OI 的第 1 条是 PPT 的 (1),OI 的第 2 条是 PPT 的 (3),OI 的第 3 条是 PPT 的 (4),OI 的第 4 条是 PPT 的 (5);PPT 的 (2)"根为黑"在 OI 中没有对应条款。OI 的理由是根色可以随时由红改黑而不影响任何路径的黑计数,因此不必作为合法性条件;PPT 把它列为必需,并在每次插入末尾执行 root.color = BLACK。考试按 PPT 的五条作答并保留最后一步,同时应能说明 OI 省略根黑条款的理由;判定一棵树是否合法时,两种口径只在"根为红、其余四条全满足"这一种树上给出不同答案。

3. 图示

PPT slide 2 给出的标准样例(颜色经 PPT 渲染图逐一核对,内部叶子 5、8 都是黑,14 是黑,1 是黑,4 和 15 是红):

                   7(B)  bh=2
                 /        \
           2(R)             11(R)
          /     \           /     \
      1(B)      5(B)     8(B)     14(B)
                 /                   \
              4(R)                   15(R)
        /  \   /  \   /  \   /  \
      NIL NIL NIL NIL NIL NIL NIL NIL      ← 全部是黑的外部节点

逐条验证这棵树:

  • (2) 根 7 是黑的 ✔
  • (3) 所有 NIL 是黑的 ✔(图中不画出,但逻辑上每个空孩子都是一个黑 NIL)
  • (4) 红节点 2 的孩子 1、5 都是黑 ✔;红节点 11 的孩子 8、14 都是黑 ✔;红节点 4 无孩子(两个孩子都是黑 NIL)✔;红节点 15 同理 ✔
  • (5) 任取一条根到 NIL 的简单路径,数黑节点:
路径 黑节点(不含 NIL) 含 NIL
7 → 2 → 1 → NIL 7, 1 = 2 3
7 → 2 → 5 → 4 → NIL 7, 5 = 2 3
7 → 2 → 5 → NIL 7, 5 = 2 3
7 → 11 → 8 → NIL 7, 8 = 2 3
7 → 11 → 14 → 15 → NIL 7, 14 = 2 3
7 → 11 → 14 → NIL 7, 14 = 2 3

全部相等 ⇒ 这是合法红黑树。注意最长路径 7-11-14-15 有 4 个节点,最短路径 7-2-1 只有 3 个,肉眼看上去"不平衡",但它满足黑高平衡,且 ✔。

对照 AVL:同样 9 个节点,AVL 会把它调成完全平衡的形状;红黑树允许这种"看起来歪"的形状存在,代价是树高常数从 变成 ,收益是旋转次数大幅下降(1.2.7、1.2.8)。

4. 复杂度表

操作 AVL Tree Red-Black Tree
查找 Search
插入 Insert(时间)
删除 Delete(时间)
插入旋转次数
删除旋转次数
每节点额外空间 平衡因子 BF(1–2 bit) color(1 bit)+ parent 指针
树高上界
是否递归实现 通常递归 可用迭代(循环 + parent 指针)

5. 典型例题

Q. Which of the following trees is NOT a valid red-black tree? Justify each candidate with the five properties.

(A) root 7B; children 2R, 11R; 2R's children 1B, 5B; 5B's left child 4R; 11R's children 8B, 14B; 14B's right child 15R. (B) root 7B; children 2R, 11R; 2R's children 1B, 5B; 5B's children 4R, NIL; 11R's children 8R, 14B.

解(中文逐步)

  • (A):就是上面那张图,逐条检查 5 条性质全部通过,合法。
  • (B):检查性质 (4)——11 是红的,它的左孩子 8 也是红的,出现红−红,违法。修法有两条路:把 8 染黑(但会让 11→8 路径多一个黑,破坏性质 (5)),或对 11 做旋转;正确答案是"不合法,违反 (4)"。

答题要点:判断一棵树是否为红黑树,按 (2)(3)(4)(5) 顺序检查,(1) 几乎不可能违反;其中 (4) 最快能查出问题(找相邻红),(5) 需要数每条路径。

6. 易错点 + 老师原话

"for each node。All simple paths, from the node to descended leaves contain the same number of black nodes. 任何一个节点往下走……上面包含的任何一条路径上的黑节点的个数都必须是一样的,所以这是一种很别致的定义平衡的方式。它叫黑高平衡,我们 avl tree 纯高度平衡……它是每一个子树里面的黑节点个数,不能差,一点都不能差,必须要完全一样。" — [稿:1-逐字稿:389]

"它子树上面每一个子树上面的黑节点也是一样的……但是,你肉眼一看明显就觉得这棵树不是很平衡,你看这一条线才只有3个节点,这一条线有123456个节点差了2倍?……但是,他仍然可以证明,即使差两倍,整体树高还是 log N 的?只不过,它最短的这条路径和最长的一条路径是有可能差两倍的,但是也不会差更多了。" — [稿:1-逐字稿:395]

三条高频错:

  1. 把"叶子"当成"没有孩子的实节点"。性质 (3) 的 leaf 指 NIL;内部叶子(如 5、8、1)可以是黑的也可以是红的,PPT 样例里 4、15 就是红叶子。
  2. 以为红黑树是 AVL 的特例。二者是并列的两种平衡策略,AVL 严格()、RB 弱平衡(),互不包含。
  3. 只查 (4) 不查 (5)。很多树没有红−红,但黑高不等,仍不合法。

7. 考试英文答题模板

A red-black tree is a binary search tree satisfying: (1) every node is red or black; (2) the root is black; (3) every leaf (NIL) is black; (4) if a node is red, both its children are black; (5) for each node, all simple paths from it to descendant leaves contain the same number of black nodes. Property (5) is a black-height balance condition, which is weaker than the height-balance condition of an AVL tree; consequently a red-black tree may have paths differing in length by a factor of two, yet its height is still .


1.2.2 black-height 的定义与计算 【A 级】

1. 一句话本质

黑高就是"从这个节点往下走到 NIL,路上碰到几个黑节点";起点自己不算,NIL 算。整棵树的黑高 = 根的黑高。

2. 定义与性质

【Definition】[PPT:1下:31-33]

The black-height of any node , denoted by , is the number of black nodes on any simple path from ( not included) down to a leaf. .

中文拆解:

  • "on any simple path"——由性质 (5) 保证任意一条路径的这一计数都一样,定义才良定义;
  • " not included"——起点不算。老师专门强调了这一条;
  • "down to a leaf"——走到 NIL 为止,NIL 是黑的。

唯一需要背的递推式(考试用它算,比一步步走路径快且不出错):

即:NIL 的黑高是 0;走到 NIL 的那一步贡献 1(因为 NIL 必黑);其后每遇到一个黑孩子再加 1。

等价的一句口头版:红节点的 = 孩子的 ;黑节点的 = 孩子的 + 1(因为黑孩子比红孩子多贡献 1)。

关键推论(1.2.3、1.2.4 要用):

口径说明:不同教材对"NIL 是否计入"有 ±1 的约定差异(CLRS 的 不含 NIL 的计数,PPT 的 含)。本文一律采用上表的递推式,它是唯一同时满足 PPT 的 、 和老师口述" 不含起点"三个约束的版本。考试时按递推式计算并写明定义即可,不要只写一个数字。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki《红黑树》没有给 black-height(黑高)单独下定义,只在第 4 条性质中要求"从根到 NIL 的每条路径黑结点数相同";《左偏红黑树》页把这条要求称为黑高平衡(black-height balance),同样没有给出计算公式。因此形式定义只能取教材口径:对结点 ,记 为从 出发到其后代 NIL 的所有简单路径,性质要求每条路径上的黑结点数相同,这个公共值记为 ;计数不含起点 ,含终点的 NIL,且约定 。写成递推式是 ,其中 是 的任一孩子。定义良定义的前提正是"任一"路径计数相同。

2.2 OI Wiki 校订|基本性质

  1. 良定义(well-defined): 与所选路径无关。为什么成立:OI 第 4 条性质直接断言所有路径计数相同。例:结点 5 的左路径 与右路径 都只计 1 个黑 NIL,故 。
  2. 递推(recurrence):。为什么成立:从 到 NIL 的路径先去孩子 , 自身对计数贡献 1 当且仅当它为黑。例:,。
  3. 单调性(monotonicity):。为什么成立: 只有黑、红两种颜色,黑则 ,红则 。例:红结点 2 的孩子 1 是黑,;若孩子为红则两者相等。
  4. 与树高的夹逼(height bound):,且 。为什么成立:前者因每条路径上实结点数不少于黑结点数;后者因红结点不能相邻,最长路径上黑结点至少占一半。例:1.2.1 的样例树 、,满足 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 不给 定义,因此不存在"OI 与 PPT 的 "可直接对照;差异出现在教材之间。约定 A 把终点 NIL 计入、起点不计,得 、实结点 ;约定 B 连终点 NIL 也不计,得 ,且 无定义。两式换算为 。以 1.2.1 的根 7 为例:约定 A 数到黑 NIL 得 ,约定 B 只数实黑结点得 ,。PPT 的引理 与归纳基 只在约定 A 下成立,本文与考试均用 A;答题时必须写出"不含起点、含 NIL"这一定义句,只写数字会被判口径不明。

3. 图示

用 1.2.1 的样例树,把每个节点的 按上面的递推式标出来:

                    7(B) [bh=2]
                  /              \
         2(R) [bh=2]              11(R) [bh=2]
         /          \             /            \
   1(B)[bh=1]   5(B)[bh=1]   8(B)[bh=1]    14(B)[bh=1]
                    /                            \
              4(R)[bh=1]                     15(R)[bh=1]
                 /     \                      /      \
              NIL      NIL                 NIL       NIL
             [bh=0]   [bh=0]              [bh=0]    [bh=0]

逐节点核对(每一格都按递推式算过):

节点 颜色 孩子(左 / 右) 递推
7 B 2(R) / 11(R) 2
2 R 1(B) / 5(B) 2
11 R 8(B) / 14(B) 2
1 B NIL / NIL 1
5 B 4(R) / NIL (4 是红不加;右孩子 NIL 也得相同值,由性质 5 保证) 1
8 B NIL / NIL 1
14 B NIL / 15(R) (走左孩子 NIL) 1
4 R NIL / NIL 1
15 R NIL / NIL 1
NIL B — 定义 0

验证一致性(必须能过,否则树是错的):

  1. 性质 (5) 的等价形式:对每个节点,"走左孩子"与"走右孩子"算出的 必须相等。 - 节点 5:左 ;右 ✔ - 节点 14:左 ;右 ✔ - 节点 7:左 ;右 ✔
  2. 引理下界: ✔; ✔; ✔(红单节点恰好取等)。

记忆钩子:红节点 不变,黑节点 加一;走到 NIL 那一步永远加一。

4. 复杂度表

量 公式 / 结论 样例树
定义 (不含)到 NIL 的黑节点数,NIL 计入 —
递推 —
与树高的关系 (1.2.4 证右半边) : ✔
计算耗时 (沿着一条路径走到底)
红节点的 ,孩子 ✔
黑节点的 ,孩子 ✔

5. 典型例题

Q. For the red-black tree below: (a) compute for every internal node; (b) list every node whose black-height differs from its parent's; (c) verify at the root.

Tree: 10B / 5R(3B, 7B(6R, 8R)) / 15B(11R, 17R)

解(中文逐步)——这棵树正是 1.2.5 例题插入序列 10,5,15,3,7,11,17,6,8 的最终结果:

                    10(B)
                  /        \
            5(R)             15(B)
           /    \           /     \
       3(B)    7(B)     11(R)    17(R)
                /  \
             6(R)  8(R)

(a) 自底向上算

  1. NIL 的 。
  2. 叶子层:。
  3. 中间层: - :左孩子 6 是红 ⇒ ;右孩子 8 也是红 ⇒ 的写法是 再加 0 ✔ 一致。 - :左孩子 3 是黑 ⇒ 。 - :左孩子 11 是红 ⇒ 。
  4. 根:左孩子 5 是红 ⇒ 。
节点 颜色
10 B 2
5 R 2
15 B 1
3 B 1
7 B 1
11 R 1
17 R 1
6 R 1
8 R 1

(b) 与父节点 不同的节点

节点 自己的 父节点 父的 是否不同
5 2 10 2 否
15 1 10 2 是
3 1 5 2 是
7 1 5 2 是
11 1 15 1 否
17 1 15 1 否
6 1 7 1 否
8 1 7 1 否

答案:——恰好全是黑节点。这不是巧合:只有黑节点会让 沿路径递增。

(c) 根部检验:,, ✔(离取等很远,说明这棵树比较"红",节点利用率高)。

6. 易错点 + 老师原话

"the number of black notes on any simple pass from X down to a leaf. 特别规定 X not included. 从一个 X 往下数数到一个 NIL,有多少个黑节点……如果 X 自己是一个黑节点的话,它是不算在内的,你想要算在内又怎么样?问题也不大,只不过,他写有这么写的道理。" — [稿:1-逐字稿:398]

"然后整棵树的黑高,就定义成是根节点的黑高。" — [稿:1-逐字稿:398]

"然后你仔细的去数一下,那黑节点的个数,它也的确是平衡的。……它沿着也是1234……任何一条路径上,它都是四个。" — [稿:1-逐字稿:392](老师把根也算进"总黑数"里数成 4;按正式定义不含根,)

四个易错:

  1. 把起点算进去。,不是 3。
  2. 忘了走到 NIL 的那一步要加 1。这是 NIL 必黑的直接后果;漏了会导致所有人都算小 1,而 关系还能勉强成立,所以自己发现不了错。
  3. 只走一条路径、不交叉验证。算完必须用"左右两条路径算出的值相等"检查一遍,这是唯一能自查的方法。
  4. 把 与"子树的黑高"混写。 是节点属性, 只是特例。

7. 考试英文答题模板

, the black-height of node , is the number of black nodes on any simple path from (excluding itself) down to a leaf (NIL); by property (5) every such path gives the same count, so the definition is well-formed. . Computationally, if , otherwise ; equivalently, a red node passes its black-height through unchanged, while a black node increments it by one.


1.2.3 引理 size(x) ≥ 2^bh(x) − 1 的归纳证明 【A 级】

1. 一句话本质

一棵黑高为 的红黑子树,至少得装 个内部节点——因为黑节点太"抠门",每往下走一层黑高就得两边都长出节点。这条引理是 1.2.4 树高界的第一块砖。

2. 定义与性质

【Lemma】[PPT:1下:36-45]

A red-black tree with internal nodes has height at most .

(注:PPT 正文写的是 , 在这里指 ;老师口述为 "Two log N plus one",即 。考试写 。)

【核心引理】

其中 是"以 为根的子树中内部节点(internal nodes)的个数",不含 NIL。PPT 的标注气泡写明:"Number of internal nodes in the subtree rooted at x"。

证明结构:对 作数学归纳法(PPT 原文 "Prove by induction")。

PPT 给出的四行证明骨架 [PPT:1下:40-45]:

If h(x) = 0, x is NULL        sizeof(x) = 2^0 − 1 = 0        ✔
Suppose it is true for all x with h(x) ≤ k.
For x with h(x) = k + 1, bh(child) = bh(x) or bh(x) − 1
Since h(child) ≤ k, sizeof(child) ≥ 2^bh(child) − 1 ≥ 2^(bh(x)−1) − 1
Hence sizeof(x) = 1 + sizeof(child1) + sizeof(child2)
                ≥ 1 + 2(2^(bh(x)−1) − 1) = 2^bh(x) − 1   ✔

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki《红黑树》没有给出本条引理,也没有树高证明;该页只在"与 2-3-4 树的关系"中说明红黑树与 4 阶 B 树同构,《左偏红黑树》页只给出一句"最长路径不超过最短路径二倍"的结论。形式陈述按教材:设 为以 为根的子树中内部结点(存 key 的实结点)个数,约定 ;则对红黑树中每个结点 ,。证明对 归纳:基例 时 ;归纳步用两个孩子黑高与父黑高至多差 1 这一事实,把两个子树的下界相加。

2.2 OI Wiki 校订|基本性质

  1. 基例(base case): 时 。为什么成立: 是定义, 是"只数内部结点"的直接结果。例:空树满足等式。
  2. 孩子黑高差(child black-height gap):。为什么成立:,括号内只取 0 或 1。例:黑结点 1 的孩子是 NIL,。
  3. 归纳步(inductive step):。为什么成立:对两个孩子各用归纳假设,再用上一条把指数统一到 。例: 时右边为 ,即至少 3 个内部结点。
  4. 取等(tightness):全黑完全二叉树取等。为什么成立:此时每个孩子都是黑,,两个子树都取到归纳下界。例: 的全黑完全二叉树有 3 个结点,。
  5. 推论(corollary):取 得 ,即 。为什么成立:、,再对不等式取 。例:、 时 、。

2.3 OI Wiki 校订|与课堂口径的差异

OI 没有本条引理,也没有 与 的记号,故不存在符号冲突;引理的陈述与证明按 Weiss/CLRS 口径,本文的递推式与之相容。需要点明的是 PPT 归纳步写 ,第二步把两个孩子放大成同一规模,是放宽而不是等号;严格式是 。考试写严格式,并在括号里说明"把两个孩子都放大到同一 "即可;两种写法得到同一个下界 ,不影响结论。

3. 图示

归纳的三种"节点形态"——为什么 只能是 或 :

情形a:x是红情形b:x是黑情形c:x是红x(R)x(B)x(R)├──left(必黑)├──left(黑或红)├──left=NIL(黑)└──right(必黑)└──right(黑或红)└──right=NILbh(left)=bh(x)bh(left)∈{bh(x),bh(x)−1}bh(NIL)=0↑因为left是黑,↑依left的颜色而定x是红⇒bh(x)=0多贡献1个黑与2^bh(x)−1=0一致

关键点: 若是红,它的孩子必为黑(性质 4),所以 ——孩子是黑,正好补上 没贡献的那 1 个黑。

用一棵最小规模的树感受" 有多紧":

bh=0:   NIL                     size=0 = 2^0−1        ← 恰好取等

bh=1:   x(B)                    size=1 = 2^1−1        ← 恰好取等
        /   \
      NIL   NIL

bh=2:   x(B)                    size=3 = 2^2−1        ← 恰好取等(全黑完全二叉树)
       /     \
     y(B)    z(B)
     / \     / \
   NIL NIL NIL NIL

取等的树是"全黑 + 完全二叉树"(等价于 2-3-4 树退化成全 2-节点的情况)。这个直觉在 1.2.4 会用到:树越"红",节点越多、黑高相对越小。

4. 复杂度表

结论 表达式 出现位置
子树规模下界 PPT Lemma 证明 ①
取等条件 子树是全黑完全二叉树 教材练习
归纳变量 高度 (不是 ) PPT 原文
归纳步用到的关键性质 性质 (4) 红节点孩子必黑 PPT 原文
证明长度 3 行递推 考试约 5 分钟

5. 典型例题

Q. Prove that any red-black subtree rooted at contains at least internal nodes. State the induction hypothesis and the two facts you use in the inductive step.

解(中文逐步 + 完整英文证明)

归纳变量:,以 为根的子树高度(单节点树 ,空树按 或单独处理;PPT 取 )。

基础(Base): 时 是 NULL(空指针)。,,,等号成立 ✔。(若把"单节点树"当 的基,则 同样成立 ✔。)

归纳假设(IH):对所有满足 的节点 ,有 。

归纳步:取 满足 ,左右孩子记作 。

  1. 因为 ,所以 , —— 直接由高度定义。
  2. 由 IH:,。
  3. 关键事实(Fact 1):。理由:从 到 NIL 的路径比从 到 NIL 的路径只多经过一个节点 ;又 不含起点,所以 的计数比 的计数"多算了一个 "当且仅当 是黑。即

  4. 由 Fact 1,两种情况下都有 ,因此

    (不等号方向:,指数函数单调递增 ✔)

  5. 同理由对 。于是

    最后一步展开: ✔

结论:对所有 ,。∎

英文证明(可直接默写)

Proof. By induction on the height . Base. If then is NULL and . Inductive hypothesis. Assume for all with . Inductive step. Let with children . Since , , so by the IH and . Because does not count the starting node, if the child is black (it contributes one) and if the child is red; in either case . Hence

6. 易错点 + 老师原话

"就我们首先证明什么东西?就证明,对于任何一个节点 X 来说,X 这棵子树的规模一定是 ≥2 的黑高次方−1。……我们第一步先证明不等式,不等式可以用数学归纳法。Prove by induction。……先定义最小的情况,树高是零的话,这棵树就是空树。空树规模就是零。" — [稿:1-逐字稿:401]

"X 的一共就最多就俩孩子,它每个孩子的黑高跟他自己的黑高是什么关系?最多就差一,最多就差一,要么是不变的,要么就差一,因为他孩子的高度跟他这棵树的高度是差一的,就差一个根节点。" — [稿:1-逐字稿:404]

四个易错:

  1. 到底数什么。数是"internal nodes"(存 key 的实节点),NIL 不算。数错就得不到 这个"完全二叉树节点数"的漂亮形式。
  2. 与 的方向。是 ,反过来说 。写反了归纳步推不下去。
  3. 放宽不等式的技巧。PPT 原文写 ,第二步是在指数上放宽(用 )。这一步看着"变松了",但正是为了把两个孩子的界统一成同一个数。
  4. 减号不能丢。,中间是 不是 。

7. 考试英文答题模板

Lemma. For any node in a red-black tree, , where counts internal nodes in the subtree rooted at . Proof. Induction on . Base: . Step: let with children ; by the IH (and likewise for ), since a child's black-height is either or . Thus .


1.2.4 树高 h ≤ 2log₂(N+1) 的完整证明链 【S 级】

1. 一句话本质

红的不能挨着红的(性质 4),所以最长路径上至少一半是黑的;黑高又保证了树"胖"(1.2.3 的引理),两头一夹就得出 树高最多是最优平衡树的两倍。

2. 定义与性质

【Lemma】[PPT:1下:36-50]

A red-black tree with internal nodes has height at most .

PPT 的证明明确分成 ①、② 两步([PPT:1下:40] 与 [PPT:1下:46]),老师课上也是这么讲的:"第一步先证明不等式……然后我们要的是说整个树的高度"。

完整三步证明链(本文把 PPT 的第 ② 步拆成"证的思路"与"最终合并"两段,因为这一步是学生最容易含糊过去的地方):

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 的《红黑树》页没有给出树高定理及证明,只在《左偏红黑树》页用一句话说明黑高平衡使最长路径不超过最短路径的二倍。形式陈述按教材:设 为根到 NIL 的最长简单路径上的实结点数(本文 1.2.1 样例取 ), 为内部结点数,;则 。证明义务有三项:把 1.2.3 的引理取在根上得 ;由红结点不相邻得 ;合并两式解出 。三项缺一不可,且每一步都要指明所用的性质编号。

2.2 OI Wiki 校订|基本性质

  1. 黑高下界(black-height lower bound):。为什么成立:最长路径上无相邻红结点,故红结点数不超过黑结点数,黑结点至少占一半;性质 (5) 又使该路径黑结点数恰为 。例:路径颜色为 B-R-B-R(根为黑)时 ,除去起点后有 1 个实黑结点,加终点 NIL 得 。
  2. 规模下界(size lower bound):。为什么成立:1.2.3 的引理取 ,并用 、。例: 时 。
  3. 树高上界(height bound):。为什么成立:,移项得 ,两边取 。例: 时 ,代入过程是 ,。
  4. 操作代价(operation cost):查找、插入、删除均为 。为什么成立:BST 操作沿一条根到叶路径,修复的变色与旋转次数由高度控制。例: 时界为 ,实际红黑树约 40 层。

2.3 OI Wiki 校订|与课堂口径的差异

OI 未给该定理,也未讨论 与 的不等式,故推导按 Weiss/CLRS,与 1.2.4 既有证明链完全一致:先 ,再 ,最后合并。PPT 原文写 ,其中 实为 ,考试必须写 。另有一处记号差异:若把 定义为根到 NIL 的边数,同一棵树的高度比本文少 1,不等式更松,结论不变;答题时应先声明" 计实结点数"或" 计边数",再代入 得 (结点口径)。两种口径不能混用。

3. 图示

看这棵"最歪"的红黑树(黑高 ):最短路径全黑,最长路径红黑相间,长度正好是黑高的两倍。

最短路径(全黑):最长路径(红黑相间):(B)(B)││(R)│(B)││(B)│(B)││(R)│NIL│(B)│NIL长度=3=bh长度=6=2·bh

为什么最长就是两倍:性质 (4) 说红节点下面必是黑。所以在任意路径上,不可能出现连续两个红节点——红节点最多占一半。设某条根→NIL 路径上有 个节点,其中红 个、黑 个,则 、,故 。又这条路径上的黑节点数 ( 是"任意"路径的计数,是最小值),注意这里要小心方向——

精确表述:取最长的那条路径,长度就是 。这条路径上黑节点数 ,因为 定义在"任何"路径上且都相等。等等——正因为它都相等,最长路径上的黑节点数恰好等于 。于是

(不含 NIL 的口径下是 ;含 NIL 时略松,仍得 。)

证明 ①:由引理得

在 1.2.3 的 中取 。由 (内部节点总数)、,立刻得

证明 ②:

PPT 原文 [PPT:1下:46-49]:

② ? Since for every red node, both of its children must be black, hence on any simple path from root to a leaf, at least half the nodes (root not included) must be black.

逐步拆:

  • 取从根到某个 NIL 的最长简单路径,其节点数(不含 NIL)为 。
  • 因为红节点的两个孩子都黑,这条路径上不存在相邻的两个红节点。
  • 把路径上的节点按"红/黑"排成一串,红不能连着红 ⇒ 红的最多与黑的一样多 ⇒ 红 黑。
  • 所以黑节点数 。
  • 由性质 (5),每条路径的黑节点数都等于 ;特别地,这条最长路径上的黑节点数 。
  • 因此 ,即 。∎

证明 ③:合并解不等式

(第二个不等号:由 且 单调增得 。)

移项:

∎

与 AVL 对照:AVL 得 ,红黑树得 。红黑树的树高常数大 39%,但这是最坏情况;实际随机数据下红黑树的平均树高只比 AVL 高一点点(1.2.8)。

4. 复杂度表

结论 公式 来源
子树规模下界 引理(1.2.3)
全树规模下界 引理 +
黑高与树高 性质 (4)
树高上界 上两式合并
AVL 树高上界 斐波那契递推
由此得查找

5. 典型例题

Q. Prove that a red-black tree with internal nodes has height at most . You may use, without proof, the fact that for every node . Each step must be justified.

解(中文逐步 + 英文完整答卷)

第一步:把引理搬到根上。 引理对任意节点成立,取 :。

用的理由: 就是全树内部节点数 ; 的定义就是 。

第二步:证明 。 取最长根→叶路径,长度 。性质 (4) 保证红节点的孩子都是黑,所以这条路径上不能有相邻红;于是红节点数 黑节点数,黑节点数 。性质 (5) 说任意路径黑节点数都等于 ,故这条最长路径上的黑节点数就是 ,得 。

用的理由:性质 (4)(红不相邻)+ 性质 (5)(黑高处处相等)+ 最长路径长度即 。

第三步:合并。 。

用的理由: 单调递增 + 两边取 。

英文答卷

Proof. Let and . (1) Apply the lemma at : since and , we get . (2) Consider a longest root-to-leaf path; its length is . By property (4), no two red nodes are adjacent on any path, so at least half of the nodes on this path are black, i.e. it contains at least black nodes. By property (5) every path contains exactly black nodes, so . (3) Combining, , hence , i.e. , and therefore .

Consequently all operations take time.

6. 易错点 + 老师原话

"怎么出来?如果我能证明一棵树的黑高大于等于 1/2 的树高,我带到不等式里面去,一折腾不等式就出来了。是不是有这个结论?……最短的那条路径是什么?路径上全都是黑节点。……最长的那条边什么,就红黑相间的。红黑相间的,再怎么说他都不会超过两倍的黑高,红节点下面必跟一个黑节点。最多就是一红一黑一红一黑,就根节点是黑的,就一黑一红一黑一红到头了,对,最长也就是两倍的黑高了。" — [稿:1-逐字稿:407, 410]

"你把关系带到不等式里头去,然后把 X 换成根节点。整个 size of 根节点,size of root 就是 n……你再解一下这个不等式,你就得到了证明。" — [稿:1-逐字稿:410]

"红黑树的树高不是特别好,不是特别理想。红黑树为什么会大家做库函数的时候优先用红黑树,而不是优先用 AVL 树?" — [稿:1-逐字稿:410](引出 1.2.8)

五个易错:

  1. 忘了把 换成 。引理是"任意节点",必须显式说明 、。
  2. 的方向写反。是黑高下界为一半高出( 大、 小),所以 。写反成 就完全错了。
  3. 说"至少一半节点是黑"但没给理由。必须点出性质 (4):红节点的孩子必黑 ⇒ 无相邻红 ⇒ 红不超过黑。
  4. 的底。PPT 写 ,其中 是 ; 与 数值差很多,考试必须写 。
  5. 漏了 。是 不是 。差一个 的常数,但在 很小时差别明显,且直接对应 的变形。

7. 考试英文答题模板

Theorem. A red-black tree with internal nodes has height at most . Proof. (i) By the lemma , taking gives where . (ii) On a longest root-to-leaf path there are nodes; property (4) forbids two adjacent red nodes, so at most half of them are red and hence at least are black. Since property (5) makes the black count equal to on every path, . (iii) Thus , so and .


1.2.5 插入的三种情形 Case 1/2/3 【S 级】

1. 一句话本质

新节点一律先染红(染黑要改整棵树的黑高,太亏),于是只有"父节点也是红"时才出事;出事以后看叔叔的颜色——叔红就变色往上推(Case 1),叔黑就旋转(Case 2 先掰直,Case 3 再转)。

2. 定义与性质

[PPT:1下:53-54]

Insert — can be done iteratively Sketch of the idea: Insert & **color red

为什么必须染红?老师的问答就是标准答案:

"如果你插了一个新节点进来,你把它染成黑的,就意味着你把一条路径上的黑色节点的个数给加一了,这一下不要紧,你所有其他路径都得加一,然后这事情就很恶心了,你就要做一个全局的黑高的调整。" — [稿:1-逐字稿:425]

"我插一个新节点染成红的,我就不会影响任何人的黑高。……他父节点是黑的,当然没事了。它父节点是个红的,你染成红的,这事情直接就不对了。" — [稿:1-逐字稿:428]

三种情形(以 为新节点、 为父、 为祖父、 为叔节点为例, 是左孩子的情形):

Case 条件 操作 是否终止
Case 1 叔节点 红 与 染黑, 染红; 继续上溯 可能继续(问题被推到祖父)
Case 2 叔节点 黑,且 是右孩子(之字形 zig-zag) 对 左旋,;转成 Case 3 不终止,转入 Case 3
Case 3 叔节点 黑,且 是左孩子(一字形 straight) 染黑, 染红,对 右旋 终止

最后一步,永远要做:

因为 Case 1 可能一路推到根,把根染成红。老师原话:"最后根强制为黑"。

循环不变式(为什么 Case 1 完了还能继续):处理完 Case 1 后, 变成红色,它的父可能是红色 ⇒ 新的违反点就在 处,故令 继续。而 Case 3 结束后,新子树的根是黑的,不可能再违反,循环必然退出。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 把插入定义为:按 BST 规则把新结点 接到某个 NIL 的位置并染红;若父结点 为黑则结束,否则因 为红可知祖父 为黑(否则 与 已构成红红),再按叔结点 的颜色与 相对 的方向分三种情形。形式化地,三种情形覆盖且只覆盖以下互斥条件: 为红; 为黑且 与 方向不同; 为黑且 与 方向相同。每种情形给出一个局部变换,变换后要么把违反点上移一层,要么使子树的根变黑而终止。

2.2 OI Wiki 校订|基本性质

  1. 染红保持黑高(red insertion preserves black-height):新结点染红不改变任何路径的黑结点数。为什么成立:插入点原是一个黑 NIL,路径计 1;插入红 后路径经 到两个黑 NIL,仍计 1。例:单黑根下插入红孩子,两条路径黑数都仍为 1。
  2. 唯一可能违反(single violation):插入后只有性质 (4) 可能被破坏。为什么成立:颜色仍二值、NIL 仍黑、根可最后染黑、每条路径黑数不变;只有"红父 + 红子"是新的。例:父为黑时无需任何修复。
  3. Case 1 保持黑高(recolouring preserves black-height): 由红变黑、 由黑变红,经过 的路径黑数净变化为 0。为什么成立:每条经过 的路径必经过 或 之一,下方 +1、上方 −1 相抵;不经过 的路径完全不变。例: 带两个红孩子,改色后 、两孩子黑,路径黑数不变。
  4. Case 3 终止(termination):Case 3 旋转后新子树根为黑,红红消失。为什么成立: 染黑、 染红后再绕 旋转, 成为子树根,其两个孩子为红 与原子树,红结点的孩子均为黑。例:链 插入 3 后得 为根、 与 为子。
  5. 旋转上界(rotation bound):一次插入至多 2 次旋转。为什么成立:Case 1 不旋转,Case 2 转 1 次后必转 Case 3,Case 3 再转 1 次即终止。例:Case 1 连续 次仍为 0 次旋转,总旋转数不超过 2。

2.3 OI Wiki 校订|与课堂口径的差异

三种情形的编号与 PPT 完全同序:OI 的 Insert case 1 是叔红,case 2 是叔黑且 与 方向不同(之字),case 3 是叔黑且同向(一字);PPT 的 Case 1/2/3 与之一一对应,可直接套用。差异来自根黑条款:OI 不要求根黑,所以其循环必须以" 不是根"为前提,否则 为红且为根时 不存在;PPT 根恒黑, 为红即保证 存在。OI 的 Case 3 写"旋转 后交换 与 的颜色",与 PPT 的" 染黑、 染红再绕 旋转"是同一变换的两种叙述顺序。考试按 PPT 写,并保留最后的 root.color = BLACK。

3. 图示

(一)PPT slide 4 的真实走查(颜色经渲染图逐一核对)

初始树(插入 4 之前的状态):

             11(B)
           /        \
        2(R)         14(B)
       /    \            \
    1(B)    7(R)         15(R)
           /    \
        5(B)    8(B)
        /
     4(R)   ← 新插入,染红,父 5 是黑 ⇒ 什么都不用做?

(二)三种情形的抽象前后对比

Case 1:叔红 → 变色,问题上推

   之前                              之后
   G(B)                              G(R)   ← 变红(可能又和它的父冲突)
   /  \                              /  \
 P(R)  U(R)          ====>        P(B)  U(B)
 /                                  /
z(R)                               z(R)
(红-红冲突)                        (局部解决,但 G 变红可能引发新冲突)

记忆:父、叔变黑;祖父变红;指针跳到祖父。


Case 2:叔黑 + 之字形 → 先掰直

   之前                              之后(对 P 左旋)
   G(B)                              G(B)
   /  \                              /  \
 P(R)  U(B)         ====>          z(R)  U(B)
 /  \                              /  \
a   z(R)                         P(R)  c
    /  \                          /  \
   b    c                        a    b

(z 是 P 的右孩子 = 之字形)        (变成"一字形",即 Case 3 的形状)

记忆:Case 2 自己解决不了问题,只是把形状掰成 Case 3。


Case 3:叔黑 + 一字形 → 变色 + 旋转,收工

   之前                              之后(P 染黑, G 染红, 对 G 右旋)
   G(B)                                 P(B)
   /  \                                 /  \
 P(R)  U(B)          ====>           z(R)  G(R)
 /                                          /  \
z(R)  c                                    b    U(B)
/  \
a   b

(z 是 P 的左孩子 = 一字形)         (新子树根是黑的 ⇒ 循环结束)

记忆:父变黑、祖父变红;对祖父旋转。


(三)强制根黑的完整例子

插入序列 1, 2, 3(全升序,最坏情况):

insert 1:        1(B)                ← 第一个节点,直接染黑(根)

insert 2:        1(B)                ← 2 染红,父 1 是黑,无需调整
                   \
                   2(R)

insert 3:        1(B)                ← 3 染红,父 2 是红 ⇒ 冲突!
                   \                   叔节点 = NIL = 黑 ⇒ 叔黑
                   2(R)                3 是 2 的右孩子 = 一字形 ⇒ Case 3
                     \
                     3(R)

Case 3 执行:父 2 染黑、祖父 1 染红、对 1 左旋:

                 2(B)                ← 新根
                /    \
             1(R)    3(R)

最后:root.color = BLACK —— 此处根已是黑,无需改动。

再看不那么幸运的情况——插入序列 30, 20, 10, 40, 50, 60,最后一步会把根染红,必须强制改黑:

(前 5 步略)执行到:

              20(B)
             /     \
          10(R)    40(R)
                     \
                     50(R)        ← 新插入

50 的父 40 是红 ⇒ 冲突。叔节点 = 10 是红 ⇒ Case 1
40 与 10 染黑,20 染红:

              20(R)    ← 根被染红了!违反性质 (2)
             /     \
          10(B)   40(B)
                     \
                     50(R)

z 上移到 20,此时 20 是根 ⇒ 循环条件 z.p == RED 不成立,退出。
** root.color = BLACK **  ⇒  20 改回黑:

              20(B)    ✔ 合法
             /     \
          10(B)   40(B)
                     \
                     50(R)

这就是"最后根强制为黑"的真实发生场景——没有这一步,树就违反性质 (2)。

4. 复杂度表

项 Case 1 Case 2 Case 3 合计
变色次数(单次执行) 3 0 2 —
旋转次数 0 1 1 ≤ 2
是否可能重复 是(上溯) 否 否 —
单次总时间 —
整个插入时间 查找 + 修复
最坏旋转次数 — — — ≤ 2(Case 2 + Case 3 各一次)

注意 Case 1 可以连续执行 次(一路推到根),但每次都是 的变色,不旋转;所以旋转次数上界仍是 2。

5. 典型例题

Q. Insert the keys 11, 2, 14, 1, 5, 7, 8, 15, 4 in this order into an initially empty red-black tree, drawing the tree after each insertion and naming the case used. (This is PPT slide 4.)

解(中文逐步,每步都标 case)

① 插入 11 → 根,染黑。

11(B)

② 插入 2 → 染红,父 11 黑,无需调整。

11(B)
/
2(R)

③ 插入 14 → 染红,父 11 黑,无需调整。

   11(B)
  /     \
2(R)    14(R)

④ 插入 1 → 染红放在 2 的左边。父 2 是红 ⇒ 冲突。叔节点 = 14 是红 ⇒ Case 1:2、14 染黑,11 染红; 上移到 11,11 是根,退出,强制根黑(此处本就该黑,等价于不变)。

   11(B)
  /     \
2(B)    14(B)
/
1(R)

⑤ 插入 5 → 放在 2 的右边。父 2 是黑 ⇒ 无需调整。

     11(B)
    /     \
 2(B)      14(B)
 /   \
1(R)  5(R)

⑥ 插入 7 → 放在 5 的右边。父 5 是红 ⇒ 冲突。叔节点 = 1 是红 ⇒ Case 1:5、1 染黑,2 染红; 上移到 2。此时 2 是红,2 的父 11 是黑 ⇒ 循环条件不成立,退出。根仍黑。

      11(B)
     /     \
  2(R)      14(B)
  /   \
1(B)   5(B)
         \
         7(R)

⑦ 插入 8 → 放在 7 的右边。父 7 是红 ⇒ 冲突。叔节点 = NIL 是黑 ⇒ 看形状:7 是 5 的右孩子,8 是 7 的右孩子 ⇒ 一字形 ⇒ Case 3:7 染黑、5 染红、对 5 左旋。

      11(B)
     /     \
  2(R)      14(B)
  /   \
1(B)   7(B)
       /   \
     5(R)  8(R)

⑧ 插入 15 → 放在 14 的右边。父 14 是黑 ⇒ 无需调整。

      11(B)
     /     \
  2(R)      14(B)
  /   \         \
1(B)   7(B)     15(R)
       /   \
     5(R)  8(R)

⑨ 插入 4 → 放在 5 的左边。父 5 是红 ⇒ 冲突。叔节点 = 8 是红 ⇒ Case 1:5、8 染黑,7 染红; 上移到 7。7 是红,7 的父 2 也是红 ⇒ 继续冲突!叔节点(2 的另一个孩子)= 1 是黑 ⇒ 看形状:7 是 2 的右孩子、2 是 11 的左孩子 ⇒ 之字形 ⇒ Case 2:对 2 左旋, 2;变成一字形后进入 Case 3:2 染黑、11 染红、对 11 右旋。

Case 1 后:                      Case 2 后(对 2 左旋):
      11(B)                            11(B)
     /     \                          /      \
  2(R)     14(B)                   7(R)      14(B)
  /   \         \                 /    \         \
1(B)  7(R)     15(R)           2(R)    8(B)     15(R)
      /  \                     /   \
   5(B)  8(B)               1(B)  5(B)
    /                                /
  4(R)                             4(R)

Case 3 后(2 染黑, 11 染红, 对 11 右旋):
              7(B)
            /      \
        2(R)        11(R)
       /   \        /    \
    1(B)  5(B)   8(B)   14(B)
           /                \
        4(R)               15(R)

最终结果与 PPT slide 4 第三张图完全一致 ✔(这是一条罕见的Case 1 → Case 2 → Case 3 全链路,非常适合当大题)。

补充例题(全升序,最坏输入)

Q. Insert 41, 38, 31, 12, 19, 8 in order.

41:41(B)38:41(B)/38(R)31:41(B)←31染红,父38红⇒冲突;叔=NIL黑;/31是38的左孩子=一字形⇒Case338(R)(38染黑,41染红,对41右旋)/31(R)↓38(B)/\31(R)41(R)12:38(B)←12染红,父31红⇒冲突;叔=41红⇒Case1/\(31、41染黑,38染红→38是根⇒强制染黑)31(B)41(B)→实际上就是恢复成黑根/12(R)19:38(B)←19染红,父12红⇒冲突;叔=31的右孩子=NIL黑/\19是12的右孩子(之字形)⇒Case2(对12左旋)31(B)41(B)然后19是...的左孩子(一字形)⇒Case3/(19染黑,31染红,对31右旋)12(R)\19(R)↓38(B)/\19(B)41(B)/\12(R)31(R)8:38(B)←8染红,父12红⇒冲突;叔=31是红⇒Case1/\(12、31染黑,19染红)19(R)41(B)←19变成红;19的父38是黑⇒循环退出/\12(B)31(B)/8(R)

六步只用了一次 Case 1(×2 次)、一次 Case 2、一次 Case 3,最多两次旋转——这就是"插入 ≤ 2 次旋转"的具体体现。

6. 易错点 + 老师原话

"第一个 case 是什么情况?他的 parent 是红的,导致他不合法,parent 的 sibling,他的兄弟姐妹,我们一般看往上看三层,然后这一层两个都是红的。……你发现有一个什么简单的办法不改变黑高,把两层的颜色换一下,直接换一下颜色就好了。……四好了,你把原来的黑节点变成红节点,存在一个危险,是黑节点本来头上是个红的,你由黑变红,那这一步又不对了。" — [稿:1-逐字稿:431](这就是 Case 1 会"上推"的原因)

"第一件事情先把它变成顺拐,但是顺拐并没有解决问题,没关系,我先顺顺顺一下,我先顺拐一下,就变成这样子了,就变成了 case3。我们最终解决的是 case3。" — [稿:1-逐字稿:434]

"也有相当的概率是说 parent 是红的,这个时候,我们得做调整。" — [稿:1-逐字稿:428]

六个易错:

  1. 新节点染黑。这会让一条路径黑高 +1,整棵树全要重调——这是本讲最致命的错误,因为它是"出发点"错了。
  2. 看错了叔叔。叔叔是父节点的兄弟,不是"祖父的另一个孩子"以外的东西;若父是祖父的左孩子,叔就是祖父的右孩子。
  3. Case 2 之后忘了继续做 Case 3。Case 2 只负责"掰直",本身不解决红−红。Case 2 永远紧跟着 Case 3。
  4. Case 3 旋转方向搞反。父在左 ⇒ 对祖父右旋;父在右 ⇒ 对祖父左旋。
  5. 忘了最后强制根黑。Case 1 一路推到根时,根会变红,必须显式改回黑(上面 30,20,10,40,50,60 的例子)。
  6. 以为 Case 1 会重复很多次很慢。Case 1 是纯变色 ,最多 次,整体仍是 ;且不旋转,所以旋转数界仍是 2。

7. 考试英文答题模板

Insertion into a red-black tree. Insert the new node with BST-Insert and colour it red, so that property (5) is preserved. If 's parent is black we are done. Otherwise, let , and be 's uncle (the sibling of ): - Case 1 ( is red): recolour and black and red; set and repeat. The black-heights are unchanged, but the violation is pushed up to . - Case 2 ( is black and is an "inner" child, forming a zig-zag): rotate at to turn the zig-zag into a straight line, set , and continue with Case 3. - Case 3 ( is black and is an "outer" child, a straight line): recolour black and red, then rotate at ; the subtree now has a black root, so the violation is fixed and the loop terminates.

Finally set root.color = BLACK. Since Case 3 terminates the loop and only Case 2 and Case 3 rotate, insertion performs at most 2 rotations in total time.


1.2.6 口诀「swap colors and rotate」 【S 级】

1. 一句话本质

红黑树所有修复(插入、删除、自顶向下、自底向上)本质是同一件事的两种动作:先看哪两个颜色该换,再看往哪个方向转。老师把它浓缩成六个字。

2. 定义与性质

"我们叫 swap colors and rotate。就是你红黑树的所有的操作,你记住一个口诀 swap colors and rotate——换颜色转一下,换颜色转一下。关键,你哪两个颜色换,然后怎么转?" — [稿:1-逐字稿:437]

口诀的两个变量:

动作 要决定的事 插入中的答案 删除中的答案
swap colors(换色) 换哪两/三个节点的颜色 视 Case 而定:Case 1 换父、叔、祖父;Case 3 换父、祖父 视 sibling 与 parent 而定
rotate(旋转) 绕哪个节点、朝哪个方向 Case 2 绕父;Case 3 绕祖父 Case 1/3 绕父;Case 4 绕父

口诀的完整展开表(这是 1.2.5 与 1.2.7 的共同骨架):

场景 swap colors(换谁) rotate(绕谁、方向) 之后
插入 Case 1 黑, 红 无 上移到
插入 Case 2 无 绕 ,使之字变一字 转 Case 3
插入 Case 3 黑, 红 绕 ,方向由 的左右决定 结束
删除 Case 1 与 互换 绕 转 Case 2/3/4
删除 Case 3 与 的"近"红孩子互换 绕 转 Case 4
删除 Case 4 取 的颜色, 黑, 的远红孩子黑 绕 结束

为什么"换色"能保持黑高不变——这是口诀成立的根本原因:

  • 插入 Case 1:、 由红变黑(各路径 +1), 由黑变红(经过 的路径 −1,但 下面的路径因为 / 变黑又 +1)。净变化为 0 ⇒ 黑高不变。
  • 插入 Case 3: 红→黑、 黑→红,再对 旋转。旋转前经过 的路径黑数 = 经过 的路径黑数;旋转后 成为子树根,原来经过 的路径现在经过 ,黑数一致。

形如"红黑树 ↔ 2-3-4 树"的对应(理解口诀的另一种视角,考试不要求但很好用):

红黑树 2-3-4 树
一个黑节点 2-节点
一个黑节点 + 一个红孩子 3-节点
一个黑节点 + 两个红孩子 4-节点
Case 1(变色) 4-节点分裂,中间键上移
Case 2/3(旋转) 3-节点内部重排
黑高 B 树的高度

于是"swap colors"= 4-节点分裂,"rotate"= 3-节点重排。这个对应解释了为什么 Case 1 是"向上推"——B 树的分裂也是向上传播的。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 把插入修复拆成"旋转"与"插入后的平衡维护"两节,没有把口诀写成算法。形式化地,一次修复(fix-up)是作用在树 与违反点 上的局部变换 ,由两类原子操作组成:换色(recolouring)只改一个或三个结点的 color 字段,不改指针;旋转(rotation)只改常数条父子指针,不改中序序列。 的合法输出必须满足:中序序列不变、性质 (5) 的黑计数不变、红红违反点要么消失,要么沿父链严格上移。口诀"swap colors and rotate"就是对"先定换哪几个颜色、再定绕谁往哪转"这两步的压缩命名。

2.2 OI Wiki 校订|基本性质

  1. 旋转保中序(inorder preservation):左旋、右旋都不改变 BST 的中序序列。为什么成立:旋转只把父子关系在三个结点之间重排,各子树仍落在原来的键区间内。例:对 左旋, 的右孩子 上升, 的左子树改挂到 右侧,中序仍是 。
  2. 换色不改形状(shape preservation):换色只改 color 字段。为什么成立:颜色不是结构信息,指针与键都不动。例:Case 1 把 染黑、 染红,树的形状与键分布完全不变。
  3. 共线判据(collinearity test): 与 同向当且仅当 或 。为什么成立:同向即三点 共线,折线即之字。例: 是 的右孩子且 是 的左孩子,三点成之字,须先掰直。
  4. 终止性(termination):Case 2 必转入 Case 3,Case 3 必终止;Case 1 使 的深度严格减小。为什么成立:Case 3 后子树根为黑,不再违反;Case 1 令 ,深度每次至少减 2,深度是非负整数,故最多上移有限次。例: 三层连续 Case 1 后 到根,循环退出。

2.3 OI Wiki 校订|与课堂口径的差异

术语与记号不同:OI 用 表示新结点、父、祖父、叔,PPT 与本文用 ( 为叔);OI 的"旋转 后令 "就是本文的"对 旋转后 ",操作完全相同。OI 把换色与旋转分节陈述,没有口诀;口诀是课堂对同一算法的压缩记法,不改变任何一步。唯一需要按 PPT 补的一句是最后强制根黑:OI 因不要求根黑而省略此步,PPT 要求。考试写清"换哪几个颜色、绕谁转、是否终止"三件事即可,口诀本身不作为评分点。

3. 图示

一句话口诀 → 三种情形的统一视图:

┌──────────────────────────────────────────────────────┐│插入后看到"红-红"(z与p都是红)│└──────────────────────┬───────────────────────────────┘│叔y是什么颜色?│┌──────────────┴──────────────┐│红│黑▼▼┌─────────┐z与p同向?│Case1│┌────┴────┐│swap││之字│一字│colors│▼▼│(p,y→B,│┌────────┐┌────────┐│g→R)││Case2││Case3││无旋转││rotate││swap││z←g││绕p││colors││继续上溯││││+rotate│└─────────┘└───┬────┘│绕g│││└───┬────┘│└───────────┤│▼│┌──────────┐││结束│││根强制黑││└──────────┘└──────►回到顶部继续判断

口诀记忆的三个"换"与"转":

Case 1: 换 3 个颜色(p, y 变黑;g 变红)  转 0 次
Case 2: 换 0 个颜色                       转 1 次(绕父)
Case 3: 换 2 个颜色(p 黑, g 红)         转 1 次(绕祖父)
最后:   root = BLACK

4. 复杂度表

视角 时间 旋转次数
「口诀」单次执行 ≤ 1
插入全过程(最坏) ≤ 2
删除全过程(最坏) ≤ 3
口诀的适用范围 插入、删除、top-down 插入 —

5. 典型例题

Q. For each of the following situations, state (a) which nodes exchange colours, (b) about which node a rotation is performed and in which direction, and (c) whether the fixprop loop terminates.

  1. New node is the right child of a red left child ; uncle is red.
  2. New node is the right child of a red left child ; uncle is black.
  3. New node is the left child of a red left child ; uncle is black.

解

题 (a) swap colors (b) rotate (c) 终止?
1 BLACK, RED 无 否, 继续
2 本次不换色 绕 左旋(把之字掰成一字) 否,转 Case 3
3 BLACK, RED 绕 右旋( 在左) 是,循环退出

补充:三题做完后都要执行 root.color = BLACK。

关键辨析:题 2 与题 3 的差别只在" 是左孩子还是右孩子",但处理方式完全不同——这就是口诀里"怎么转"要判断的那一步。判断规则是:

、、 三点是否共线(一字形)?共线 → Case 3;成折线(之字形)→ Case 2。

6. 易错点 + 老师原话

"我们叫 swap colors and rotate。就是你红黑树的所有的操作,你记住一个口诀 swap colors and rotate——换颜色转一下,换颜色转一下。关键,你哪两个颜色换,然后怎么转?它俩的颜色换一下,再转。转一下变成样子,这棵树全好了。insertion 还是比较简单的。" — [稿:1-逐字稿:437]

"对于红黑树,你只要掌握它的性质,重要的性质就(够了)。" — [稿:1-逐字稿:452](老师明确说不需要背实现细节,只要抓住性质与口诀)

四个易错:

  1. 把口诀当万灵药,不判断"换哪两个"。口诀只给了框架,具体换谁必须看 case;老师特意强调"关键,你哪两个颜色换,然后怎么转"。
  2. 以为 Case 3 是"先转再换色"。顺序上换色与旋转可以互换(结果相同),但考试写步骤时按 PPT 写"先换色再旋转",最稳。
  3. 在 Case 2 里换色。Case 2 只旋转不换色,换色留给 Case 3。
  4. 漏掉根强制黑。口诀不包含这一步,但每次插入的最后必须写。

7. 考试英文答题模板

The essence of all red-black fix-up operations can be compressed into the mnemonic "swap colours and rotate". For insertion, after colouring the new node red: - If the uncle is red (Case 1), swap colours among parent, uncle and grandparent, then propagate the violation upward. - If the uncle is black and the configuration is a zig-zag (Case 2), rotate at the parent first to straighten it. - If the uncle is black and the configuration is a straight line (Case 3), swap colours between parent and grandparent and then rotate at the grandparent; this terminates the fix-up.

In every case, finish by forcing the root to be black. The only real questions are therefore which pairs of colours to swap and about which node and in which direction to rotate.


1.2.7 删除:最多 3 次旋转 【S 级】

1. 一句话本质

删红节点什么都不用做;删黑节点才麻烦——但麻烦有上限:沿路径上溯,最多转 3 次就完事。这正是红黑树压过 AVL 的地方。

2. 定义与性质

[PPT:1下:81-91]

Delete a leaf node: Reset its parent link to NIL. Delete a degree 1 node: Replace the node by its single child. Delete a degree 2 node: ① Replace the node by the largest one in its left subtree or the smallest one in its right subtree. ② Delete the replacing node from the subtree. Keep the color … Adjust only if the node is black. Must add 1 black to the path of the replacing node.

拆成三条:

(a) 删除的三种度数情形(和普通 BST 一样):

度数 操作
0(叶子) 直接删,父指针置 NIL
1 用唯一的孩子顶替
2 用左子树最大或右子树最小顶替,颜色保持不变("Keep the color"),转而删掉那个顶替者原来的位置

为什么"Keep the color":度数 2 的节点只是"换了个 key 进去",节点本身的位置和颜色都不动,树的形状完全没变 ⇒ 不产生任何红黑性质违反。真正的删除发生在下面(度数 0 或 1 的位置)。

(b) 只有删黑才要修:

  • 删红节点 ⇒ 黑高不变、不产生红−红(红的父必黑,红的孩子必黑,删掉红的反而让黑的连上黑的)⇒ 什么都不用做。
  • 删黑节点 ⇒ 那条路径少 1 个黑 ⇒ 违反性质 (5) ⇒ 必须补 1 个黑回去。

(c) 删除修复的四种情形(设 是"缺了一个黑"的位置, 是 的兄弟 sibling):

Case 条件 操作 结果
1 是红 与 换色,绕 旋转 转成 2/3/4,未解决
2 黑,且 的两个孩子都黑 染红, 若 原为红则染黑结束;否则继续上溯
3 黑, 的近孩子红、远孩子黑 与近孩子换色,绕 旋转 转成 Case 4
4 黑, 的远孩子红 取 的颜色, 染黑,远孩子染黑,绕 旋转 解决,结束

PPT [PPT:1下:98] 把 Case 2/3/4 的入口条件画成三张图,[PPT:1下:107-123] 再展开每种的中间态,[PPT:1下:130-155] 给出完整走查。

关键:为什么最多 3 次旋转

  • Case 1 转 1 次 → 必然进入 2/3/4;
  • Case 2 不旋转,只是上溯(可能重复多次,但每次都 变色);
  • Case 3 转 1 次 → 必然进入 Case 4;
  • Case 4 转 1 次 → 必然结束。

所以旋转最多 次(Case 1 → Case 3 → Case 4 这条最长链)。而 Case 2 虽然可以重复,但它不转,只变色,故不影响旋转计数。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 的删除定义分两步:若待删结点 有两个孩子,先与右子树最小结点交换数据并把 移到该位置,使问题化为度 0 或度 1;度 1 时用唯一孩子顶替并染黑;度 0 且为黑时才会破坏性质 (5)。设 为缺一个黑的黑色叶位置, 为父、 为兄弟、 与 分别为"近"侄与"远"侄(近远按 是左孩子还是右孩子确定)。四种情形由 的取值划分: 红; 黑且 全黑; 黑、 红、 黑; 黑、 红。四类互斥且穷尽。

2.2 OI Wiki 校订|基本性质

  1. 删红无事(red deletion):被物理删除的结点为红时不违反任何性质。为什么成立:红结点不计入黑高,删去后其位置由 NIL 顶替,路径黑数不变;红结点的父必黑,也不会产生新的红红。例:删去样例树中的红结点 4,两条相关路径黑数仍为 2。
  2. 缺黑记号(double-black):删黑后把"缺 1 个黑"记在顶替位置 上。为什么成立:该路径黑数比其余路径少 1,性质 (5) 被破坏,必须补回。例:删黑叶 2 后,经过原 2 位置的路径比兄弟路径少 1 个黑。
  3. Case 1 归约(reduction): 红时交换 与 的颜色并绕 旋转,新的兄弟变黑,问题落入 Case 2/3/4。为什么成立:旋转后原 的黑色孩子成为 的新兄弟,其颜色必黑。例:、 换色后 、, 的新兄弟为黑。
  4. Case 2 上移(propagation): 全黑时把 染红,令 ;若 原为红则染黑后终止。为什么成立: 子树整体少 1 个黑,与 侧持平;但 以上仍可能缺黑,故上移。例: 为红时, 染红后把 染黑,两侧黑数同时补齐。
  5. Case 4 终止(termination):远侄 红时,令 取 的颜色、 染黑、 染黑,再绕 旋转,缺黑被补足且循环结束。为什么成立:旋转后 占据 的位置,原 侧多经过一个黑 , 侧黑数不变。例: 变换后 为子树根、 在左、 在右,所有路径黑数相等。

2.3 OI Wiki 校订|与课堂口径的差异

四种情形的编号与 PPT 同序:1 兄弟红、2 兄弟与两侄全黑、3 近侄红远侄黑、4 远侄红;OI 的 与 PPT 的 、近侄、远侄一一对应,"近/远"都随 是左孩子还是右孩子翻转。两处差异需要点明:其一,OI 未给出"删除最多 3 次旋转"的界,其正文只说"从 开始向上递归维护",没有统计旋转次数;该界按 Weiss 口径保留,推导是 Case 1 转 1 次、Case 3 转 1 次、Case 4 转 1 次,Case 2 只变色不旋转。其二,OI 的 Case 2 注明"若 为红则退出循环并在最后染黑",与 PPT 的" 原红则染黑结束"一致。考试按 PPT 的四 case 加"≤3 次旋转"作答。

3. 图示

(一)四种情形

Case 1(w 红):w 与 p 换色 + 绕 p 旋转 → 落到 2/3/4
        p(B)                        p(R)
       /    \                      /    \
    x(BB)   w(R)      ====>     w(B)    x(BB)
            /  \                /   \
        (必有黑) (必有黑)       ...   ...
   ※ 转完问题没解决,只改变了 case

Case 2(w 黑, w 两孩子都黑):w 染红,x 上移
        p(?)                        p(?) ← 新的 x
       /    \                      /    \
    x(BB)   w(B)      ====>     x(BB)  w(R)
            /  \                        /  \
         黑    黑                     黑    黑
   ※ 若 p 原为红 ⇒ p 染黑,结束
     若 p 原为黑 ⇒ 继续上溯(可能重复)

Case 3(w 黑, 近孩子红, 远孩子黑):w 与近孩子换色 + 绕 w 旋转 → 转 Case 4
        p(?)                        p(?)
       /    \                      /    \
    x(BB)   w(B)      ====>     x(BB)  w'(B)   ← 新的 w
            /  \                        /  \
          R    黑                    红    ...
         (近)                              (远=原近孩子, 现黑)
   ※ 变成"远孩子红"的 Case 4

Case 4(w 黑, 远孩子红):w 取 p 色, p 黑, 远孩子黑, 绕 p 旋转 → 结束
        p(?)                        w(?)
       /    \                      /    \
    x(BB)   w(B)      ====>     p(B)    R→B
            /  \                /   \
          ?     R(远)        x(B)   ...
   ※ 补够了黑,x 可以安全删除

(二)PPT slide 7 的完整走查(Case 1 → Case 2.1 / Case 2.2 / Case 3 → Case 4)

初始树(删除某个节点后 处缺黑):

              10(B)
           /         \
        5(B)         15(B)
       /    \        /     \
    3(B)   7(R)   11(B)   17(B)
           /  \
        6(B)  8(B)
   ↑
   x(缺黑)

分支 A:进入 Case 2.1( 黑,两孩子都黑)

              10(B)
           /         \
        5(R)         15(B)      ← w=5 染红
       /    \        /     \
    3(B)   7(R)   11(B)   17(B)   ← x 上移到 5 的位置
           /  \
        6(B)  8(B)
              ↑ 新的 x

若 5 的父(10)是黑 ⇒ 继续上溯;此处 10 是黑 ⇒ 再进 Case 2, 变成 15……

(PPT 走查继续到 Case 2.2 → Case 3 → Case 4,最终得到 10 / 6,15 / 5,7,8,11,17 形状,共 3 次旋转。)

分支 B:Case 3 → Case 4

Case 3 前(w=7 黑, 近孩子 5 红, 远孩子 8 黑):
              10(B)
             /      \
          7(B)      15(B)
         /    \      /   \
      5(R)   8(B) 11(B) 17(B)
      /
    6(B)
     ↑ x

Case 3:w=7 与近孩子 5 换色(7→R, 5→B),绕 7 右旋:
              10(B)
             /      \
          5(B)      15(B)
         /    \      /   \
      6(B)   7(R) 11(B) 17(B)
               \
               8(B)
               ↑ x(或新 w 位置)

Case 4:w=7 → 取 p(5) 的颜色 B;p(5) → B;远孩子 8 → B;绕 5 右旋:
              10(B)
             /      \
          7(B)      15(B)
         /    \      /   \
      5(B)   8(B) 11(B) 17(B)
      /
    6(B)

4. 复杂度表

维度 AVL Tree Red-Black Tree
删除时间
删除旋转次数 (一路转到根)
删除变色次数 — (Case 2 可重复)
删除后是否要上溯 是,可能到根 是(仅 Case 2),但不再旋转
插入旋转次数
插入时间

这就是红黑树的"绝对优势"所在:删除时 AVL 可能需要 次旋转(每次旋转 但次数多),红黑树最多 3 次。

5. 典型例题

Q. The red-black tree below is given. Delete the key 6. Show the state after each step of the fix-up, name the case used, and count the rotations.

4(B) / \ 2(B) 6(B) / \ / \ 1(R) 3(R) 5(R) 7(R)

解(中文逐步)

第一次看可能以为删 6 就完事了,但 6 是黑节点 ⇒ 必须补黑。

Step 0:确认删除位置。 6 的度数为 2(孩子 5、7)⇒ 用左子树最大(5)或右子树最小(7)顶替。这里选左子树最大 5:

        4(B)
       /    \
    2(B)    5(B)       ← 5 顶替 6,颜色沿用 6 的黑色
    /  \      \
  1(R) 3(R)   7(R)     ← 现在要删的是原来 5 的位置

删掉 5 原来的位置(5 是红的)⇒ 删红无事,树已合法:

        4(B)
       /    \
    2(B)    5(B)
    /  \      \
  1(R) 3(R)   7(R)

旋转次数:0。 这是"删红或顶替后删的是红 ⇒ 什么都不用做"的典型。


换成删 1(红叶子) —— 删红,直接删,0 次旋转:

        4(B)
       /    \
    2(B)    6(B)
      \     /  \
     3(R) 5(R) 7(R)

再换成删 2(黑,但有红孩子 3) —— 用 3 顶替,3 继承 2 的黑色("Keep the color"):

        4(B)
       /    \
    3(B)    6(B)
           /  \
        5(R)  7(R)

旋转次数:0。 这才是"删度数 1 的节点"的正解——用孩子顶替 + 继承颜色,一步搞定,不需要进修复循环。


真正需要修复的例子:删 2,且 2 没有红孩子的情况:

原始树 4B / 2B(1R,3R) / 6B(5R,7R),删掉 1 和 3 后再删 2,此时 2 是黑叶子:

        4(B)
       /    \
    2(B)    6(B)         删 2(黑叶子)⇒ x = 2 的 NIL 位置,缺 1 个黑
           /  \
        5(R)  7(R)
         ↑ x 的兄弟 w = 6

w=6 是黑;w 的两个孩子 5、7 都是红 ⇒ w 的**远孩子 7 是红** ⇒ Case 4

Case 4:
  w(6) 取 p(4) 的颜色 ⇒ 6 → B
  p(4) → B(本来就是)
  远孩子 7 → B
  绕 p(4) 左旋

结果:
              6(B)
            /      \
         4(B)      7(B)
                  /
               5(R)
        ↑ 原来 x 的位置现在在 4 的左子树,黑数已补齐

旋转次数:1(Case 4 直接终止)

记忆:远孩子红 ⇒ 一步到位(Case 4);近孩子红 ⇒ 两步(Case 3→4);两孩子都黑 ⇒ 只变色不上移或上溯(Case 2);兄弟红 ⇒ 先转一次改局面(Case 1)。

6. 易错点 + 老师原话

"在我找老大顶替的时候,我们不要改 color,不要改颜色。最后删掉的节点,是个红的,那就妥了。什么都不用干就妥了,为什么?因为黑高没有改变嘛,你删掉红节点,不改变任何人的黑高就没事。" — [稿:1-逐字稿:446]

"那我们要做的,你要想办法在删黑节点之前,你要在黑节点的路径上加一个黑节点。在一条 path 上面加一个黑,这样的话,你就可以很安全的把节点给去掉了,他黑高还是平的。" — [稿:1-逐字稿:449]

"关键是看 sibling 的颜色分 case。sibling 是红的话,我要干一件事情,swap colors and rotate,就是 sibling 和他 parent 这两个颜色换一下,然后转一下……什么问题都没有解决。" — [稿:1-逐字稿:452]

"还有的话我们快速过一下,因为不需要大家背下来。对于红黑树,你只要掌握它的性质,重要的性质就(够了)。" — [稿:1-逐字稿:452]

五个易错:

  1. 删度数 2 的节点时改颜色。必须"Keep the color",否则形状没变颜色却变了,会产生新的违反。
  2. 删红节点还去修复。删红什么都不用做,多做反而破坏。
  3. 以为 Case 1 解决问题。Case 1 只把局面转成 2/3/4,PPT 明确标注 "Still not solved"。
  4. 以为 Case 2 会旋转。Case 2 只变色 + 上移指针,不旋转。所以"最多 3 次旋转"里的 3 不包含 Case 2。
  5. 把"最多 3 次"当成"最多 3 步"。上溯可以有 步(每步变色 ),但旋转只有 ≤ 3 次。

7. 考试英文答题模板

Deletion. Reduce to deleting a node of degree 0 or 1: for a degree-2 node, replace its key by the largest key in its left subtree (or the smallest in its right subtree), keeping the colour, and delete the replacement node. - If the physically removed node is red, no property is violated and we stop. - If it is black, one black is missing on that path; let be the doubly-black position and its sibling. - Case 1 ( red): exchange colours of and , rotate at ; then fall into Case 2/3/4. - Case 2 ( black with two black children): colour red and move up to ; if was red, colour it black and stop. - Case 3 ( black, near child red, far child black): exchange colours of and its near child and rotate at ; then go to Case 4. - Case 4 ( black with a red far child): give the parent's colour, colour black and the far child black, then rotate at ; this restores the black-height and terminates.

Since Case 1 leads to one of 2/3/4, Case 3 leads to Case 4, and Case 4 terminates, deletion performs at most 3 rotations, in time.


1.2.8 红黑树 vs AVL:全面对比 【S 级】

1. 一句话本质

插入两者打平(都 ≤ 2 次旋转),删除红黑树完胜(≤3 vs );红黑树为此多付一个 parent 指针的空间代价,但换来了迭代实现和随机数据下的实际更快。

2. 定义与性质

[PPT:1下:161-166] 原文表:

Number of rotations

AVL Red-Black Tree
Insertion ≤ 2 ≤ 2
Deletion ≤ 3

老师的完整论述(本考点最重要的一段原话,建议整段背下来):

"重点是你要知道 red black tree 为什么受欢迎。为什么我们大家不用 AVL tree?一个很重要的原因,insertion 没有什么太大的优势,它旋转的次数都是要两次,只不过它用循环,它用递归,所以它快一点,但是它有代价,它多了 parent 指针。它的绝对优势是在 deletion 方面,deletion 的最坏,是要 次旋转,一路旋转上去的。而 red black tree 的旋转,最多就三次,你看前面的四个 case 涉及到的旋转,你就算从第一个 case 1 直走到第四个 case,它最多也就转三次。然后他就解决了问题,这个是很快的。但是,我们说在大型系统里面,我们不做删除,因为删除很烦。在这种情况下,red black tree 还有什么优势?它就没什么优势了。他没什么优势。实验表明就是纯随机数据下面的操作,red black tree 的总的操作的效率比 AVL tree 要稍微快。" — [稿:1-逐字稿:470]

2b. 五个维度的差异根源

维度 AVL RB 为什么
平衡判据 (高度差) 黑高相等 RB 允许高度差到 2 倍
树高 AVL 更矮 ⇒ 查找略快
插入旋转 打平
删除旋转 AVL 删除可能一路转到根;RB 的 Case 4 必然终止
额外空间 BF(1–2 bit/节点) color(1 bit)+ parent 指针(4–8 B/节点) RB 更"臃肿"
实现方式 通常递归 可迭代(靠 parent 指针) RB 省函数调用开销
适用场景 查找密集、删除少 插删混合、通用库 见 1.2.9

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 没有 AVL 与红黑树的对照条目,只在《红黑树》页说明红黑树是"自平衡的二叉搜索树",并列出工业用例。形式化地,两种树各自维护一个不变式:AVL 要求每个结点 ;红黑树要求 1.2.1 的四条性质。比较的指标定义为一次操作中结构修改的次数,即旋转次数,而不是比较次数或树高。两个不变式都蕴含 ,但常数不同,旋转次数的差别才是选择依据。

2.2 OI Wiki 校订|基本性质

  1. 不变式互不包含(incomparable invariants):AVL 树不必是红黑树,红黑树也不必是 AVL 树。为什么成立:红黑树允许路径长度相差二倍,AVL 把高度差限制在 1。例:根 10(B) 的右孩子为 15(B),左孩子 5(R) 的两个孩子为 3(B) 与 7(B),3 的左孩子为 1(R);红黑性质成立,但根的两侧高度为 3 与 1,,不是 AVL。
  2. 高度常数(height constants):AVL 的界是 ,红黑树是 ,后者更大。为什么成立:AVL 的递推给出斐波那契型最小规模,红黑树只约束黑高。例: 时 AVL 界约 5.6,红黑树界为 8。
  3. 插入旋转(insertion rotations):两者都至多 2 次。为什么成立:AVL 的单旋或双旋至多两次;红黑树的 Case 2 与 Case 3 各一次,Case 1 不转。例:插入 3 到 链,红黑树转 1 次,AVL 也转 1 次。
  4. 删除旋转(deletion rotations):AVL 最坏 次,红黑树至多 3 次。为什么成立:AVL 删除后平衡因子的修正可一路传到根;红黑树的 Case 4 一旦执行必终止。例:删黑叶触发 Case 1→Case 3→Case 4,共 3 次旋转。
  5. 空间(space):红黑树每结点多存 color 与 parent 指针,AVL 只存平衡因子。为什么成立:红黑树用 parent 指针支持迭代上溯。例: 个结点时 parent 指针按 8 B 计约 80 MB,而 2 bit 的平衡因子约 2.5 MB。

2.3 OI Wiki 校订|与课堂口径的差异

OI 没有 AVL 对照条目,也没有"删除 ≤3 次旋转"这一结论;其《红黑树》页的"实际工程项目中的使用"只列 Linux 调度器与 epoll、Nginx 定时器、libstdc++ 与 OpenJDK 的 TreeMap,用来说明工业界采用红黑树,并未解释为何不用 AVL。因此本考点的全部对比数值按 PPT/Weiss,其中删除旋转界在 OI 侧无对应表述,按 Weiss 口径保留。考试以 PPT 的"插入都 ≤2、删除 AVL 为 而红黑树 ≤3"为准;引用 OI 时只能引它的工业用例,不能把"≤3"说成 OI 的结论。

3. 图示

同一个键集合 10, 5, 15, 3, 7, 11, 17, 6, 8(9 个节点)在两棵树里的形态对比:

AVL(严格平衡,h=3):                 RB(弱平衡,h=4):

           7                          10(B)
         /   \                      /      \
       3      11                 5(R)       15(B)
      / \    /  \                /   \      /    \
     1   5  10   15            3(B) 7(B) 11(R) 17(R)
     (示意)       \                  /  \
                  17               6(R) 8(R)

注意:AVL 的高度是 3( 的上界,实际 3),RB 的高度是 4( 的上界)。RB 更高,但每次插入/删除要改的指针更少。

删除时的操作量对比(这是核心差异的图形化):

AVL删一个节点(最坏):叶子──旋转──►父──旋转──►祖父──►...──►根|________N次旋转,每层都要重算BF________|×时间O(logN)RB删一个节点(最坏):x──Case1(转1次)──►Case3(转1次)──►Case4(转1次)──►结束|____________最多3次旋转____________|(中间的Case2只变色不旋转,可重复但不计入旋转)

4. 复杂度表

操作 AVL 时间 RB 时间 AVL 旋转 RB 旋转 AVL 额外空间 RB 额外空间
查找 Search 0 0 BF color+parent
插入 Insert ≤ 2 ≤ 2 BF 更新沿路径 color 更新沿路径
删除 Delete ≤ 3 BF 更新沿路径 color 更新沿路径
树高上界 — — — —
单节点额外存储 1–2 bit 1 bit + 1 指针 — — 小 大
实现风格 递归 迭代 — — — —
随机数据实测 稍慢 快约 10%+ — — — —

5. 典型例题

Q. (a) Compare AVL trees and red-black trees on the number of rotations needed for insertion and for deletion. (b) A student claims "red-black trees are faster than AVL trees because their height is smaller." Refute or correct this claim. (c) Under what workload does the advantage of red-black trees disappear?

解(中文逐步)

(a) 旋转次数对比

AVL RB
插入 ≤ 2(与 RB 相同) ≤ 2
删除 (最坏一路转到根) ≤ 3

结论:插入打平,删除红黑树有绝对优势。

(b) 反驳

该说法完全颠倒了:

  • 红黑树的树高上界是 ,AVL 是 ,红黑树更高();
  • 红黑树快的原因不是树高更小,而是改结构(旋转)的次数更少,尤其在删除时;
  • 另外它用迭代而非递归,省函数调用。

正确表述:"Red-black trees are generally faster not because they are shorter—they are actually taller than AVL trees—but because they require far fewer structural modifications, at most 3 rotations for a deletion versus for AVL."

(c) 优势消失的场景

当系统几乎不做删除时(大型数据存储的典型工作模式),红黑树的核心优势(删除 ≤ 3 次旋转)完全用不上,此时两者只剩:

  • 树高差异(AVL 略矮 ⇒ 查找略快)
  • 随机数据下红黑树约快 10%+(这是老师说的 Project 实验要验证的量)

此时用 AVL 也完全可以,甚至更省空间(没有 parent 指针)。

6. 易错点 + 老师原话

"它的绝对优势是在 deletion 方面,deletion 的最坏,是要 次旋转,一路旋转上去的。而 red black tree 的旋转,最多就三次……你就算从第一个 case 1 直走到第四个 case,它最多也就转三次。" — [稿:1-逐字稿:470]

"insertion 没有什么太大的优势,它旋转的次数都是要两次,只不过它用循环,它用递归,所以它快一点,但是它有代价,它多了 parent 指针。" — [稿:1-逐字稿:470]

"实验表明就是纯随机数据下面的操作,red black tree 的总的操作的效率比 AVL tree 要稍微快。" — [稿:1-逐字稿:470]

"那么个10%几到底是10%几?你们第一个 project 就让你们去做实验。你们去做实验,你们去试一试,看它到底快多少。" — [稿:1-逐字稿:473]

七个易错:

  1. 说"红黑树插入更快"。插入旋转次数都是 ≤ 2,红黑树只是用迭代实现省了点开销,且多了 parent 指针。优势在删除。
  2. 说"红黑树树高更小所以更快"。反了,红黑树更高。
  3. 说"红黑树是 AVL 的特例/改进版"。两者是并列的两种平衡定义,互不包含。
  4. 忘了 parent 指针这笔账。红黑树多 1 个指针的空间,在大数据量下很可观。
  5. 把" 次旋转"理解成""。AVL 删除是 次旋转、每次 ,总共 。
  6. 说"红黑树一定比 AVL 快"。只在随机数据/插删混合下快约 10%;查找密集时 AVL 可能更优。
  7. 把"最多 3 次旋转"当成"最多 3 步"。见 1.2.7 第 5 条。

7. 考试英文答题模板

Both AVL trees and red-black trees support search, insertion and deletion in . Their rotation counts differ: - Insertion: both need at most 2 rotations. - Deletion: AVL may need up to rotations propagated all the way to the root, whereas a red-black tree needs at most 3.

Therefore the decisive advantage of red-black trees lies in deletion. In return, a red-black tree stores one extra parent pointer per node and has a weaker height bound ( vs ), so it is not shorter; it is faster only because it performs fewer structural changes and can be implemented iteratively. On random data, red-black trees are reported to be roughly 10% faster overall.


1.2.9 大型系统不做删除时的取舍 【B 级】

1. 一句话本质

红黑树的全部优势都押在删除上;删得少,优势就没了——所以到底选哪个,先问工作负载里删除占多少。

2. 定义与性质

老师的三句递进([稿:1-逐字稿:470]):

  1. "在大型系统里面,我们不做删除,因为删除很烦。"
  2. "在这种情况下,red black tree 还有什么优势?它就没什么优势了。"
  3. "实验表明就是纯随机数据下面的操作,red black tree 的总的操作的效率比 AVL tree 要稍微快。"(约 10%+)

为什么"大型系统不做删除":

原因 说明
删除本身复杂 红黑树四种 case,AVL 要一路旋转;代码量大、易错
删除成本高 物理删除后空间碎片、索引重建代价远大于逻辑删除
数据多"只增不改" 日志、审计、时序数据、符号表大多只追加
逻辑删除替代 打删除标记(tombstone),不动树结构

决策表:

工作负载 推荐 理由
插删混合、通用库(std::map、TreeMap) 红黑树 删除 ≤ 3 次旋转
几乎只读、构建一次查多次 AVL 或红黑树均可 树高差不多,AVL 更省空间
几乎不删、纯随机 红黑树略快 ~10% 实测
海量数据落磁盘 都不用,用 B+ 树(1.2.10–1.2.13) 树高 ,I/O 少

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 没有讨论"删除占比"与结构选型的关系,其《红黑树》页只列工业用例。形式化地,把选型写成带约束的最小化问题:给定工作负载中查找、插入、删除的比例 ,,以及内存预算 ,在满足 的平衡树中选择使期望代价 最小者,其中 对 AVL 含 次旋转、对红黑树至多 3 次,空间项按每结点额外字节计入。删除比例 是唯一的杠杆: 时两种树的差别只剩树高与空间。

2.2 OI Wiki 校订|基本性质

  1. 优势定位(advantage localisation):红黑树相对 AVL 的结构性优势只在删除。为什么成立:插入旋转两者都 ≤2,查找的渐近阶相同,只有删除旋转次数不同。例: 次删除,AVL 最坏每次约 14 次旋转,红黑树每次 ≤3 次。
  2. 收益随删除比例增长(linear in ):删除带来的旋转收益与 成正比, 时收益为 0。为什么成立:总代价是三项的加权和,删除项系数为 。例: 的只增负载下,红黑树的"≤3"一次也用不上。
  3. 空间代价(space overhead):红黑树每结点多一个 parent 指针与 1 bit 颜色。为什么成立:迭代实现需要父指针,AVL 的平衡因子可嵌在结点里。例: 结点、指针 8 B,多出约 80 MB。
  4. 磁盘情形(disk regime):数据落磁盘时两种二叉平衡树都不选,改用 B+ 树。为什么成立:磁盘按块传输,代价由 I/O 次数决定,B+ 树的高扇出把高度降到 。例: 条记录,B+ 树约 5 次 I/O,二叉树约 30 次。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的《红黑树》页只说明红黑树在 Linux、Nginx、STL、JDK 中的内存索引用法,没有"大型系统不做删除"这一工作负载前提,也没有与 AVL 的取舍分析;因此本考点的结论(删除少则红黑树优势消失、磁盘索引改用 B+ 树)来自 PPT 与课堂论述,考试按 PPT。OI 的用例与 PPT 不冲突:它证明的是"内存型有序容器普遍选红黑树",而 PPT 讨论的是"删除占比决定选型",两者层面不同。答题时先给工作负载比例,再谈旋转与空间,最后才提 B+ 树。

3. 图示

选择一棵平衡树│┌───────────┴───────────┐││数据在内存?数据在磁盘?││┌─────┴─────┐▼││B+Tree(M阶)删除频繁?几乎不删│││├─节点=一个磁盘块▼▼├─树高=O(log_MN)RBTreeAVL/RB└─非叶只存索引(≤3旋转)都行(1.2.10–1.2.13)

老师原话的完整链条:

"真正大型的系统里面,我们这两种树都不用的。……你需要用到大量的查找,但是,我们是从来不用这个二叉搜索树来做数据的查找维护的,我们用什么?用数据库,用的是什么?数据库用的是 B tree。" — [稿:1-逐字稿:473]

4. 复杂度表

场景 最优选择 关键指标
内存 + 频繁删 RB Tree 删除旋转 ≤ 3
内存 + 只增不删 AVL 或 RB 空间、树高
内存 + 纯随机 RB(快 ~10%) 常数因子
磁盘/海量 B+ Tree 磁盘 I/O 次数
纯查找、静态 排序数组 + 二分 缓存友好

5. 典型例题

Q. A key-value store keeps an in-memory index of 10 million records. The workload is: 70% lookups, 30% inserts, and essentially no deletes (deleted records are tombstoned). Would you choose an AVL tree or a red-black tree? Justify using rotation counts and space, and state what you would use instead if the index lived on disk.

解(中文逐步)

  1. 删除为 0 ⇒ 红黑树"≤3 次旋转"的优势完全不体现。
  2. 插入占 30% ⇒ 两者都是 ≤ 2 次旋转,打平。
  3. 查找占 70% ⇒ 树高是关键。AVL 的上界 小于 RB 的 ⇒ AVL 略优(平均也略矮)。
  4. 空间 ⇒ RB 每节点多一个 parent 指针。1000 万节点 × 8 B = 80 MB 的额外开销,而 AVL 只需 2 bit/节点 ≈ 2.5 MB ⇒ AVL 更省。
  5. 结论:在"无删除 + 查找为主"的场景下,AVL 至少不差,且省 80 MB;若还要考虑实现简单与迭代,可用 RB,差异在 10% 以内。
  6. 若索引在磁盘上 ⇒ 两者都不用,改用 B+ 树: 阶多叉使树高降到 ,一次 I/O 读一个节点,把 次寻道压到 3–4 次。

6. 易错点 + 老师原话

"但是,我们说在大型系统里面,我们不做删除,因为删除很烦。在这种情况下,red black tree 还有什么优势?它就没什么优势了。他没什么优势。" — [稿:1-逐字稿:470]

"通常的大型的数据存储里头,我们一般也不删除。因为删除太麻烦了,不划算,又复杂。" — [稿:1-逐字稿:371](讲 AVL 时也说过同样的话,说明这是他一贯观点)

三个易错:

  1. 无脑答"红黑树最好"。题目一旦给出"不做删除",就要把结论反过来。
  2. 忽略空间。parent 指针不是免费的。
  3. 忘了还有 B+ 树这个选项。题目问"磁盘索引"时答红黑树/AVL 是典型失分点。

7. 考试英文答题模板

The advantage of red-black trees is concentrated in deletion (≤ 3 rotations vs for AVL). In many large systems deletions are rare or are replaced by logical deletion, so this advantage disappears. In that regime an AVL tree is at least as good, and it is more space-efficient because it stores only a balance factor instead of a parent pointer plus a colour. Measured on random data, red-black trees remain only about 10% faster overall. For indexes that live on disk, neither tree is used; a B+ tree is preferred because its high fan-out reduces the height to and hence drastically reduces disk I/O.


1.2.10 B+ 树的 M 阶结构 【S 级】

1. 一句话本质

B+ 树是"多叉的、绝对平衡的搜索树":内部节点只当目录(存索引),真实数据全在最底下一层,叶子之间用链表串起来。

2. 定义与性质

【Definition】[PPT:1下:175-181]

A B+ tree of order M is a tree with the following structural properties: (1) The root is either a leaf or has between 2 and M children. (2) All nonleaf nodes (except the root) have between ⌈M/2⌉ and M children. (3) All leaves are at the same depth. Assume each nonroot leaf also has between ⌈M/2⌉ and M children.

PPT 标注气泡:

"And M − 1 smallest key values in the subtrees except the 1st one." —— 内部节点的每个键,是其右侧指针所指子树的最小值(第一个子树不存)。

再配上 [稿:1-逐字稿:518-524] 的现场解释:

"所有的 interior node 内部节点,它存的是 M 个指针和 M 减一个值。值是什么?12 指的是它右边的指针指向的数组里面的最小值。……第一个不存,第一个没有必要存,它就存后面 M 减 1 个数组的最小值。"

四条性质的工程含义:

性质 作用
(1) 根 2–M 个孩子 保证树不会退化成一条链
(2) 非根内部节点 ⌈M/2⌉–M 个孩子 保证至少半满,从而树高 ≤
(3) 叶子同层 绝对平衡(没有"红黑"那种两倍差)
数据全在叶子 内部节点小 ⇒ 一个磁盘块能装更多索引 ⇒ 扇出大 ⇒ 树矮

内部节点 vs 叶子的分工(核心区分):

内部节点(Interior / Internal node) 叶子节点(Leaf)
存什么 只有索引(子树最小值),不存数据 全部真实数据(或数据块的地址)
键的数量 个孩子的节点有 个键 存 ≤ M−1 个键值
指针 个指向孩子的指针 指向下一个叶子的链表指针
图示标记 圆圈/椭圆 方框(数组)

命名来源:"+" 就是在 B 树上多了一条叶子链。老师原话:

"原始的 B 树和 B+ 树的区别在于:B+ 树所有真实数据都在最底层,中间的都是索引……B 树是所有的节点都存的是真实数据,所以你就会想到 B 树是比较省空间的。" — [稿:1-逐字稿:578-584]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录 B+ 树的独立条目,以下按教材(Weiss)口径整理。

设 为阶(order),。B+ 树(B+ tree)是满足下列条件的有根多叉搜索树:每个内部结点 有 个孩子与 个键;根或为叶,或满足 ;每个非根内部结点满足 ;所有叶在同一深度;每个非根叶含 到 个键,叶之间用 next 指针按升序相连。内部结点的第 个键等于第 个孩子子树中的最小键,第一个孩子没有对应键;全部数据记录(或记录地址)只存于叶,内部结点只存索引。

2.2 OI Wiki 校订|基本性质

  1. 键孩子换算(key-child identity): 个孩子的结点恰有 个键。为什么成立: 个孩子把键域切成 段,段与段之间只需 个分隔键。例: 的根有 4 个孩子时存 3 个键,如 21|48|72。
  2. 半满(half-full):非根内部结点的孩子数不少于 。为什么成立:分裂把溢出结点分成两片,每片至少保留一半的孩子,否则分裂会立刻产生下溢。例: 时孩子下界为 3,键下界为 2。
  3. 绝对平衡(perfect balance):所有叶同深度。为什么成立:插入只在叶上发生,只有根分裂才使整棵树长高一层,所有叶同步加一。例: 连续插入使根分裂时,全部叶由深度 2 变为 3。
  4. 索引冗余(index redundancy):内部键是其右侧子树最小键的副本。为什么成立:路由只需比较键的大小,副本让"走哪个孩子"成为一次比较。例:根 21|48|72 中,21 是第二个孩子子树的最小键,第一个孩子子树的最小键不存。

2.3 OI Wiki 校订|与课堂口径的差异

与 OI Wiki 的收录情况:OI Wiki 的数据结构部分收录了红黑树与左偏红黑树,并有 B 树相关页面,但没有 B+ 树的独立条目,也没有阶、键数区间、叶链的统一定义。因此本考点的定义按教材(Weiss)与 PPT,不引 OI 链接。阶的口径需要注意:本文与 PPT 把阶定义为"最大孩子数 ",键上限因而是 ;部分资料把阶定义为"最大键数",同样的树会被叫作 阶。考试按 PPT 的" 个孩子、 个键",并在答案里写出这句换算。 时非根内部结点键数为 。

3. 图示

(一)PPT slide 9 的 4 阶 B+ 树(2-3-4 树)完整结构

next2148721215253141598491148111213151819212425263138414346484950596872788488919299索引(内部结点,键为副本)数据(叶子结点,同深度,next 指针成链)
图 1 M=4 的 B+ 树(2-3-4 树):根 3 个键 / 4 个孩子;5 个内部结点;12 片叶全在同一层。内部键是右侧子树最小键的副本(48 复制自 [48,49,50],72 复制自 [72,78]),第一个孩子没有对应键。

关键观察:

  • 根有 4 个孩子(= M = 4)✔ 满足性质 (1) 的 。
  • 内部节点 12|15(2 个键、3 个孩子)、25|31|41(3 键 4 孩子)、59(1 键 2 孩子)——都 ≥ ⌈4/2⌉ = 2 个孩子 ✔。
  • 所有叶子在同一层 ✔。
  • 21 是"右边那棵子树的最小值"?——不,21 是第 2 个指针所指子树的最小值,即 [21,24] 的最小值 21 ✔;48 是第 3 个指针所指 [48,49,50] 的最小值 ✔;72 是 [72,78] 的最小值 ✔。第 1 个指针(指向 [1,4,8,11])没有对应的键 ✔ 与 PPT 气泡一致。
  • 叶子键数:[1,4,8,11] 有 4 个(= M−1 上限),[12,13] 有 2 个,[59,68] 有 2 个——注意 [12,13] 有 2 个键 = 1 个孩子? 不是,叶子不按"孩子"算。叶子的键数下界是 ⌈M/2⌉−1 = ⌈4/2⌉−1 = 1。所以 2 个键满足下界 ✔。

(二)M 阶结构的一般形状(区分内部/叶子)

K1K2…Kr−1根:2 ≤ r ≤ M 个孩子K′1…K′s−1…K″1…K″s′−1内部:⌈M/2⌉ ≤ s ≤ M 个孩子leafleafleaf…leafleafleafleaf叶:⌈M/2⌉−1 ≤ 键数 ≤ M−1,全部同深度
图 2 M 阶 B+ 树的骨架:内部结点(上两排)只放键与指针,键数是孩子数减一;叶子(最下排)放全部数据并带 next 指针,所有叶同深度。根的孩子数放宽到 2 ~ M,其余内部结点须 ⌈M/2⌉ 以上。

(三)为什么 B+ 树比 B 树更适合做磁盘索引

B 树:键与数据混排k₁ d₁k₂ d₂k dk d数据分散在各层,内部结点也带数据⇒ 结点更大、扇出更小B+ 树:数据全在叶子k₁k₂i₁i₂i₃ddd内部只存索引(小)⇒ 扇出大;数据同层 ⇒ 查找深度恒定叶链 ⇒ 范围查询顺着走,不回根
图 3 两种结构的存储分布差异:B 树的每个结点都带数据,一次磁盘 I/O 读到的键数因此变少;B+ 树把数据全部下沉到叶子,内部结点只剩索引,同样大小的磁盘块能装下更多键,树更矮、I/O 更少。
  • 内部节点更小 ⇒ 一个磁盘块装更多键 ⇒ 扇出更大 ⇒ 树更矮 ⇒ I/O 更少;
  • 数据集中在一层 ⇒ 任何查找都走相同的深度,性能可预测;
  • 叶子链 ⇒ 范围查询(WHERE x BETWEEN a AND b)只需找到起点再顺着链走,不用回到根。

4. 复杂度表

量 公式 说明
阶 Order 每个节点最多 M 个孩子
根的孩子数 除非根就是叶子
非根内部节点孩子数 至少半满
树高(最坏) PPT:Depth(M,N)=O(⌈log_⌈M/2⌉ N⌉)
查找 ,与 M 无关 每层用二分, 与 抵消
插入 PPT [PPT:1下:276]
查找 I/O 次数 每层一次磁盘读
最优 M 3 或 4(课件 Note) 实际磁盘系统用几百

5. 典型例题

Q. Consider a B+ tree of order 4 (a 2-3-4 tree). (a) What is the maximum and minimum number of keys in a non-root internal node? (b) What is the maximum and minimum number of keys in a non-root leaf? (c) How many disk accesses are needed to find a key in a B+ tree of order 200 storing records? (d) Why is the data stored only in leaves?

解

(a) 非根内部节点

  • 孩子数 ;
  • 键数 = 孩子数 − 1 ⇒ 。

(b) 非根叶子

  • 按 PPT "each nonroot leaf also has between ⌈M/2⌉ and M children",叶子也被赋予"孩子数"这一称呼,翻译成键数就是 到 个键值?⚠️ 这里 PPT 的表述有问题,见 1.2.11 的专门讨论。
  • 按通行定义(也是 PPT 图示实际呈现的):叶子键数 。

(c) 磁盘访问次数

时,,树高最坏 层。加上读数据一次,共 约 5–6 次 I/O。

对比二叉树: 次 I/O。少了一个数量级。

(d) 为什么数据只在叶子

  1. 内部节点必须小:内部节点只存 key + 指针,才能让一个磁盘块(如 4 KB)装下几百个键,扇出上百,树高降到 3–4 层。
  2. 查找路径长度一致:所有 key 都在同深度,最好/最坏查找代价相同,性能可预测(数据库 SLA 需要这个)。
  3. 范围查询高效:叶子链让 BETWEEN 查询顺序扫描,无需回溯。

代价:数据只存一份在叶子,内部索引是冗余的额外空间——这正是老师说的"B 树是省空间的"。

6. 易错点 + 老师原话

"它不是个二叉搜索树了,它是个多叉树,它是个 M 叉树。" — [稿:1-逐字稿:509]

"这棵树的树高不可能很高,是为什么?在这里决定的:根节点不能是单独一支,根节点至少有两个孩子,最多有 M 个孩子;所有的中间节点除了根以外,不能少于 M/2 个孩子。……所以你想它树高肯定是 log 数量级的。" — [稿:1-逐字稿:512]

"所有的真实数据都是放在叶子节点上的。……在这个里头,我们说它最多就四个嘛,有的叶子节点存满了,存了四个数据,有的不太满,不太满,它也得至少有两个,它不能只有一个。" — [稿:1-逐字稿:515]

六个易错:

  1. 把 M 阶当成"M 个键"。是 M 个孩子、M−1 个键。见 1.2.11。
  2. 以为内部节点也存数据。B+ 树内部只存索引;B 树才每层都存数据。
  3. 以为叶子可以不同层。性质 (3) 要求同深度(绝对平衡),这是 B+ 树与红黑树的根本差别。
  4. 忘了叶子链。没有叶子链就不是 B+ 树(是 B 树)。
  5. 把"根的特殊性"忘了。根可以只有 2 个孩子(甚至本身就是叶子),不受 ⌈M/2⌉ 约束。
  6. 分不清"键"与"孩子": 个孩子的内部节点有 个键(第一个孩子没有对应的"最小值"键)。

7. 考试英文答题模板

A B+ tree of order is a multi-way search tree satisfying: (1) the root is either a leaf or has between 2 and children; (2) every non-leaf node other than the root has between and children; (3) all leaves are at the same depth; and every non-root leaf holds a number of keys within the corresponding bound. All actual records (or pointers to them) reside only in the leaves; internal nodes store merely index keys — specifically, the minimum key of each child subtree except the first — together with child pointers, and the leaves are linked together to support range queries. Because internal nodes are small, the fan-out is large, so the height is only and a search costs disk accesses.


1.2.11 键数上下界 ⌈M/2⌉−1 ~ M−1 【S 级】

1. 一句话本质

B+ 树的界有两套说法:说"孩子"就是 ,说"键"就是 。考试问什么就答什么,别混。

2. 定义与性质

核心换算(必须刻进脑子):

理由: 个孩子被 个键隔开。所以

对象 孩子数 键数
根(非叶)
非根内部节点
叶子 (不适用)

为什么键数上限是 :M 个指针最多把空间切成 M 段,M 段只需 M−1 个分隔键。

为什么下限带 :半满要求是"孩子至少一半",而孩子比键多 1,所以键的下限要比"一半"少 1。

⚠️ PPT slide 9 的表述问题(这是本讲最需要澄清的一处):

(2) All nonleaf nodes (except the root) have between ⌈M/2⌉ and M children. Assume each nonroot leaf also has between ⌈M/2⌉ and M children. — [PPT:1下:178-181]

这里把"children"这个词套用到叶子上,是 PPT 的措辞不严谨:

  • 叶子没有孩子,它只有键;
  • 正确的读法应是"每个非根叶子有 到 个键值"(字面),或" 到 个键"(按"孩子−1"换算)。

PPT 图示(M=4)的逐叶核对——这就是判断"哪种读法才对"的关键证据:

叶子 键数 字面读法下限 2 换算读法下限 1 换算读法上限 3
[1,4,8,11] 4 ✔ (≥2) ✔ ✘ (4 > 3)
[12,13] 2 ✔ ✔ ✔
[15,18,19] 3 ✔ ✔ ✔(取等)
[59,68] 2 ✔ ✔ ✔
[91,92,99] 3 ✔ ✔ ✔
  • 字面读法(叶子 2~4 个键):全表 ✔ 全过;
  • 换算读法(叶子 1~3 个键):[1,4,8,11] 的 4 个键超上限,✘。

所以 PPT 这张图的实际约定是"叶子 2~4 个键",即把 M=4 当成"键数上限 = M"而不是"M−1"。这与通行教材(CLRS/Weiss)的" 阶 = 个孩子 = 个键"不一致——PPT 的图更接近把 "order 4" 理解为"4 个键、5 个孩子"的 5 阶树。

考试怎么办:

  1. 题目若写 "a B+ tree of order " 且没给图,用通行定义:键数 ;
  2. 题目若给了图,先数叶子键数的最小值与最大值,反推题目用的是哪套约定,再答题;
  3. 无论哪种,答题时把换算过程写出来(" children keys"),阅卷老师能看出你会。

给出 M=5 与 M=7 的具体值(这两个值考试最常出现):

M 孩子数范围(非根内部) 键数范围(非根内部) 键数范围(根) 键数范围(叶子)
3 2 ~ 3 1 ~ 2 1 ~ 2 1 ~ 2
4 2 ~ 4 1 ~ 3 1 ~ 3 1 ~ 3
5 3 ~ 5 2 ~ 4 1 ~ 4 2 ~ 4
6 3 ~ 6 2 ~ 5 1 ~ 5 2 ~ 5
7 4 ~ 7 3 ~ 6 1 ~ 6 3 ~ 6
8 4 ~ 8 3 ~ 7 1 ~ 7 3 ~ 7

M=5 的验算: 个孩子(下界);键 = 3−1 = 2(下界);键上限 = 5−1 = 4。 M=7 的验算: 个孩子(下界);键 = 4−1 = 3(下界);键上限 = 7−1 = 6。

注意 M 为奇数时 是"进一": 而不是 2.5, 而不是 3.5。这是最容易算错的地方。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录 B+ 树的独立条目,以下按教材(Weiss)口径整理。

对结点 记 为孩子数、 为键数。B+ 树的合法性由三条不等式与一条等式给出:;根若为内部结点则 ;每个非根内部结点满足 ;每个非根叶满足 。把等式代入不等式即得键数区间:非根内部结点与叶同为 ,根为 。 表示向上取整, 为奇数时必须进一。

2.2 OI Wiki 校订|基本性质

  1. 换算恒等式(key-child identity):。为什么成立: 个孩子由 个键隔开,键数不可能等于孩子数。例: 的结点有 3 个孩子时存 2 个键,如 20|40。
  2. 非根下界(non-root lower bound):键数不少于 。为什么成立:孩子数下界是 ,减去 1 即键下界。例: 得 , 得 。
  3. 上限(upper bound):键数不多于 。为什么成立: 个指针最多切出 段,段间只需 个键。例: 的结点最多 4 个键,10|20|30|40。
  4. 根例外(root exception):根非叶时键数为 到 。为什么成立:根只要求 ,允许只有 2 个孩子即 1 个键。例: 的根 40 带两个孩子合法,而非根结点只有 1 个键就下溢。
  5. 叶界(leaf bound):非根叶的键数区间与非根内部结点相同。为什么成立:叶也受"至少半满"约束,只是没有孩子,界直接落在键上。例: 的叶 10|15 有 2 个键,合法;10 只有 1 个键,非法。

2.3 OI Wiki 校订|与课堂口径的差异

与 OI Wiki 的收录情况:OI Wiki 没有 B+ 树条目,其 B 树与红黑树页面均不给出 B+ 树的键数区间,故本考点的公式按 Weiss/PPT,不能引 OI 为依据。需要点明 PPT 的一处措辞问题:原文写"每个非根叶也有 到 个 children",叶没有孩子,按 换算应读作键数 。PPT 的 图示中叶 [1,4,8,11] 有 4 个键,超过 ,说明该图实际按"键上限 "绘制,与"order 4"的标注冲突。考试若题目给了图,先数叶的键数反推约定;若未给图,一律用 , 即 。

3. 图示

(一)M=5 的合法节点形状

非根内部节点:键数2~4,孩子数3~5合法(键=2,孩子=3)合法(键=4,孩子=5)┌────┬────┐┌────┬────┬────┬────┐│20│40││10│20│30│40│└─┬──┴─┬──┴─┬─┘└─┬─┴─┬─┴─┬─┴─┬─┴─┬─┘▼▼▼▼▼▼▼▼3个孩子5个孩子非法(键=1,孩子=2)←半满以下!┌────┐│20│└─┬──┴─┬─┘▼▼2个孩子<⌈5/2⌉=3✗

(二)M=7 的合法节点形状

非根内部节点:键数3~6,孩子数4~7┌───┬───┬───┬───┬───┬───┐│10│20│30│40│50│60│←6个键(上限=M−1)└─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┘▼▼▼▼▼▼▼7个孩子(上限=M)┌───┬───┬───┐│20│40│60│←3个键(下限=⌈M/2⌉−1=4−1=3)└─┬─┴─┬─┴─┬─┴─┬─┘▼▼▼▼4个孩子(下限=⌈M/2⌉=4)

(三)"键 = 孩子 − 1"的直观图

┌────┬────┬────┬────┐│K₁│K₂│K₃│K₄│4个键└─┬──┴─┬──┴─┬──┴─┬──┴─┬─┘│││││▼▼▼▼▼P₀P₁P₂P₃P₄5个指针/孩子└────────────5=4+1───────────┘P₀指向<K₁的子树;Pᵢ指向[Kᵢ,Kᵢ₊₁)的子树;Kᵢ=Pᵢ所指子树的最小值(PPT气泡)

4. 复杂度表

M 根键数 非根内部键数 叶子键数 树高底
3 1–2 1–2 1–2
4 1–3 1–3 1–3 2
5 1–4 2–4 2–4 3
7 1–6 3–6 3–6 4
100 1–99 49–99 49–99 50
结论 公式
键数 = 孩子数 − 1
键数上限
键数下限(非根)
根键数下限 (根非叶且若只有 2 孩子)
分裂后两半的键数 与 (见 1.2.12)

5. 典型例题

Q. (a) For a B+ tree of order 5, state the minimum and maximum number of keys in a non-root internal node, in the root, and in a non-root leaf. (b) Same question for order 7. (c) A non-root internal node of an order-9 B+ tree has 3 keys. Is it valid? (d) How many keys can a node of an order-M B+ tree hold temporarily during insertion?

解

(a) M = 5

  • 。
  • 非根内部节点:孩子 ⇒ 键 。
  • 根:孩子 ⇒ 键 (根可以只有 2 个孩子)。
  • 叶子:键 。

(b) M = 7

  • 。(不是 3.5,必须进一)
  • 非根内部节点:孩子 ⇒ 键 。
  • 根:键 。
  • 叶子:键 。

(c) M = 9,内部节点 3 个键

  • 个孩子(下界)⇒ 键下界 。
  • 3 个键 < 4 ⇒ 不合法(underflow)。

(d) 插入时的临时超额

PPT [PPT:1下:268] 的算法写着 while (this node has M+1 keys),说明实现时必须预留 M+1 个槽位:

"你要知道每个节点虽然是度 M,但是你要开 M+1 的空间。因为你是先插进去才判断它溢出的,你得让它先能插得进去。" — [稿:1-逐字稿:563]

即:上限 M−1,插入后临时可达 M,M+1 是"溢出判定阈值"(PPT 用 M+1 keys 表示"超过 M−1"这种说法在实现上略有差异,实际判断是 keys.size() > M-1)。

6. 易错点 + 老师原话

"它最多就四个嘛,有的叶子节点存满了,存了四个数据,有的不太满,不太满,它也得至少有两个,它不能只有一个。" — [稿:1-逐字稿:515](老师对 M=4 的图示说"至少有两个"——这正是"键数下限 = 而不是 "的口语版,与 1.2.11 的正式公式有 差异,属于 PPT 图的约定)

"所有的中间节点不能少于 M/2 个孩子。" — [稿:1-逐字稿:512](注意老师说的是孩子,不是键)

八个易错:

  1. 把 M 当成键数上限。M 是孩子上限,键上限是 M−1。
  2. 没进位。 是 3,不是 2。
  3. 忘了减 1。孩子下界 ⇒ 键下界 。
  4. 把根的下界套用非根的公式。根可以只有 1 个键(2 个孩子)。
  5. 把"孩子"和"键"混着答。题目问 keys 就答 M−1,问 children 就答 M。
  6. 奇偶 M 不分开算。M 偶数时 (如 M=4 → 2);M 奇数时要进一(M=5 → 3,M=7 → 4)。
  7. 以为叶子可以只有 0 个键。非根叶子至少 个。
  8. 看到 PPT 图示 [1,4,8,11](4 个键,M=4)就以为上限是 M。那张图实际是 5 阶画法,与"order 4"标注冲突——以公式为准,见下方不一致说明。

7. 考试英文答题模板

For a B+ tree of order : the number of children of a node is at most and the number of keys is therefore at most . A non-root internal node has between and children, hence between and keys; the same bound applies to a non-root leaf. The root, if it is not a leaf, has between and children, i.e. between and keys. For this gives keys per non-root node and for the root; for it gives keys per non-root node and for the root.


1.2.12 插入分裂与树高增长 【A 级】

1. 一句话本质

先插进去,插爆了就一分为二,把中间那个键塞给父亲;父亲也爆就继续往上,一直到根爆了——生成新根,树高 +1。B+ 树是自底向上长高的。

2. 定义与性质

PPT [PPT:1下:262-276] 伪代码(原文):

Btree Insert ( ElementType X, Btree T )
{
    Search from root to leaf for X and find the proper leaf node;
    Insert X;
    while ( this node has M+1 keys ) {
        split it into 2 nodes with ⌈(M+1)/2⌉ and ⌊(M+1)/2⌋ keys, respectively;
        if (this node is the root)
            create a new root with two children;
        check its parent;
    }
}

逐句翻译:

伪代码 含义 注意
Search from root to leaf 从根走到应插入的叶子 次 I/O
Insert X 先无条件插入(可能临时超限) 需要预留 M 个槽位
while (has M+1 keys) 溢出则循环处理 向上传播
split into 2 nodes with ⌈(M+1)/2⌉ and ⌊(M+1)/2⌋ PPT 按 B 树写的键数;B+ 树叶分裂应读作左 、右 ,并复制右片首键 内部节点才把中间键移出并上移
if (is root) create new root 根也爆 ⇒ 新根,树高 +1 自底向上长高
check its parent 继续向上检查 —

两个关键数字(不同教材讲法不同,按 PPT):

  • 叶子分裂: 个键(原 个 + 1 个新键)劈成左 个、右 个,复制右片最小键到父结点,右片保留它。 时为 2 + 3,复制第 3 个键。

PPT 写的是"分裂成 和 个键"。这个公式来自 B 树:中间键被移到父结点,不留在任何一片,所以两片之和比总键数少 1;B+ 树的叶分裂不移动数据键,两片之和必须等于 。按 代入 PPT 公式得 3 + 3 = 6,与实际 5 个键矛盾,正说明两个模型不能混用。考试写"左 、右 、复制右片首键"即可。

  • 内部节点分裂: 个键、 个孩子,分裂后中间键上移到父节点,左右各 与 个键。

老师的实操版:

"它爆了怎么办?我们就去看一下,他的 parent 还有没有空……把这个节点给它一分为二,劈开,劈开以后,对于他的 parent 来说,他多了一个子树,他还能多吗?我们发现他没满,他可以多。" — [稿:1-逐字稿:545]

"然后我就继续调整他的 parent……所以最多,也调整到根节点为止,然后做一个 split 就好了。" — [稿:1-逐字稿:548]

树高怎么增长(老师原话):

"这是有 om 的时间复杂度,是 log N 乘以 m……总之来说,我迅速的缩小了……这一爆了,内存溢出了,我们就把它一切为二,这种方法有可能让这棵树迅速的长高。" — [稿:1-逐字稿:551, 575]

两种策略的对比(老师重点讨论的):

策略 做法 优点 缺点
简单分裂(simple splitting) 溢出就分裂,不找兄弟借 代码极简 树可能快速长高,节点平均半满
借键调整(find sibling and adjust) 找有富余键的兄弟,借一个过来 节点更满,树更矮 代码复杂,需叶子链/父指针导航

老师的选择:

"First find a sibling with 2 keys and adjust. Keep more nodes full." "但是,你要问我怎么做两种方法,我怎么选?我肯定选闭着眼睛 split。因为你要算一下划算不划算,我要去找 sibling……我为什么要额外的去写它调整左右节点的事情?" — [稿:1-逐字稿:554, 557]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录 B+ 树的独立条目,以下按教材(Weiss)口径整理。

插入算法定义如下:输入为树 与键 ,先沿根到叶的路径找到应落下的叶,把 按序插入该叶;若该叶键数超过 则溢出,把它分裂成左右两片,两片键数都落在 内;叶分裂时把右片的最小键复制一份上移到父结点,右片仍保留该键;内部结点溢出时把中间键移出并上移,左右两片各得一部分键;若根溢出,则新建一个根,把分裂得到的两片作为它的两个孩子。循环直到某层不再溢出。

2.2 OI Wiki 校订|基本性质

  1. 自底向上长高(bottom-up growth):只有根分裂使树高加 1。为什么成立:非根分裂只把一片变两片、把键上移,层数不变;根分裂新建一层,所有叶同步加一。例: 依次插入 10,20,30,40,50,插 50 时根分裂,树高由 0 变 1。
  2. 叶分裂复制(copy-up at leaves):叶分裂复制右片最小键上移,右片保留该键。为什么成立:数据只存叶,若把键"移走"就会丢记录;内部结点分裂才是移动。例: 的叶 10|15|20|30 插入 25 后溢出,分成 10|15 与 20|25|30,复制 20 上移,右片仍有 20。
  3. 传播上界(propagation bound):一次插入至多触发 次分裂, 为树高。为什么成立:每层最多分裂一次,根分裂后循环终止。例: 插入 70 时叶、父、根依次分裂,最后新建根,共 3 次。
  4. 填充率(occupancy):随机插入下结点平均约 满。为什么成立:分裂阈值使结点在溢出瞬间被对半切开,长期分布偏向半满。例: 的结点平均约 3.4 个键,低于上限 4。
  5. 高度界(height bound):。为什么成立:每个非根结点至少 个孩子,第 层的叶数至少 。例:、 时 ,取 14 层。

2.3 OI Wiki 校订|与课堂口径的差异

与 OI Wiki 的收录情况:OI Wiki 没有 B+ 树条目,插入的分裂与复制语义只能按 Weiss/PPT,不能引 OI。本册正文已统一按 B+ 树语义书写:叶分裂把右片最小键复制上移、右叶保留该键(若写成"移动",20 就会从数据层消失,而 B+ 树的数据键只能存在于叶层)。 时 5 个键分裂为左 、右 ,复制 keys[2]。PPT 的 与 是针对 B 树(含被移走的中间键)的公式,直接套到 B+ 树叶分裂会多算一个键;考试按"左 、右 、复制右片首键"作答,并写出复制的是哪个键。

3. 图示

(一)一次完整分裂(顺序插入 10,20,30,40,50,M=5)

初始:一个叶子(也是根)insert10,20,30,40:┌──────────────────────┐│10│20│30│40│4个键=M−1✔└──────────────────────┘insert50:┌───────────────────────────┐│10│20│30│40│50│5个键>M−1⇒溢出!└───────────────────────────┘│split(左⌈6/2⌉=3,右⌊6/2⌋=2)▼┌─────────────────────────┐│30(根)│←中间键30上移└────┬──────────────┬──────┘▼▼┌──────────────┐┌────────────┐│10│20││40│50│←左右两半└──────────────┘└────────────┘└────叶子链────┘树高:0→1(+1)

(二)分裂向上传播到根(M=3,插入 18, 1, 19, 28, 70)

走查约定(先对齐口径)

  • 即 3 阶(order 3)B+ 树,俗称 2-3 树:每个结点最多 个键(key);内部结点(internal node)最多 3 个孩子指针(child pointer),非根内部结点至少 个键、2 个孩子。
  • 叶结点(leaf node)的键数区间按本册 1.2.11 取 ; 时为 ,所以分裂后出现只有 1 个键的叶(如 [16])仍然合法,不是下溢。
  • 内部结点的键是右子树的最小键(1.2.11 中 指向区间 ),所以走查时 就走第 个孩子。
  • 叶分裂是复制:把右片首键复制一份上移,右片保留它;内部结点分裂是移动:中间键上移后不再留在左右两片。

初始树(合法,可逐结点核对)

root:[22]根:1个键/2个孩子├─内部[11,16]2个键/3个孩子│├─叶[2,8]2个键│├─叶[11,12]2个键│└─叶[16,17]2个键└─内部[41,58]2个键/3个孩子├─叶[22,31]2个键├─叶[41,52]2个键└─叶[58,61]2个键
结点 键数 孩子数 判定
根 [22] 1 2 ✔ 根允许 1 个键
内部 [11,16] 2 3 ✔ 键数 ≤2、孩子数 ≤3
内部 [41,58] 2 3 ✔ 键数 ≤2、孩子数 ≤3
六个叶 各 2 — ✔ 落在

为什么起始叶写 [2,8]、[16,17] 而不是 [1,8]、[16,18]:这样 18、1、19、28、70 五次插入都是新键,叶分裂的「左 、右 、复制右片首键」只有一种写法,便于逐步核对。若叶中已有等值键,分裂点会依赖具体的重复键约定,但本节的树高结论不变。

第 1 步:插入 18(叶裂 → 父裂 → 传到根;根未裂,树高不变)

  1. 读入 18 → 定位: 走根的左孩子,进入内部 [11,16]; 走最右孩子,落到叶 [16,17](该叶覆盖 )。
  2. 插入叶:[16,17] → [16,17,18],3 个键 ,溢出。
  3. 叶分裂(复制):左片 [16]( 个键),右片 [17,18]( 个键);复制右片首键 17 上移,17 仍留在右叶。
  4. 父收到 17:内部 [11,16] 收到 17 后先变成 [11,16,17](3 个键 ,溢出),孩子数也由 3 变 4:[2,8]、[11,12]、[16]、[17,18]。
  5. 内部结点分裂(移动):三键 [11,16,17] 的中间键 16 上移,16 不再留在任何一片;左片 [11]、右片 [17]。注意:从叶复制上来的是 17,但内部结点裂开时上移的是它自己的中间键 16;17 留在右片 [17] 里当分隔键。孩子指针按原顺序切开:左片 [11] 带前两个 [2,8]、[11,12];右片 [17] 带后两个 [16]、[17,18]。
  6. 根收到 16:根 [22] → [16,22],2 个键 ,未溢出,停止。树高不变(根到叶仍是 2 条边)。
root:[16,22]├─内部[11]│├─叶[2,8]│└─叶[11,12]├─内部[17]│├─叶[16]│└─叶[17,18]└─内部[41,58]├─叶[22,31]├─叶[41,52]└─叶[58,61]

两个易错点

  1. 叶分裂复制,内部结点分裂移动:本步叶 [16,17,18] 裂开时,17 只是复制一份到父结点,右叶 [17,18] 里仍有 17;紧接着内部 [11,16,17] 裂开时,16 是移动上去的,左 [11]、右 [17] 都不再含 16。把两处都写成「上移」或都写成「复制」,都会丢键或重复键。
  2. 传到根 ≠ 树高 +1:这次分裂确实从叶传到了根,但根原来只有 1 个键,收到 16 后是 [16,22],2 个键没有超过 ,所以根没有分裂,树高不变。只有根在收到上移键后达到 3 个键、自己也要裂开时,才会新建一层根,树高才 +1(见第 4 步插入 28)。

第 2 步:插入 1(叶裂,父接住,不上传)

  1. 读入 1 → 定位: 走根左孩子, 走内部 [11] 的最左孩子,落到叶 [2,8]。
  2. 插入叶:[2,8] → [1,2,8],3 个键 ,溢出。
  3. 叶分裂(复制):左 [1]、右 [2,8];复制右片首键 2 上移,2 仍留在右叶。
  4. 父收到 2:内部 [11] → [2,11],2 个键 ,未溢出,停止。孩子变为 [1]、[2,8]、[11,12];树高不变。

此时左子树变为内部 [2,11] 带叶 [1]、[2,8]、[11,12],其余部分与上一步相同。

第 3 步:插入 19(叶裂,父接住,不上传)

  1. 读入 19 → 定位: 走根左孩子, 走内部 [17] 的右孩子,落到叶 [17,18](覆盖 )。
  2. 插入叶:[17,18] → [17,18,19],3 个键 ,溢出。
  3. 叶分裂(复制):左 [17]、右 [18,19];复制右片首键 18 上移,18 仍留在右叶。
  4. 父收到 18:内部 [17] → [17,18],2 个键 ,未溢出,停止。孩子变为 [16]、[17]、[18,19];树高不变。

第 4 步:插入 28(叶裂 → 父裂 → 根裂;新建根,树高 +1)

  1. 读入 28 → 定位: 走根右孩子 [41,58], 走最左孩子,落到叶 [22,31](覆盖 )。
  2. 插入叶:[22,31] → [22,28,31],3 个键 ,溢出。
  3. 叶分裂(复制):左 [22]、右 [28,31];复制右片首键 28 上移,28 仍留在右叶。
  4. 父收到 28:内部 [41,58] → [28,41,58],3 个键 ,溢出,继续上传。
  5. 内部结点分裂(移动):中间键 41 上移;左 [28] 带 [22]、[28,31],右 [58] 带 [41,52]、[58,61]。同样,叶复制上来的是 28,这里上移的是内部结点自己的中间键 41。
  6. 根收到 41:根 [16,22] → [16,22,41],3 个键 ,根也溢出。
  7. 根分裂(移动):中间键 22 上移;左 [16] 带原来的 [2,11]、[17,18],右 [41] 带刚裂出的 [28]、[58];新建根 [22],以 [16]、[41] 为两个孩子。树高由 2 变 3(根到叶由 2 条边变 3 条边)。
root:[22]├─内部[16]│├─内部[2,11]──叶[1]|[2,8]|[11,12]│└─内部[17,18]──叶[16]|[17]|[18,19]└─内部[41]├─内部[28]──叶[22]|[28,31]└─内部[58]──叶[41,52]|[58,61]

第 5 步:插入 70(叶裂,父接住,不上传)

  1. 读入 70 → 定位: 走根右孩子 [41], 走其右孩子 [58], 落到叶 [58,61]。
  2. 插入叶:[58,61] → [58,61,70],3 个键 ,溢出。
  3. 叶分裂(复制):左 [58]、右 [61,70];复制右片首键 61 上移,61 仍留在右叶。
  4. 父收到 61:内部 [58] → [58,61],2 个键 ,未溢出,停止。孩子变为 [41,52]、[58]、[61,70];树高仍为 3。

最终树

root:[22]├─内部[16]│├─内部[2,11]──叶[1]|[2,8]|[11,12]│└─内部[17,18]──叶[16]|[17]|[18,19]└─内部[41]├─内部[28]──叶[22]|[28,31]└─内部[58,61]──叶[41,52]|[58]|[61,70]

逐步状态汇总

插入 叶的变化 上移键(方式) 父 / 根的变化 树高
18 [16,17] → 左 [16],右 [17,18] 17(叶复制)→ 16(内部移动) 父 [11,16] → 左 [11],右 [17];根 [22] → [16,22] 不变(2)
1 [2,8] → 左 [1],右 [2,8] 2(叶复制) 父 [11] → [2,11];根不变 不变(2)
19 [17,18] → 左 [17],右 [18,19] 18(叶复制) 父 [17] → [17,18];根不变 不变(2)
28 [22,31] → 左 [22],右 [28,31] 28(叶复制)→ 41(内部移动)→ 22(根移动) 父 [41,58] → 左 [28],右 [58];根 [16,22] → 左 [16],右 [41],新根 [22] 2 → 3
70 [58,61] → 左 [58],右 [61,70] 61(叶复制) 父 [58] → [58,61];根不变 不变(3)

结论

插入 18 的分裂确实一路传到了根:叶 [16,17] 裂、父 [11,16] 裂、根 [22] 收到 16。但根只从 1 个键变成 2 个键,没有溢出,所以树高不变。一般规律是:分裂可以一路传到根,但只有根本身也溢出、需要新建根时,树高才 +1——本序列中插入 28 才是「根裂、树高 2→3」,插入 18 传到了根但根没裂,插入 70 只裂到父结点就停住了。因此「插入导致树高增长」是低频事件,不是每次分裂都会长高。

合法性复核:最终树中每个结点键数都不超过 ;每个内部结点的孩子数都等于键数 ;五次叶分裂复制上移的键(17、2、18、28、61)都仍留在右叶;三次内部结点分裂上移的键(16、41、22)都不在左右两片之中。

(三)M=5 时插入 25 的一次干净分裂

状态:根 [40,70](2 个键),其左叶子 [10,15,20,30](4 个键,满),现插入 25。

步骤1:找到叶子[10,15,20,30],插入25┌────────────────────────────┐│10│15│20│25│30│5个键>M−1=4└────────────────────────────┘↓溢出步骤2:split——劈成两片,并把右片的最小键20**复制**一份上移左[10,15]↑复制20[20,25,30]右(注意:20仍留在右叶里,叶层不能丢任何数据键)步骤3:把20的副本插入父节点(根)根[40,70]→[20,40,70],3个键≤4⇒未溢出,停止最终:┌────┬────┬────┐│20│40│70│←根3个键(上限4)└─┬──┴─┬──┴─┬──┘▼▼▼[10,15][20,25,30][其余叶子…]树高:不变(仍为1)

B+ 树与 B 树的分裂语义差别(这是本节最容易写错的一处):B 树的中间键移动到父结点,原结点不再保留它;B+ 树的全部数据键都在叶层,叶分裂只能把右片的最小键复制一份到父结点作为分隔键,右片必须原样保留这个键。所以 的叶子装入 5 个键后分裂为左 个、右 个,上移的是 keys[2](第 3 个,本例 20),它在右片中仍然存在。内部结点分裂才是"移动":中间键上移后不再留在原结点。

PPT 的公式 与 是按 B 树(含被移走的中间键)写的,直接套到 B+ 树叶分裂会多算一个键;考试按"左 、右 、复制右片首键"作答,并写出复制的是哪个键。

4. 复杂度表

项 复杂度 说明
查找插入位置 次 I/O 从根到叶
叶子内插入 数组内移动元素
单次分裂 移动键
分裂传播层数 最坏 一路到根
总插入时间 (PPT) PPT [PPT:1下:276]
树高增长频率 仅在根分裂时 +1 非常罕见
平均节点填充率 约 (随机插入) 理论结果

5. 典型例题

Q. (a) Insert 10, 20, 30, 40, 50, 60 into an initially empty B+ tree of order 5, showing every split. (b) After the above, insert 25 into the tree containing 10,15,20,25,30 — show the split and state whether the height increases. (c) In the worst case, how many splits can one insertion trigger?

解

(a) M=5,键上限 4

insert10:[10]insert20:[10,20]insert30:[10,20,30]insert40:[10,20,30,40]←4个键,恰好满insert50:[10,20,30,40,50]←5个键>4⇒溢出分裂:复制右片最小键30上移左[10,20],右[30,40,50](30留在右叶)┌──────┐│30│←新根(树高0→1)└──┬─┬─┘▼▼[10,20][30,40,50]insert60:叶子[30,40,50]→[30,40,50,60](4个,未溢出)┌──────┐│30│└──┬─┬─┘▼▼[10,20][30,40,50,60]

1 次分裂,树高 +1。

(b) M=5 时插入 25(叶子已满的情形)

设叶子为 [10,15,20,30](4 个键,正好 满),现插入 25:

①插入后:[10,15,20,25,30]共5个键>M−1=4⇒溢出②分裂(复制右片最小键20上移):左[10,15]|复制20↑|右[20,25,30](20仍留在右叶)③父节点原为[40,70](2个键),插入20的副本后变[20,40,70](3个键≤4,未溢出)┌────┬────┬────┐│20│40│70│←根3个键,未溢出└─┬──┴─┬──┴─┬──┘▼▼▼[10,15][20,25,30][其余叶子]结论:树高不变(仍是1)。

对比:若插入时根已是 [20,25,40,70](4 个键,满),上移一个键后就变 5 个 ⇒ 根溢出 ⇒ 新建根,树高 +1。所以"是否长高"取决于根当时有没有空位。

(c) 最坏分裂次数

一次插入最多触发 次分裂( = 树高),即每一层都分裂,最后根也分裂。因此 。

6. 易错点 + 老师原话

"麻烦是在于,比如说我要插入一,我找到他好像应该插入在这儿,但是它爆了。就内存溢出了,因为它最多它 order 3 嘛,它最多只能放三个。但是,你现在插入了第四个数,第四个数,它爆了怎么办?我们就去看一下,他的 parent 还有没有空。" — [稿:1-逐字稿:545]

"劈开四个数字,一批一批两个,然后去调整他的 parent,调整他的 parent,有纪要的话,继续调整他的 parent。然后所以最多,也调整到根节点为止。" — [稿:1-逐字稿:548]

"然后你要知道每个节点虽然是度 M,但是你要开 M+1 的空间。因为你是先插进去才判断它溢出的,你得让它先能插得进去。" — [稿:1-逐字稿:563]

七个易错:

  1. 先判断溢出再插入。必须是"先插入,再看是否溢出",所以数组要留 个位置。
  2. 叶分裂与内部结点分裂的语义不能混:叶分裂把右片最小键复制到父结点,右叶仍保留该键;内部结点分裂才把中间键移动到父结点,原结点不再保留它。
  3. 分裂后不检查父节点。必须继续上网,一直到某层不溢出或根溢出。
  4. 根溢出时忘了建新根。根分裂会新建一层,树高 +1。
  5. 以为叶分裂会丢键。叶分裂后两片键数之和仍等于分裂前的键数,父结点多出来的只是副本;把副本当成"移动",最右叶就会少一个数据键。
  6. 以为插入一定会长高。只有根分裂才长高,其余层分裂只是横向变宽。
  7. 不预留 M+1 空间导致数组越界(编程题必踩)。

7. 考试英文答题模板

Insertion. Search from the root to the appropriate leaf and insert the key there. If the node now holds more than keys it overflows; split it into two nodes holding and keys respectively, and promote the middle key to the parent. If the parent overflows, repeat the split-and-promote process. If the root overflows, split it and create a new root with two children, which increases the height by one. Since only a root split increases the height, a B+ tree grows bottom-up and remains perfectly balanced. A single insertion triggers at most splits and runs in time.


1.2.13 磁盘 I/O 权衡 【B 级】

1. 一句话本质

B+ 树存在的唯一理由:磁盘的一次读写以"块"为单位,所以让一个节点 = 一个块、节点里塞几百个键,把树高压到 3–4 层,把 30 次寻道变成 3 次。

2. 定义与性质

[纪要:1-conclusion] 三、给出的三条:

  • 多叉树结构:节点存储多个键值和指针,大幅降低树高,减少磁盘 I/O 次数。
  • 数据分层:B+ 树将所有真实数据存储在叶子节点,内部节点仅存储索引(子节点的最小值),叶子节点间通过指针相连。
  • 分裂开销权衡:分裂操作虽然可能导致树高增加,但由于磁盘读写特性(顺序读写块),其开销在可接受范围内,优于复杂的节点合并调整。

核心量化(考试可写):

具体算例:

参数 值
磁盘块大小 4 KB = 4096 B
键(如 int) 4 B
指针 8 B
每键开销 12 B
一个节点能装的键数
即
条记录 —
B+ 树高度 ⇒ 5 层
二叉树高度 层
I/O 次数比 5 : 30 = 1 : 6

M 是不是越大越好?—— 不是。这是本考点最反直觉的一处。

老师的完整推导:

"是不是因为 M 越大就越好?M 越大树高就越矮!最极端的情况我让 M 等于 N 行不行?……为什么大家不这么干?……如果分叉非常多的话,你要怎么往节点里面插?它是个数组,你往数组里面插,它最坏的时间复杂度是多少?O(M)。所以你往上走、往下走的时候,你的时间复杂度跟 M 是有关系的。所以 M 很大,是不好的。" — [稿:1-逐字稿:566, 572]

"这块的时间复杂度是 O(M) 的。对于 B+ 树的插入来说……这是有 O(M) 的时间复杂度,是 log N 乘以 M。log 乘以 M 的话就是 M 分母有一个 log M,当 M 很大的时候,这是递增函数,所以 M 太大的时候,不好的。" — [稿:1-逐字稿:572, 575]

公式化的推导:

  • 在 增大时递增( 从 3 到 342 时, 从 1.9 涨到 41.8);
  • 所以 M 太大 ⇒ 插入变慢。

但查找为什么不受 M 影响:

正好抵消!老师的原话:

"find 这件事情跟 M 是没有关系的。因为什么?find 虽然它的 depth 它的路径长度是 log N 除以 log M。但是,你在中间每个节点去 find 的时间复杂度并不是 O(M),而是二分查找是一个 log M。分子上一个 log M,分母上一个 log M,它们会抵消掉。查找的复杂度跟分叉是没有关系的。" — [稿:1-逐字稿:575]

所以 M 的取法(三股力量):

力量 倾向
减少 I/O(内存/缓存) M 越大越好
减少插入成本(数组移动) M 越小越好
减少节点数/树高 M 越大越好
课件结论 M = 3 或 4 最优([PPT:1下:281] "Note: The best choice of M is 3 or 4.")

为什么实际数据库用 M ≈ 300:课件说的 3/4 是理论最优(针对"每层 O(M) 数组操作"的模型);实际系统中"一次磁盘 I/O"的成本远高于"数组移动几百个元素",所以逐利结果是 M 尽量填满一个块。

"但是我要告诉大家,这并不意味着你最下面数组存储真实数据,数组只能存三个或者四个数据,那就不,那就没有什么意义了?数组可以很大,数组里面存的东西仍然可以。所以你真实的数据一大片存在某一个硬盘上,你的 B 树里面叶子节点存的是那块硬盘的索引,不一定都是真实的数据。" — [稿:1-逐字稿:575]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录 B+ 树的独立条目,以下按教材(Weiss)口径整理。

把磁盘代价模型写成形式定义:磁盘以块(block)为单位传输,块大小 字节;一个 B+ 树结点占一个块;每个键占 字节、每个指针占 字节,则阶满足 ,取 。一次查找的代价定义为从根到叶经过的块传输次数,即树高 ;一次插入的代价为 次读加至多 次分裂写。目标是在给定 条记录时使 与每次分裂的块内移动量之和最小。这个模型把"比较次数"排除在代价之外,是 B+ 树与内存平衡树的根本区别。

2.2 OI Wiki 校订|基本性质

  1. I/O 主导(I/O dominance):B+ 树的代价按块传输计,不按比较计。为什么成立:一次磁盘寻道约 秒,一次内存比较约 秒,相差五个数量级。例:4 KB 块、键 4 B、指针 8 B,一个块可装约 341 个键。
  2. 扇出(fan-out):。为什么成立:内部结点只存索引键与指针,不存数据,块内能放下的键数即扇出。例:,取 ,。
  3. 树高(height):。为什么成立:非根结点至少半满,每层至少 路分支。例:、 时 ,取 5 层。
  4. 查找与 无关(-independent search):。为什么成立:层数中的 与层内二分的 精确抵消。例: 时层数约 3.55,每层约 8.4 次比较,乘积约 29.9,即 。
  5. 插入随 增大(insertion grows with ):,系数 在 时递增。为什么成立:每层数组插入是 ,层数只按 下降。例: 时系数约 1.9, 时约 40.7。

2.3 OI Wiki 校订|与课堂口径的差异

与 OI Wiki 的收录情况:OI Wiki 没有 B+ 树条目,也没有磁盘块代价模型;其《红黑树》页把红黑树定位为内存型平衡树,工业用例全部是内存索引,这与 PPT 的"内存用红黑树、磁盘用 B+ 树"分工一致,但没有给出 的取值推导。因此本考点的 、5 次 I/O、 与"理论最优 或 4"全部按 PPT/Weiss,考试以 PPT 为准。需要注意两种"最优"的模型不同:课件的 3/4 按每层 数组操作计,实际系统的几百按"一次块传输远贵于块内移动"计,答题时要先声明用哪个模型。

3. 图示

内存中的二叉树(每个节点一次访存):磁盘上的B+树(每个节点一次I/O):●┌─────────────────────┐/\│100│200│300│...│3400│←1个块=1个节点●●└──┬───┴───┴───┴────┬──┘/\/\┌────┘└────┐●●●●▼▼......┌──────────────┐┌──────────────┐│索引(索引)││索引(索引)│高度≈30└──────┬───────┘└──────┬───────┘30次cachemiss││┌───────────┘└────────┐▼▼┌──────────┐┌──────────┐│数据块1││数据块k│└──────────┘└──────────┘└──────────叶子链──────────►高度≈44次磁盘I/O

M 增大时两种操作的分道扬镳:

代价││╱插入O((M/logM)·logN)│╱←随M增大而变慢│╱│╱│╱│────────────────●────────────────────查找O(logN)←与M无关(水平线)│M=3/4└──────────────────────────────────────────►M理论最优实际系统(填满一个块)

4. 复杂度表

量 公式 说明
树高 PPT Depth(M,N)
查找 I/O 每层一次块读
查找时间 (与 M 无关) 层内二分抵消
插入时间 随 M 增大而变慢
分裂代价 块内数组移动
最优 M(理论) 3 或 4 课件 Note
最优 M(实践) 约 300(填满一个块) 磁盘 I/O 主导
对比二叉树 I/O 约多 6 倍

5. 典型例题

Q. A disk has 4 KB blocks; a key takes 4 bytes and a pointer 8 bytes. (a) Estimate the order M of a B+ tree stored on this disk. (b) Estimate the number of disk accesses to search records. (c) Explain, using asymptotic analysis, why M should not be made arbitrarily large even though a larger M makes the tree shorter. (d) Why does the search time nevertheless stay ?

解

(a) 估算 M

一个内部节点需要 个指针和 个键:

取 M ≈ 341()。

(b) 磁盘访问次数

⇒ 需要 约 5 次 I/O(4 层内部 + 1 次读数据)。

(c) 为什么 M 不能无限大

  • 查找:,与 M 无关。
  • 插入:。
  • 是 M 的递增函数(对 单调增)。所以 M 越大,插入越慢。
  • 极端情况 :树只有 1 层,但每次插入要在一张 N 元素的数组里搬移,变成 。

(d) 为什么查找与 M 无关

分子分母的 精确抵消——这是 B+ 树设计中最漂亮的一处"巧合"(其实是必然:不论分多少叉,总比较次数都等于"键之间的二分决策次数",而 个键的二分决策是 次)。

6. 易错点 + 老师原话

"M 是不是越大越好?……最极端的情况我让 M 等于 N 行不行?我直接我有 N 个元素,我就 N 个节点,然后它就只有一层。行吗?为什么大家不这么干?" — [稿:1-逐字稿:566]

"它最坏的时间复杂度是多少?O(M) 了。所以你往上走、往下走的时候,你的时间复杂度跟 M 是有关系的。所以 M 很大,是不好的。" — [稿:1-逐字稿:572]

"分子上一个 log M,分母上一个 log M,它们会抵消掉。查找的复杂度跟分叉是没有关系的。" — [稿:1-逐字稿:575]

"Note: The best choice of M is 3 or 4." — [PPT:1下:281]

五个易错:

  1. 说"M 越大树越矮所以越好"。树矮了,但每层数组操作 涨上去了,插入变慢。结论:M 有最优值。
  2. 说"查找也随 M 变慢"。查找是 与 M 无关( 抵消)。
  3. 忘了分裂是"块内数组移动"。 的来源就在这。
  4. 答"最优 M 很大"。课件明确 3 或 4;实际系统大是因为"块大小"约束,两者模型不同。
  5. 把"磁盘 I/O 次数"当成"比较次数"。B+ 树关心的是 I/O(块传输),不是 CPU 比较。

7. 考试英文答题模板

B+ trees are designed for disk-resident data. A disk transfers data in blocks, so a node is sized to one block, giving a large fan-out (hundreds) and a height of only — about 4–5 for records, versus for a binary tree. Hence a search costs only disk accesses. The choice of is a trade-off. Search is unaffected: . But insertion costs , which increases with because inserting into a node's sorted array takes . The course note therefore states that the best choice is or ; real systems use a much larger simply because one block read dominates the cost of moving a few hundred keys.


1.2.14 为何 NIL 是黑的(内部叶子可以是红的) 【A 级】

1. 一句话本质

把 NIL 统一当成黑节点,性质 (5) 就自动对"空子树"也成立,不用写一大堆例外;而"内部叶子"(没有孩子的实节点)可以是红的,因为 (5) 只约束 NIL,不约束它。

2. 定义与性质

[稿:1-逐字稿:422] 的课堂问答是标准答案:

老师:"为什么叶子一定要是黑的?……除了 NIL,我们红黑树里头默认的叶子是 NIL,空节点。内部的叶子不一定是黑的,你看我们前面那个例子里头,它那个叶子就是红的。叶子可以是红节点,是红的。"

老师:"(有同学说)'叶子是黑的'——(我说)不是。为什么叶子一定要是黑的?……(反问)你有保证是黑的,那我也对了。"(记录不全,但语义是:内部叶子可以是红的)

三条理由:

理由 1:让性质 (5) 对所有节点都成立,无需例外。

性质 (5) 说"每个节点到其所有后代叶子的路径上黑节点数相同"。如果 NIL 不是黑的:

  • 一个只有一个孩子的节点(如 14 只有左孩子),"到后代叶子的路径"只算实节点,那么"有 NIL 的位置"和"没有 NIL 的位置"会走出不同的计数,必须写"例外条款";
  • 把 NIL 定义为黑,所有路径都以"一个黑 NIL"收尾,计数统一。

形式化:

若 NIL 不是黑的,右侧这条路径的黑数会少 1, 就不良定义了。

理由 2:让 的定义统一(1.2.2)。

定义成"到 leaf 的黑节点数"。若 NIL 不是黑的,"到 leaf"这个终点就没有黑贡献, 与 的递推会分裂成两套规则。

理由 3:实现上的便利。

  • NIL 可以用一个共享的哨兵节点(sentinel)表示,它的 color = BLACK、left = right = parent = self;
  • 这样红黑树的修复代码里不需要任何 null 检查,所有"孩子"都是合法节点指针。CLRS 用 T.nil 就是这个技巧。

内部叶子为什么可以是红的:

外部节点(NIL) 内部叶子(无孩子的实节点)
是否存 key 否 是
颜色 必黑(性质 3) 可红可黑
受性质 (5) 约束? 是(作为路径终点) 是(作为路径上的节点)
受性质 (4) 约束? 是(但它没有孩子,自动满足) 是(它的孩子是 NIL,都是黑,自动满足)
PPT slide 2 例子 所有 NIL 4 和 15 是红的

PPT slide 2 的验证:节点 4 没有孩子(两个孩子都是 NIL,都是黑),所以即使 4 是红的也满足性质 (4)。同理 15。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《红黑树》 · https://oi-wiki.org/ds/rbtree/

OI Wiki 把"NIL 节点(空叶子节点)为黑色"直接列为四条性质中的第 2 条,并把 NIL 定义为空指针所指的外部结点。形式化地,对每个实结点 ,若某个孩子指针为空,则它指向一个外部结点 ; 不存键、没有孩子、颜色恒为 BLACK,所有 可以共享同一个哨兵(sentinel)。与此相对,内部叶子(internal leaf)是度为 0 的实结点,它存键、有两个指向 的孩子,颜色可红可黑。把 画成结点后,每条根到叶的路径都有统一终点与统一基数,性质 (5) 才是关于有限对象的全称命题。

2.2 OI Wiki 校订|基本性质

  1. NIL 必黑(black sentinel):。为什么成立:性质 (5) 要求每条路径的黑计数相同,若终点不计色就必须为"空路径"写例外;把终点定为黑,计数统一。例:单黑结点带两个 NIL,两条路径各计 1 个黑 NIL。
  2. 内部叶可红(red internal leaf):度为 0 的实结点可以是红色。为什么成立:性质 (4) 只约束红结点的孩子,而它的两个孩子都是黑 NIL,自动满足。例:1.2.1 样例中的 4 与 15 都是红内部叶,两条相关路径黑数仍相等。
  3. 哨兵等价(sentinel equivalence):把所有 NIL 换成一个共享黑哨兵不改变任何性质的判定。为什么成立:NIL 无键、无子树,只提供颜色与父指针,判定只读取这两项。例:14 的右孩子与 4 的两个孩子都指向同一 T.nil,红黑判定结果不变。
  4. 黑高基例(base case):。为什么成立: 不含起点,NIL 又没有后代,计数只能是 0。例:红内部叶 4 的 。
  5. 两种表述等价(decision equivalence):"NIL 为黑"与"外部空结点为黑"是同一命题。为什么成立:两者都指空指针所指的对象,只是前者用缩写、后者用全称。例:判断题"每个叶都是黑的"必须先声明 leaf 指 NIL,否则该命题为假。

2.3 OI Wiki 校订|与课堂口径的差异

OI 把 NIL 为黑单列为性质 2,并且不要求内部叶子为黑,也没有"根为黑"条款;PPT 的 (3) 与 OI 的性质 2 完全对应,PPT 的 (2) 根黑正是 OI 省去的那条。因此 1.2.14 的结论两边一致:必黑的是外部 NIL,内部叶子可红。OI 的中文表述"空叶子节点"与 PPT 的"NIL 哨兵"等价:它把空指针实体化为一个无键的黑结点,使"叶子"在两种表述里指同一批对象,也使 的递推有统一基例。考试按 PPT 的 (3) 写 "Every leaf (NIL) is black",并在括号中注明 leaf 指 external node;不要写"所有叶子必黑"这种省略限定语的句子。

3. 图示

(一)同一棵树,内外部节点用不同形状表示

                7(B)   ← internal node(实节点,存 key)
              /      \
          2(R)        11(R)
          /  \        /    \
       1(B)  5(B)   8(B)   14(B)   ← 全部是 internal nodes
              /                \
           4(R)                15(R)  ← 4 和 15 是"内部叶子",可以是红!
           /  \                /  \
        ⬛NIL ⬛NIL          ⬛NIL ⬛NIL   ← NIL(外部节点)必黑,用 ⬛ 表示
        ⬛NIL ⬛NIL          ⬛NIL ⬛NIL

(二)为什么内部叶子可以是红的——反例说明

若规定"所有叶子必须黑",则下面这棵合法树会被误判为非法:

        10(B)
       /     \
    5(R)     15(B)
              \
              17(R)    ← 17 没有孩子,是"叶子"

  17 是红的,它的两个孩子都是 NIL(黑)⇒ 性质 (4) ✔
  各路径黑数:10-5-NIL = 2;10-15-17-NIL = 2 ✔  ⇒ 合法!

  ⇒ "叶子必黑"这条命题是错的,只有"NIL 必黑"成立。

(三)NIL 作为哨兵(sentinel)的实现

若不用哨兵(需要到处判空):        用共享哨兵:

  if (x->left != NULL &&            所有空孩子都指向同一个 T.nil:
     x->left->color == RED) ...
                                     T.nil.color = BLACK
  if (x->right != NULL &&            T.nil.left  = T.nil
     x->right->color == RED) ...     T.nil.right = T.nil
                                     T.nil.parent= T.nil
  if (x == NULL) return 0;    ← 到处是 null 检查
                                     ⇒ 代码里没有任何 NULL 分支

4. 复杂度表

维度 NIL 是黑 NIL 若为红
性质 (5) 的表述 统一(无例外) 需加"空子树除外"等条款
递推 统一 需两套规则
实现代码 哨兵,无 null 检查 处处判空
空间 1 个哨兵 无
引理 归纳基础 干净 基础不成立
对象 颜色约束
根 必黑(性质 2)
NIL(外部节点) 必黑(性质 3)
内部节点(含"内部叶子") 可红可黑
红节点的孩子 必黑(性质 4)

5. 典型例题

Q. (a) True or false: "Every leaf of a red-black tree is black." Justify. (b) True or false: "A red node in a red-black tree cannot be a leaf (i.e., cannot have two NIL children)." Justify. (c) Why is it convenient to treat NIL as a black node?

解

(a) 真或假?

false(若 "leaf" 指"没有孩子的实节点")。性质 (3) 说的是"Every leaf (NIL) is black",括号里的 NIL 是关键限定词。内部叶子可以是红的,例如 PPT slide 2 的 4 和 15。

若题目明确 "leaf = NIL",则命题为真。答题时必须先定义 leaf。

(b) 真或假?

false。红节点完全可以没有孩子:

   15(R)
   /   \
 NIL   NIL

性质 (4) 要求红节点的两个孩子都是黑;NIL 是黑 ⇒ 满足。所以红节点可以是叶子。

(c) 为什么把 NIL 当黑节点方便

  1. 性质 (5) 无需例外:所有路径都以黑 NIL 收尾,黑高统一定义。
  2. 统一递推: 是自然的归纳基础,引理 的基础情形 干净成立。
  3. 实现无 null 检查:一个哨兵节点 T.nil 承担所有空指针,修复循环里不必判空。
  4. 旋转代码简化:旋转时要修改"父节点的孩子指针",若孩子可能是 NULL 就需要额外分支;用哨兵后 x.p.left = y 永远合法。

6. 易错点 + 老师原话

"你说的叶子是黑的?不是。为什么叶子一定要是黑的?除了 NIL,我们红黑树里头默认的叶子是 NIL,空节点,内部的叶子不一定是黑的,你看我们前面那个例子里头,它那个叶子就是红的。叶子可以是叶节点,是红的。" — [稿:1-逐字稿:422]

"黑高的要求非常高。黑高要求的是所有路径上的黑节点个数都得是一样的。如果你插了一个新节点进来,你把它染成黑的,就意味着你把一条路径上的黑色节点的个数给加一了。" — [稿:1-逐字稿:425](这句同时解释了 1.2.5 的"为什么染红",以及 NIL 作为统一终点的必要性)

四个易错:

  1. 把"叶子"当成"没有孩子的实节点"。性质 (3) 的 leaf 是 NIL。
  2. 以为红节点不能是叶子。可以,只要它的两个孩子是 NIL(黑)。
  3. 以为 NIL 必黑是因为"它是空的所以黑"。不是——是定义它为黑,为了统一性质 (5)。
  4. 忘了 NIL 计入 。见 1.2.2。

7. 考试英文答题模板

Property (3) states that every leaf (NIL) is black. The parenthetical qualification is essential: a "leaf" in this context is an external node, i.e. a NIL pointer, not an internal node that happens to have no children. Consequently an internal leaf may be red — for instance, a red node whose two children are both NIL satisfies property (4) because NIL is black. Treating NIL as a black node makes property (5) hold uniformly for every node without exception, gives the black-height a single recursive definition with base case , and allows a single shared sentinel node to replace all null pointers, so that the fix-up code needs no null checks.


附录 A · 与考点总表 / 速查册的不一致之处

本节记录写作过程中与 02_考点分析/考点总表.md、03_知识点概要/A0_速查册.md、02_考点分析/高频易错清单.md 及 PPT 原文之间的差异,供复核。

A.1 与 A0_速查册.md 的不一致

# 速查册原文 本文 / PPT 实际 处理
1 1.2.2:"bh(x) = 从 x(不含 x)到叶子的任意简单路径上的黑节点数;NIL 算黑并计入" PPT 定义确为"NIL 算黑",但是否计入计数在文字上未明说;PPT 的归纳基础 要求 本文给出显式递推式,消除含糊(1.2.2 口径说明)
2 1.2.1:"内部'叶子'(无孩子的实节点)可以是红的;'叶子'指 NIL" 与 PPT/逐字稿一致 ✔ 保留并强化
3 1.2.3 归纳步:"size(x) = 1 + 2·size(child) ≥ ..." PPT 原文是 ,速查册的""是放宽(假设两个孩子同规模),PPT 用同样放宽 本文同时给出严格式与放宽式,并说明放宽的目的
4 1.2.11:"根:1 ~ M−1 个键;非根内部:⌈M/2⌉−1 ~ M−1;叶子:⌈M/2⌉−1 ~ M−1" PPT slide 9 原文写"nonroot leaf also has between ⌈M/2⌉ and M children",字面上是不含 −1 的 本文按"键数 = 孩子数 − 1"统一成 ,并在 1.2.11 专门解释 PPT 用词问题
5 1.2.12:"键数 > M−1(溢出)→ 分裂为左右两半" PPT 伪代码写 while (has M+1 keys),分裂结果为 与 (B 树口径) 本文区分两种语义:叶分裂左 、右 且复制右片首键;内部结点才移动中间键。PPT 公式在 时给 3+3=6,与实际 5 个键不符
6 1.2.5:"Case 2:叔节点黑,且呈之字形 → 对 x 旋转" PPT 与 CLRS 的旋转对象都是父节点 (旋转后 变成 的位置),不是 本文统一写"对 旋转",并注明等价说法

A.2 与 考点总表.md 的不一致

# 总表表述 实际情况 建议
1 1.2.2 证据写 [PPT:1下:31] 定义确在第 31–33 行 ✔ 无需改
2 1.2.4 证据写 [PPT:1下:36],题型"证明题 高频" 定理在 36 行,证明链在 40–50 行;老师课上确实逐句讲了证明([稿:407,410]),"高频"判断合理 无需改
3 1.2.5 证据写 [PPT:1下:50+] Case 1/2 插入内容在 53–79 行,Case 1/2/3 图标在 4 页 建议改为 [PPT:1下:53-79]
4 1.2.7 证据写 [PPT:1下](无行号) 删除在 81–155 行;旋转次数表在 161–166 行 建议补 [PPT:1下:81-155, 161-166]
5 1.2.10 证据写 [PPT:1下] "For a general B+ tree of order M" 该句在 262 行(插入算法段),定义在 175–181 行 建议改为 [PPT:1下:175-181]
6 1.2.11 证据写 [PPT:1下](无行号) 键数界没有单独成页,是从定义 175–181 行 + 图示 187–208 行推出来的 建议注明"推导自 175–208"
7 1.2.13 证据仅 [纪要:1-conclusion] 三、 老师课上大量口述了 M 的权衡([稿:566,572,575]),且有课件 Note [PPT:1下:281] 建议补 [稿:1-逐字稿:566-575]、[PPT:1下:281]
8 1.2.14 证据写 [稿:1-逐字稿:422] ✔ 准确 无需改
9 1.2.9 定级 B 老师用整整两段专门讲"大型系统不做删除 → 优势消失"([稿:470]),并承接 1.2.8 一起构成"为什么用红黑树"的完整答案;考试中极易作为 1.2.8 的追问出现 建议升为 A(至少与 1.2.8 合并出题)

A.3 与 高频易错清单.md 的不一致

# 清单原文 实际 处理
1 第 4 条:"题:红黑树的绝对优势在哪?答:在删除…插入方面优势不大(都要 ≤2 次旋转),且红黑树多了 parent 指针开销" ✔ 与 [稿:470] 完全一致 采用
2 第 6 条:"题:红黑树是 AVL 树的特例吗?答:不是" ✔ 正确 采用(1.2.1 易错点 2、1.2.8 易错点 3)
3 四、必背公式:"红黑树树高 h ≤ 2 log₂(N+1)" ✔ 正确;PPT 写作 (ln 实为 log₂) 采用,并在 1.2.4 易错点 4 指出 ln/log₂ 问题
4 三、必须手算动作:"算红黑树某节点 black-height —— 易错点:bh 不含起点自身;NIL 算黑且计入" ✔ 但"NIL 计入"与 PPT 归纳基础 需调和 1.2.2 给递推式消除歧义
5 三、必须手算动作:"给 M 阶 B+ 树插入 → 画分裂 —— 易错点:键数上限 M−1、下限 ⌈M/2⌉−1;分裂中间键上移" ✔ 与 PPT 一致;但需区分:叶分裂复制右片首键(右片保留),内部结点分裂移动中间键 1.2.12 已按两种语义分写

A.4 PPT 自身的四处硬伤(考试时的应对)

位置 问题 应对
[PPT:1下:181] "Assume each nonroot leaf also has between ⌈M/2⌉ and M children" 叶子没有 children;且与本页图示(M=4,叶子 [1,4,8,11] 有 4 个键,超出 M−1=3)自相矛盾 按" 阶 = 个孩子 = 个键"的通行定义作答,并写出换算过程;1.2.11 已详述
[PPT:1下:36] "height at most " 应为 ; 是笔误或排版失真 考试写 ;1.2.4 易错点 4 已指出
[PPT:1下:269] "split into 2 nodes with ⌈(M+1)/2⌉ and ⌊(M+1)/2⌋ keys" M=5 时得 3+3=6,而实际只有 5 个键;该公式是 B 树口径(中间键被移走),未说明 B+ 树叶分裂不移动数据键 考试按"左 、右 、复制右片首键"作答;1.2.12 已给推导
[PPT:1下:353] "size(x) ≥ 1 + 2·size(child)" 严格式是 ;PPT 把两个孩子都放大成同一个 再取较小者,属于放宽而非等式 证明里写成"(放大到同规模)",别说成等号

附录 B · 本讲"必背清单"(考前 5 分钟)

红黑树五条性质:非红即黑 / 根黑 / NIL 黑 / 红子必黑 / 黑高相等
bh(x):x 到 NIL 的黑节点数(不含 x);红节点不变,黑节点 +1
size(x) ≥ 2^bh(x) − 1
h ≤ 2log₂(N+1)      ← 由 N ≥ 2^bh−1 与 bh ≥ h/2 合并
插入:先染红 → 叔红变色上推(Case1) / 叔黑之字先掰直(Case2) / 叔黑一字换色+旋祖父(Case3)
口诀:swap colors and rotate;最后 root = BLACK
插入旋转 ≤ 2;删除旋转 ≤ 3;AVL 删除是 O(log N) ⇒ RB 的绝对优势在 deletion
B+ 树:M 个孩子 = M−1 个键;键数 ⌈M/2⌉−1 ~ M−1;数据全在叶子;叶子同深度 + 链表
分裂:叶分裂复制右片首键(右片保留),内部结点移动中间键;根分裂 ⇒ 树高 +1(自底向上长高)
M 的权衡:查找 O(log N) 与 M 无关;插入 O((M/log M)log N) 随 M 增大 ⇒ 理论最优 M=3/4
NIL 必黑是为了让性质 (5) 无例外;内部叶子可以是红的
2.1.1 – 2.1.11

详解 03 · 回溯

配套 A0_速查册.md 的「2 上」部分。速查册给结论,本册给推导、图示与手算过程。 证据约定:[PPT:2上:n] = 01_原始素材/PPT_文本/ADS/第二讲(上)Backtracking.txt 行号;[稿:n] = 课堂录音文稿/2-逐字稿.md 行号;[纪要:2上] = 课堂录音文稿/2-conclusion.md 行号。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

穷举所有candidate→数量爆炸(8皇后8^8,围棋10^170)(2.1.1动机)→把"所有候选"组织成一棵解空间树(子集树2^n/排列树n!)(2.1.2)→DFS这棵树,边走边用剪枝函数砍枝(2.1.1/2.1.3)├─约束函数:砍"不可行"(N皇后同列同对角线)(2.1.4)└─限界函数:砍"不可能更优"→剪枝函数优化到极限后,还能靠「搜索顺序」再砍(2.1.5/2.1.10)最大距离优先:候选只有2个,砍一个就是砍半棵树→博弈场景:树深无穷,改用评估函数截断+Minimax(2.1.6/2.1.8)→Alpha-Beta剪枝:α≥β就停(2.1.7)★→AlphaGo:评估函数换成深度网络+MCTS(2.1.11)→贯穿性结论:剪枝不改最坏复杂度,只改常数与最好情况(2.1.9)★

一句话把整讲串起来([稿:80-83]):

「backtracking 的时间复杂度是很难估计的,但是……它跟很多东西有关,同时它还跟你搜索的顺序有关。」

即复杂度不是一个 Big-O 就能说完的量:


2.1.1 回溯 = 解空间树 + DFS + 剪枝 【A 级】

1. 一句话本质

找答案最笨的办法是把所有候选答案列成一张表一个个看;回溯就是「列到一半发现这条路根本不可能是答案,就不列了,退回来换下一条」——它保证不漏解,但可以不做无用功。

2. 定义与性质

PPT 原文 [PPT:2上:7-30]:

A sure-fire way to find the answer to a problem is to make a list of all
candidate answers, examine each, and following the examination of all or
some of the candidates, declare the identified answer.

Backtracking enables us to eliminate the explicit examination of a large
subset of the candidates — while still guaranteeing that the answer will
be found if the algorithm is run to termination. AND, if there are not
too many candidates.

The basic idea is that suppose we have a partial solution (x1, ..., xi)
where each xk ∈ Sk for 1 ≤ k ≤ i < n. First we add x_{i+1} ∈ S_{i+1}
and check if (x1, ..., xi, x_{i+1}) satisfies the constraints. If the
answer is "yes" we continue to add the next x, else we delete x_i and
backtrack to the previous partial solution (x1, ..., x_{i-1}).

中文拆解,三个要素缺一不可:

要素 含义 少了会怎样
部分解(partial solution) 前 个分量已定,后面待定 无法增量判断,只能生成完整解再判
候选集(candidate set) 第 个分量的取值域 无分支,退化成线性扫描
剪枝(pruning) 不满足约束就删掉 并回退 退化成纯穷举,

关键区别:回溯 ≠ 穷举。 两者都保证找到解(complete),但回溯不是先生成解再判,而是在生成过程中就判——所以它能 "eliminate the explicit examination of a large subset of the candidates"。这个"subset 从来没被生成过"是回溯的全部价值。

PPT 模板 [PPT:2上:187-203]:

c
bool Backtracking ( int i )
{   bool Found = false;
    if ( i > N )
        return true;                    /* solved with (x1, …, xN) */
    for ( each xi ∈ Si ) {
        OK = Check( (x1, …, xi), R );   /* pruning */
        if ( OK ) {
            Count xi in;                /* 标记:把 xi 纳入部分解 */
            Found = Backtracking( i+1 );
            if ( !Found )
                Undo( i );              /* 撤销:恢复成 (x1, …, x_{i-1}) */
        }
        if ( Found ) break;             /* 只要一个解就提前收工 */
    }
    return Found;
}

这是求单个解的版本(if (Found) break;)。若要求全部解,删掉 if (Found) break;,并把叶节点改成"记录解后 return false(继续找)"。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《回溯法》 · https://oi-wiki.org/search/backtracking/

OI Wiki 对回溯法的界定只有一段简介与一个四步过程:本质是"走不通就回头",过程为构造空间树、遍历、遇边界条件即停止向下并改搜另一条链、达目标条件即输出。把这几句写成形式定义,需要补上候选集与约束:设第 个分量的取值域为 ,约束关系为 ,部分解(partial solution)为 且 ;称 可行,若它对 的投影成立;称 为解,若 满足 。算法在第 层对每个 生成 ,只在可行时递归,返回前撤销该分量。

OI 的"边界条件"即本课程的剪枝函数 Check,"达到目标条件"即叶结点判定;"构造空间树"是逻辑动作,树由递归调用隐式给出,程序不分配结点。OI 的形式化止于此,可行前缀的单调性与完备性证明均未展开,这两项按教材口径补足。

2.2 OI Wiki 校订|基本性质

  1. 完备性(completeness):若 对前缀单调,回溯不遗漏解。理由:每个可行前缀的全部延长都在递归中被枚举,被剪的只是不可行前缀的延长,其中不含解。例:、、约束 ,剪掉 后仍得 。
  2. 单调性是剪枝合法的唯一前提:若存在"前缀违规而整体合法"的解,剪枝即丢解。例:把约束改成"解中恰有 个皇后落在副对角线",则前缀只含 个时按此约束判为不合格,但它可被延长成恰含 个的合法解,据此剪枝就丢解。
  3. 空间为 :递归深度为 ,每层只保存一个候选与标记位,树不落盘。例: 子集树共 个结点,递归栈同时只占 层。
  4. 时间上界等于解空间结点数:无剪枝时访问 个结点。例:子集树为 , 时 ;排列树为 , 时 。
  5. 撤销不可省:标记数组全局共享,漏掉 Undo 会使兄弟分支被误判。例:4 皇后若不清 col[], 这一支会因列标记残留而全部判死。

2.3 OI Wiki 校订|与课堂口径的差异

OI 只给直觉与模板,未定义可行前缀、单调性与完备性;课堂用 PPT 的 Check / Count / Undo 三元组补足,并把"剪枝合法性依赖约束单调"列为证明题要点。术语上 OI 称"边界条件",课堂称"约束函数"(constraint function)。考试按课堂口径写形式定义,OI 的"走不通就回头"只作直觉句附后。

3. 图示

()←部分解长度0┌──────────┬───────┴───────┬──────────┐x1=1x1=2x1=3x1=4←长度1││││x2=2x2=4x2=1x2=1←长度2(已剪掉冲突分支)││││x3=3✗x3=1x3=?✗x3=?✗←✗=Check返回false│x4=3✓→解(2,4,1,3)

DFS 顺序 = 前序扩展、后序回退(PPT 称 "depth-first search ( post-order traversal )" [PPT:2上:109-110])。注意 PPT 的脚注:

Note: No tree is actually constructed. The game tree is just an abstract concept.
                                                   [PPT:2上:114-125]

考试写答案时,"解空间树"是逻辑结构;程序里并不 allocate 这棵树,它由递归栈隐式体现。这句话是简答题的加分点。

4. 复杂度表

量 表达式
解空间大小(无剪枝) :子集树 ,排列树
最坏时间 与无剪枝同阶: 或
最好时间 第一条路径就走通,
空间 (递归深度 × 每层标记),树不落盘
实际运行时间 取决于剪枝函数 和 搜索顺序,无闭式

5. 典型例题

Problem. Given constraints , enumerate all vectors with satisfying . Describe the backtracking algorithm and state its worst-case cost.

中文逐步:

  1. 定义部分解 。
  2. 递归到第 层:对每个 ,调用 Check(P_i, R)。
  3. Check 返回 false 的分支整棵子树都不进——因为约束是单调的(monotone): 已违反 ,任何延长只会增加违反项。这是回溯正确性的唯一前提。
  4. Check 返回 true 就 Count xi in → 递归 → 失败则 Undo(i)。
  5. 最坏代价 = 结点总数 = 。

单调性必须写:若约束不单调(存在"前缀违规但整体合法"的解),剪枝就会丢解。例:N 皇后"列不重复"对前缀单调;但"最终恰好 3 个皇后在副对角线上"就不单调。

6. 易错点 + 老师原话

  • 易错①:漏掉 Undo(i)。回溯的撤销是必须的,因为标记数组(col[]、diag[]、used[])是全局共享的。忘了撤销 → 兄弟分支全部被误判 → 解变少甚至无解。
  • 易错②:把"回溯"答成"穷举 + 事后筛选"。两者最坏复杂度同阶,但回溯的剪枝发生在生成过程中,被剪子树从未展开。
  • 易错③:以为要真把解空间树建出来。[PPT:2上:114-125] 明说 "No tree is actually constructed."
  • 老师原话:

「我们搜索的时候优先搜索那些可能性比较少的。」[稿:80]

「backtracking 的时间复杂度是很难估计的……它跟很多东西有关,同时它还跟你搜索的顺序有关。」[稿:82-83]

7. 考试英文答题模板

Backtracking systematically searches a solution space organized as a tree. It maintains a partial solution , extends it by each candidate , and invokes a pruning function Check on the extended prefix. If Check fails, the entire subtree of that prefix is discarded and the algorithm backtracks by undoing the choice; otherwise it recurses. Pruning is sound because the constraints are monotone: once a prefix violates them, every extension does too. Backtracking therefore guarantees completeness while never explicitly generating the pruned subset of candidates. Its worst case is , but the actual running time depends on both the strength of the pruning function and the order in which candidates are explored.


2.1.2 解空间树:子集树 vs 排列树 【A 级】

1. 一句话本质

解长什么样,树就长什么样:每个元素"选或不选"→ 二叉子集树;每个位置"从剩下的人里挑一个"→ n 叉排列树。搞错树型,答案的数量级就差好几个数量级。

2. 定义与性质

树型 English 分支因子 叶子数 部分解含义 典型题
子集树 subset tree 2(选 / 不选) 已决定前 个元素的去留 0/1 背包、子集和、装载问题
排列树 permutation tree (剩余未用元素数) 已排定前 个位置 N 皇后、TSP、全排列

统一记法:叶子数 。子集树 ;排列树 ,乘积 。

N 皇后为什么能降到 (PPT 原文 [PPT:2上:41-72]):

Qi ::= queen in the i-th row
xi ::= the column index in which Qi is placed
⇒ Solution = (x1, x2, ..., x8)

This implies that the solution must be a permutation of 1, 2, ..., 8.
Thus the number of candidates in the solution space is reduced to 8!.

Constraints:  Si = {1,...,8} for 1 ≤ i ≤ 8
              xi ≠ xj if i ≠ j            (column distinct)
              (xi − xj)/(i − j) ≠ ±1      (diagonal / antidiagonal distinct)

关键推理链:"第 行恰好放一个皇后"把行冲突直接消掉,于是 只需记列号;再由 消掉列冲突 ⇒ 解必是 的排列 ⇒ ,而非 。降了 400 倍,靠的是"把约束预先编码进解的形式",而不是靠剪枝。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《回溯法》 · https://oi-wiki.org/search/backtracking/

OI 的回溯页不区分树型,只要求"构造空间树",例题是八皇后与迷宫。子集树(subset tree)与排列树(permutation tree)的形式区分按教材补足:设解向量长度为 ,深度为 的结点对应前缀 ,则它的孩子数为 ,深度 的结点数为 ,叶结点数为 。若对所有 有 ,称为子集树;若 ,称为排列树。

两类树的分支数由"约束是否已被编码进解的形式"决定:0/1 背包的每个分量只有取与不取两种,故 ;全排列与 TSP 要求每个元素恰出现一次,故第 层只剩 个未用元素可选。

2.2 OI Wiki 校订|基本性质

  1. 子集树结点总数为 :等比求和 。例: 时 。
  2. 排列树结点总数为 :第 层为 。例: 时 ,不是 ,因为最深层已无分支。
  3. 叶子数等于解空间大小:每片叶对应一条完整解向量。例: 时子集树 叶、排列树 叶。
  4. 排列树的分支因子随深度递减:第 层的每个结点只有 个孩子。例: 时三层分支数依次为 。
  5. 树型由"每元素恰一次"决定:有这个硬约束才是排列树。例:把 0/1 背包改成"把 件物品全部按顺序装箱",解变为排列,叶数从 变为 。
  6. 错误编码会放大数量级:若不用"每行一个皇后",N 皇后是满 叉树。例: 时 叶,编码后为 叶。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的回溯页只按例题给模板,未点明例题属于哪种树;其八皇后例题(Checker Challenge)实际已使用"每行一个"的编码,解空间是 而非 ,但页面没有写出这一步。迷宫例题是网格图上的路径搜索,既非子集树也非排列树,属于"图上 DFS 加访问标记",不能套用 或 。考试按课堂的两分法作答,并要写出"编码把 降为 "这一句。

3. 图示

子集树n=3(0/1背包:第i层决定物品i取不取)排列树n=3(全排列)()()┌────────┴────────┐┌───────┬──────┴──────┬───────┐取1不取1123┌───┴───┐┌───┴───┐┌──┴──┐┌──┴──┐┌──┴──┐取2不取2取2不取2231312┌─┴─┐┌─┴─┐┌─┴─┐┌─┴─┐││││││取3不取3取3不取3...323121叶数=2^3=8叶数=3!=6

4. 复杂度表

树型 叶子数 结点总数 深度 空间(递归栈)
子集树 + 布尔标记
排列树 + 已用标记

排列树的结点总数约 (),不是 ——越深的层越窄。

5. 典型例题

Problem. For the 0/1 knapsack with items , , capacity : (a) what tree does the solution space form? (b) How large is it? (c) Can any constraint turn it into a permutation tree?

中文逐步:

  1. 解是 0/1 向量 ⇒ 子集树, 叶。
  2. 若问题改为"把 件物品全部按某顺序装箱"(每件必装、只定顺序),解变成排列 ⇒ 排列树, 叶。
  3. (c) 不能。判据是"是否有 'each exactly once' 的硬约束":有 ⇒ 排列树;没有 ⇒ 子集树。"选或不选"与"挑一个顺序"是两种不同的组合结构。
  4. 反向看 N 皇后:若不用"每行一个"的编码,解就是 (每行可选 列、允许同行多皇后),此时是满 叉树;用了"每行恰一个"才升格为排列树。

6. 易错点 + 老师原话

  • 易错①:说"N 皇后是 叉树, 个叶子"。错。PPT 明确写 "the number of candidates in the solution space is reduced to 8!"。判卷先看这一步。
  • 易错②:把子集树的"分支因子 2"说成"深度 2"。 个物品 ⇒ 深度 ,每层 2 分支。
  • 易错③:以为排列树"每个结点有 个孩子"。第 层只有 个。
  • 老师原话(构造性思维):

「你先从最最简单的树开始想,比如说,一个节点有点太简单,看不出来啥……你先想两个节点的最简单的。」[稿:116]

这是老师贯穿全课的方法论:画树从 、 起手,别一上来就想 。

7. 考试英文答题模板

If each component is a binary choice (take / not take), the solution space is a subset tree with leaves and depth . If the solution must be a permutation of distinct items — i.e. each element appears exactly once — the space is a permutation tree, where level has branches, giving leaves (about nodes in total). For -queens, encoding the solution as where is the column of the queen in row exploits the "one queen per row" and "distinct columns" constraints, reducing the candidate count from to .


2.1.3 约束函数 vs 限界函数 【A 级】

1. 一句话本质

两种剪枝只是"砍的理由"不同:约束函数砍"这根本不合法"(硬约束,二值判定);限界函数砍"这就算做完也不如已经找到的那个"(优化问题,比大小)。

2. 定义与性质

约束函数 Constraint Function 限界函数 Bounding Function
判据 部分解是否违反硬性规则 部分解的乐观上界是否 当前最优
返回 true / false 上界数值 vs best 比较
适用问题 判定 / 枚举所有可行解 优化问题(求 max / min)
剪掉的子树 里面一个可行解都没有 里面可能有可行解,但不可能更优
举例 N 皇后同列/同对角线 0/1 背包 curValue + 剩余上界 ≤ best
是否依赖 best 不依赖 依赖(best 变好后剪枝变强)

两者可以同时存在:0/1 背包里"重量超限"是约束函数,"上界不够优"是限界函数。

限界函数的经典形式(0/1 背包,按单位价值降序):

若 ,则第 层及以下全部剪掉。

关键性质:上界必须"乐观"(admissible / optimistic)。宁可松一点(剪得少),不能紧过头(剪掉真解)。这与 A* 搜索的启发函数可采纳性同源。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《搜索优化》 · https://oi-wiki.org/search/opt/

OI 把剪枝(pruning)分为三类:记忆化搜索(memoization)、最优性剪枝(optimality pruning)、可行性剪枝(feasibility pruning)。按本课程的两分法,后两类即约束函数与限界函数。形式定义如下。

可行性剪枝:设 为部分解是否违反硬约束的谓词, 为假则剪去整棵子树。合法性要求 单调: 为假蕴含对所有延长 ()都有 为假。

最优性剪枝:设目标为最大化, 为 的所有可行延长所能达到的目标值的上界(optimistic bound), 为已找到的最优值。若 ,则剪去整棵子树。这里 必须满足 ,其中 为延长解的目标值,否则会剪掉真最优解。

2.2 OI Wiki 校订|基本性质

  1. 约束函数不依赖 ,限界函数依赖:前者只判合法性,后者要与当前最优比大小。例:4 皇后只需约束函数即可求解,全程无需 。
  2. 界必须乐观(admissible):低估会剪掉最优解。例:最大化问题中当前 ,某结点已得 ,剩余一件 、、剩余容量 ;真实可达 ,若界只算已得值 便误剪。
  3. 界的松紧决定剪枝量:界越松, 要越大才剪得动。例:0/1 背包用分数背包放松得 ,用"剩余全拿"得 ,后者要等 。
  4. 两类剪枝可叠加,且应先测便宜的:可行性判定常为 ,界计算常为 。例:4 皇后先判列与对角线,结点数从 降到 。
  5. 记忆化只适用于状态可复用的搜索:同一状态被多次到达才能合并。例:子集和可按 记忆化;N 皇后的部分解带路径信息,不可合并。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的三分法与本课程的两分法不是同一划分:记忆化在课堂属于"用空间换时间",既不是约束也不是限界;只有可行性、最优性两类对应课堂的约束函数与限界函数。OI 未讨论"界必须可采纳"的证明,也未提 的单向性(求 max 只增、求 min 只减),而这两条是课堂的判断依据。考试按课堂两分法作答,若题目出现记忆化,另列一条说明。

3. 图示

部分解P_i│┌─────────┴─────────┐Check_constraintbound(P_i)≤best?(合法性/feasibility)(最优性/optimality)││false│truetrue│false││整枝剪掉整枝剪掉"这里没有任何可行解""这里有可行解,但都赢不了best"

4. 复杂度表

剪枝类型 最坏复杂度 最好情况 剪枝强度
无剪枝 同 0
仅约束函数 仍 由约束紧不紧决定
仅限界函数 仍 可能 由 best 好坏 + 搜索顺序决定
两者合用 仍 (见 2.1.9) 最强

注意"仍"——这是 2.1.9 的伏笔:剪枝不改变最坏复杂度。

5. 典型例题

Problem. 0/1 knapsack, , , items , sorted by : . At a node the partial solution is with value 20, weight 6. Can we prune?

中文逐步:

  1. 约束函数:,合法,不剪。
  2. 限界函数:剩余物品 全装需 (剩余容量),用分数背包放松:装物品 3 只能装 ,得 。

  3. 若 ,剪掉;若 ,继续。

  4. 若用更粗的"剩余全拿":,则 best 要涨到 50 才剪得动——界越松,剪枝越废。所以要求"乐观,但尽量紧"。

6. 易错点 + 老师原话

  • 易错①:限界函数写成悲观的(按"剩余装不满"估),会剪掉真最优解。
  • 易错②:以为限界函数也能用于"枚举所有解"类问题。不能——没有 best 可比。
  • 易错③:忘记 best 是全局变量,求 max 时只增、求 min 时只减;顺序不当会让好解太晚出现,剪枝效率暴跌(→ 2.1.5)。
  • 老师原话:

「我们说跟你前面剪枝的 check 函数有关?你那个 check 函数尽可能的返回 no 你就剪得快一点。」[稿:83]

「当剪枝的函数没法再优化的时候,你要看一看搜索的顺序。」[稿:83]

这就是本讲的"优化优先级":先优化 Check(强弱),再优化顺序(先后)。

7. 考试英文答题模板

A constraint function tests a partial solution against the hard feasibility rules and returns true / false; failing it means the whole subtree contains no feasible solution, so it is discarded. A bounding function applies only to optimization problems: it computes an optimistic bound on the best objective value reachable from the partial solution and prunes the subtree if that bound cannot beat the best solution found so far. A constraint function prunes on legality and does not depend on best; a bounding function prunes on optimality and does. Both are sound only if the bound is admissible, i.e. it never underestimates the optimum of the subtree.


2.1.4 N 皇后(4 皇后完整解空间树) 【A 级】

1. 一句话本质

每行放一个皇后,逐行往下放;每放一个就检查"同列吗?同对角线吗?"——冲突就换列,全部放完就是一个解。

2. 定义与性质

Problem. Place queens on an board so that no two attack. Two queens attack iff they share a row, a column, a diagonal, or an antidiagonal.

编码 = 第 行皇后所在列号。两条约束:

为什么一条式子管住两条对角线:主对角线(diagonal)与副对角线(antidiagonal)上任意两点的斜率都是 ,即 。PPT 写作 ,等价。

O(1) 判定技巧(考试写代码加分):维护三个布尔数组 col[c]、d1[r-c+n]、d2[r+c],进入置 true、回退置 false。判定从 降到 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《回溯法》 · https://oi-wiki.org/search/backtracking/

OI 的例题即 Checker Challenge(USACO 1.5.4):在 棋盘上放 个棋子,使每行、每列、每条对角线至多一个棋子,方案用序列 表示, 为第 行棋子的列号,要求按字典序输出前 个解与解的总数。这个表示已经隐含"每行恰一个"。

形式定义:解集为

第一个条件管列,第二个条件同时管主对角线(diagonal)与副对角线(antidiagonal),因为两族对角线上的点满足斜率 。若目标是枚举,则输出 ;若只求一个解,则在第一个可行叶处终止。

2.2 OI Wiki 校订|基本性质

  1. 无解: 的两个排列 、 都有 ; 的 个排列逐一检查,每个都有一对落在同一对角线上。
  2. 恰有 个解: 个排列中有 个违反约束。例: 的 为 互异, 为 互异,故两族对角线均无冲突。
  3. 两解互为镜像:变换 把解映为解。例:,故 的本质不同解只有 个。
  4. 剪枝后访问 个结点: 个根加 个非根,叶 片;不剪枝的排列树为 个结点。例: 整支无解, 是其镜像分支,同样无解,剪掉的正是这些注定失败的前缀。
  5. 判定可做到 :维护 col[c]、d1[i-c+n]、d2[i+c] 三个布尔数组,进入置真、回退置假。例: 时 d1 下标范围 ,d2 范围 。
  6. 解空间是排列树而非 叉树: 互异把第 层分支数压到 。例: 时 叶,而满 叉树为 叶。

2.3 OI Wiki 校订|与课堂口径的差异

OI 例题以 为输入,只要求按字典序输出前 个解与总数( 共 个解),不要求画树;课堂以 手算,并要求画出完整剪枝树、写出 个结点与两解的镜像关系。OI 也未区分"含对称的解数"与"本质不同解数",这两列在课堂表格里必须分开写。考试按课堂:写坐标、写结点数、写镜像对应。

3. 图示

完整解空间树(排列树, 叶):

                                x1                            第 1 层:4 个
              x1=1        x1=2        x1=3        x1=4
              /│\          /│\          /│\          /│\     第 2 层:12 个
          2  3  4       1  3  4       1  2  4       1  2  3
                                                              第 3 层:24 个
         (每个 x2 结点有 2 个孩子;每个 x3 结点只剩 1 列)
                                                              第 4 层:24 叶 = 4!

剪枝后(每层扩展时调用约束函数;✗ = Check 返回 false):

()第0层:1┌──────────┬───────┴───────┬──────────┐x1=1x1=2x1=3x1=4第1层:4││││x2=2x2=4x2=1x2=1第2层:6╱╲│││x3=3✗x3=4✗x3=1✓x3=4✓x3=1✗第3层:4│││││(死)(死)x4=3✓x4=2✓(死)第4层:2解(2,4,1,3)解(3,1,4,2)剪枝后非根结点=4+6+4+2=16,叶子=2。不剪枝的完整排列树=4+12+24+24=64个非根结点,叶子24。

逐点验算解 ( 全部成立):

点对 判定
2 1 ✓
1 2 ✓
1 3 ✓
3 1 ✓
1 2 ✓
2 1 ✓

(等价写法: 依次为 互异; 依次为 互异。)

另一支 的死法(最常被漏检):

  • : → 剪。
  • :, 与 冲突(), 与 冲突()⇒ 死。
  • :, 与 副对角冲突(), 与 主对角冲突()⇒ 死。

⇒ 整支无解。同理 镜像于 ,也无解。

4 皇后的两个解: 与 ——互为镜像(左右翻转)。这正是 2.1.11 里"旋转/对称后本质不同的下法只有 3 种"的同类观察。

解的总数(含对称) 本质不同解数
4 2 1
5 10 2
6 4 1
8 92 12

4. 复杂度表

版本 解空间 实际访问结点(4 皇后)
无剪枝(满 叉,) 叶 341 个结点
无剪枝(排列树) 叶
加列 + 对角线约束 同上 17(1 根 + 16 个非根)→ 只剩 2 解

一般 :最坏 (不用"每行一个"编码则 ),空间 。 时 ,加剪枝后实际访问约 2000 量级。

5. 典型例题

Problem. Apply backtracking to the 4-queens problem; list every branch examined and all solutions.

中文逐步:

  1. 根: 可取 1,2,3,4。两个解互为镜像,但考试要求全部展开,不能只画一半就宣布"由对称性"。
  2. :如上,三支全死 ⇒ 无解。
  3. :。 - : → 剪。 - : → 剪。 - :唯一存活。
  4. 时剩 : - :与 差 3()✓;与 差 1()✓ → 保留。 - :与 差 1()→ 剪。
  5. 只剩 3:与 ()、()、()均无冲突 → 解 。
  6. :镜像过程 → 解 。
  7. :镜像于 ,无解。

结论:4 皇后恰有 2 解。 手算时每一对 都要代 ,不能凭"看着像"——第 4 步里 与 就很容易被误判为冲突。

6. 易错点 + 老师原话

  • 易错①:对角线判定写成 (等于才是冲突)。
  • 易错②:只检查新皇后与紧邻上一行。必须与所有已放皇后比。
  • 易错③:以为 有解。4 皇后的两个解首行分别是 2 和 3。
  • 易错④:忘记"每行一个"这个编码前提,画成 的树。
  • 易错⑤:回溯时忘清 col/d1/d2 标记,导致兄弟分支被误剪。
  • 老师原话(讲对称性):

「tic tac toe 它就有三种下法……你考虑到它,对称,旋转之后,本质上不同的下法就三种。」[稿:92]

同理 4 皇后的 2 个解本质只有 1 个(互为镜像)。

7. 考试英文答题模板

Encode a solution as , where is the column of the queen in row . Since no two queens may share a row, exactly one queen is placed per row; since no two may share a column, the are pairwise distinct, so the solution space is a permutation tree with leaves. The constraint (pruning) function rejects a partial solution if for some

the first testing the column and the second testing both diagonals. For the pruned tree visits only 16 non-root nodes and yields exactly two solutions, and , which are mirror images of each other; the subtree with dies entirely. Using three boolean arrays for columns and the two diagonal families makes each test , giving worst-case time and space.


2.1.5 搜索顺序优化:最大距离优先 【S 级】

1. 一句话本质

剪枝砍不动的时候,先砍"选择最少"的那一刀:有些位置上只有 2 种可能,判死一种就直接扔掉半棵树;而在"可能性很多"的位置上判死,只扔掉一根细枝。

2. 定义与性质

同一棵解空间树,结点总数与叶子数完全不变;改变的只是 DFS 遇到第一个"死"的先后顺序,从而改变剪枝函数被触发的位置,进而改变被整体丢弃的子树大小。

形式化:设某搜索步的候选集为 ,若 且其中一支被判死,丢弃的占当前结点的 ;若 ,只丢弃 。所以

⇒ 按候选集从小到大排序("smaller first") 是通用启发式。PPT 原文只有一句:

When different Si's have different sizes
                              Smaller Si first
                                              [PPT:2上:207-208]

距离问题(Turnpike)里的具体化:最大距离 必然是两端点之差。所以一旦锁定 ,该步只有 2 种可能:它归属 或归属 。判死一种 → 扔掉半边。

反之,最小距离 可能发生在任意相邻对之间,共 对(每对还有 2 个方向)。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《搜索优化》 · https://oi-wiki.org/search/opt/

OI 在"剪枝思路"里列出一条与本考点相邻的调整法:通过对子树的比较剪掉重复子树和明显不是最有"前途"的子树。它讨论的是"剪哪棵子树",没有讨论"先展开哪一层",也未给出定量判据,形式化需自行补足。设某层候选集为 ,DFS 按固定次序枚举;一次失败的 Check 丢弃该候选对应的整棵子树,被丢弃的结点数占当前剩余搜索空间的比例约为 。记剪枝收益为该比例,则同一棵树在不同枚举次序下的总访问结点数不同,而树的形状、叶子数与解的个数都不变。

由此得到最小剩余取值(minimum remaining values, MRV)规则:优先展开 最小的那一层。

2.2 OI Wiki 校订|基本性质

  1. 顺序不改变解空间:叶子数、解数与树的结构都由问题定义决定。例:Turnpike 的叶数在"最大距离优先"与"最小距离优先"下相同。
  2. 单位剪枝收益与 成正比:判死一个候选就丢掉它的整棵子树。例: 时丢一半, 时只丢十分之一。
  3. 最大距离只有两种归属: 只能由两个端点实现。例:剩余最大为 时,新点只能是 或 。
  4. 最小距离的候选数为 : 个相邻对,每对两个方向。例: 时 个候选,判死一个只丢 。
  5. 顺序是第二顺位:先强化 Check,再调顺序,因为无剪枝时顺序没有收益。例:4 皇后把 Check 换成恒真,任何顺序都访问 个结点,调顺序不再有意义。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的调整法与本考点的 MRV 相邻但不相同:调整法讲"剪掉哪棵子树",MRV 讲"先展开哪一层",两者都指向"优先处理更可能失败的分支",但只有 MRV 能给出"候选数 对 "这样的定量结论。考试按课堂:必须写出" ⇒ 砍一枝即砍半树"这条因果链。

3. 图示

PPT 的 Turnpike 搜索树([PPT:2上:136-142]),,:

[0,...,10]D_max=10┌────────┴────────┐x6=10x1=0│D_max=8┌───────┴───────┐x5=8x2=2││D_max=7(后续矛盾→回溯)┌──────┴──────┐x4=7x2=3││D_max=6D_max=6┌───┴───┐┌───┴───┐x3=6x3=4x3=5...✗✗✓↓最终(0,3,5,6,8,10)

每一步的分支数都是 2;按最小距离优先时该步分支数是 。

对照:若按"最小距离优先"—— 意味着有两点相距 1,它可能出现在 共 5 处,每处 2 个方向 ⇒ 10 个候选。判死 1 个只砍 。

4. 复杂度表

搜索顺序 解空间(叶子数) 实际访问结点 说明
任意顺序,不剪 型 全展开 无差别
最小距离优先 同上(不变) 接近全展开 每步候选多,剪枝只砍细枝
最大距离优先 同上(不变) 显著下降(数倍) 每步 2 候选,砍一枝 = 砍半树
理论最坏 同上 不变 见 2.1.9

关键:第 2 列从不改变。搜索顺序只影响第 3 列。

5. 典型例题(含反例)

Problem. , , . Reconstruct the point set, then compare the largest-distance-first order with the smallest-distance-first order.

A. 最大距离优先(高效)

  1. ⇒ 。
  2. ,;从 删去 。
  3. 每步取剩余最大距离 ,只二分( 或 )。Check:新点与所有已定点的距离必须都在 里;否则立刻返回 false,扔掉半棵树。
  4. 结果 。验算距离集 ,排序后与原 一致 ✓。

B. 最小距离优先(反例,剪不动)

  1. 先取 。它必须对应某对相邻点,5 个位置 × 2 方向 ⇒ 10 个候选。
  2. 试 :Check 通过(1 在 中)——这一步几乎没有任何鉴别力,因为 1 是最小的数,几乎任何位置都成立。
  3. 下一层: 中还有两个 2。 可能落在更多点对位置,候选数反而增加。
  4. 假设到第 4 层才发现矛盾。此时被砍掉的是第 4 层某个候选的一根细枝,上面三层全部保留,要逐个重试。
  5. 极端情况:直到接近叶子才判死 ⇒ "剪枝"退化成"事后验算",树基本全展开。

C. 为什么差这么多(本质)

最大距离优先 最小距离优先
每步候选数 2(只有两端) 最多 (任意相邻对 × 方向)
判死一次丢弃 的树 的树
判死时机 早(约束最强) 晚(弱约束,几乎总能满足)
候选数可理论证明吗 是( 只能来自端点) 否(无结构)

老师原话:

「大的距离只对应两种,最多就两种,不可能有第三种。那你证明了这一点之后,这就很棒了,我们搜索的时候优先搜索那些可能性比较少的,好处是说一共就两种可能,如果你杀掉了一种可能,你直接就杀掉了半棵树。」[稿:80]

「我们从比如说从一开始从最小值开始,那么最小值?它可能发生在很多对儿收费站之间,它有太多种可能,这个可能性就非常多。当你探测到一个事情不可能的时候,你只能砍掉一棵很小的树,这样的话,你效率就会低一点。」[稿:80]

("收费站"是老师对 Turnpike 例题的口语化转述。)

6. 易错点 + 老师原话

  • 易错①:说"搜索顺序改变了解空间大小"。错——叶子数、解的数量、最坏复杂度都不变。
  • 易错②:只说"大的先试"而不说为什么。判分要的是"候选数最少 ⇒ 单位剪枝收益最大"这条因果链。
  • 易错③:认为"先排序这 不值"。老师现场纠正过一个学生(想用"找最大值只需 "省掉排序):距离集合有 个元素,找最大值本身也是 ,排序只是多个 因子,相对回溯节省的搜索量不值一提。[稿:56-68]
  • 易错④:把"最大距离优先"当成距离问题专用技巧。它的普适形式是 MRV(minimum remaining values,最少剩余取值优先),即 "Smaller first"。
  • 老师原话(顺序何时才重要):

「当剪枝的函数没法再优化的时候,你要看一看搜索的顺序,搜索的顺序,它也能省掉很多时间。」[稿:83]

顺序优化是"第二顺位":先把 Check 做强,再调顺序。

7. 考试英文答题模板

When different branches have different numbers of candidates, the search order matters, because the benefit of a failed Check is proportional to the size of the subtree it discards. If a step has only candidates, killing one discards of the remaining space; if it has many candidates, killing one discards almost nothing. In Turnpike reconstruction the largest remaining distance must equal , i.e. it is realized by the two extreme points only, so that step has exactly two candidates; rejecting one removes half of the search tree, and the same argument repeats at every subsequent level. The smallest distance, by contrast, may be realized by any of the adjacent pairs in either direction, so rejecting it removes only a negligible fraction and the tree is explored almost in full. Hence one should explore the step with the smallest candidate set first ("smaller first", the MRV heuristic). This changes only the number of nodes actually visited — the solution space, the number of solutions and the worst-case complexity are unchanged.


2.1.6 Minimax 与博弈树 【A 级】

1. 一句话本质

我下棋时假设对手也聪明:我挑最大分的一步,对手一定挑最小分的一步来坑我,一层层交替推上去,根上的数就是"双方都最优"时我能拿到的分。

2. 定义与性质

PPT 原文 [PPT:2上:252-255]:

Use an evaluation function to quantify the "goodness" of a position. For example:
        f(P) = W_Computer − W_Human
where W is the number of potential wins at position P.
The human is trying to minimize the value of the position P,
while the computer is trying to maximize it.

定义( 为评估函数 evaluation function):

三条性质:

  1. MAX / MIN 严格交替(我一步、对手一步),层号奇偶决定角色。
  2. 评估函数只在"截断处"取值。博弈树深不见底(围棋 ),必须设深度上限(depth limit)。
  3. Minimax 给出"双方都最优"的值(game-theoretic value),不是"所有解里的最好分"——它假设对手不犯错。

Tic-tac-toe 的规模(PPT [PPT:2上:226-242]):

19,683 possible board layouts (3^9 since each of the nine spaces can be X, O or blank)
362,880 (i.e., 9!) possible games (different sequences for placing the Xs and Os)

19,683 是局面数(),362,880 是对局序列数()。二者常被混淆,是选择题点(合法局面约 5,478)。考虑旋转对称后本质不同的开局只有 3 种(角、边、心)——[稿:92]。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Alpha–Beta 剪枝》 · https://oi-wiki.org/search/alpha-beta/

OI 的 Minimax 一节给出完整设定:在局面确定的双人零和对弈中构造搜索树,奇数层为己方、偶数层为对方;叶结点赋估值,估值越大表示己方赢面越大;奇数层取子结点的最大值,偶数层取最小值;自顶向下遍历,回溯时用子树信息更新,根的值即双方都采取最优策略时己方能够获得的最大分数。

形式定义:设 为叶结点(或达到深度上限的结点)的估值,则

MAX 与 MIN 的角色由层号与根的角色的奇偶关系决定,一旦根的角色固定,全树角色随之确定。

2.2 OI Wiki 校订|基本性质

  1. 根的值为博弈论值(game-theoretic value):它假设对手不犯错。例:根为 MAX、两个 MIN 子结点的叶值为 与 ,则子值分别为 与 ,根值为 ,而不是 。
  2. 落在叶值区间内:对树高归纳可得 。例:上例 。
  3. 时间为 、空间为 :DFS 只保留一条根到叶的路径。例: 共 片叶、 个结点,栈深 。
  4. 角色由根唯一确定:根的角色一经声明,各层角色按奇偶整体平移,不能中途改判。例:tic-tac-toe 中先手为 MAX 时第 层为 MIN;若把先手改判为 MIN,则第 层由取 max 改为取 min,值随之改变。
  5. 深度截断把精确值变成估计:截断处的值取自 ,与真实胜负可能不符。例:同一局面看 层与看 层可能给出不同的根值。

2.3 OI Wiki 校订|与课堂口径的差异

OI 用"分数"统称结点值,用"Alpha 玩家/Beta 玩家"指代求最大与求最小的一方,并按"奇数层为己方"固定角色;课堂固定"电脑 = MAX",用 保证符号一致。两者对 的递归定义一致,差别只在记号与根的选取。考试按课堂:答卷先声明根的角色与层号约定,再写 的三种情形。

3. 图示

PPT 的两层 tic-tac-toe 博弈树 [PPT:2上:265-276]:

                      1          max      ← 根:电脑选
                  -2      -1     min      ← 第 2 层:人走,取 min
      1   2  -1  0  -1  -2  0  1  0   -1  1  0        ← 叶子:f(P) 的值

PPT 版面上的 max/min 标签相对固定视角(电脑 = Max)标注,随深度奇偶交替。手算时最稳妥:从根往下数层号,奇数层 = MAX(我),偶数层 = MIN(对手),并在答卷上声明所用约定。

「如果这一步是人来做决策,想让我得分尽可能少,那这一步,是我来下,我就要选得分尽可能多的。」[稿:98]

4. 复杂度表

量 值
分支因子 / 深度 /
叶节点数
Minimax 时间(朴素)
Minimax 空间(DFS) (只存一条根到叶的路径)
tic-tac-toe 全树 序列;合法局面
围棋全树 (,)

5. 典型例题

Problem. A game tree has depth 2 (root MAX, children MIN). The 3 root children have leaf values , , . Compute the Minimax value of the root and the chosen move.

中文逐步:

  1. 第 1 个 MIN 结点:。
  2. 第 2 个 MIN 结点:。
  3. 第 3 个 MIN 结点:。
  4. 根(MAX): ⇒ 选第 1 步。
  5. 解读:选第 1 步保证至少 3 分;若选第 2/3 步,对手能压到 2。注意:第 3 支的叶子 14 完全无用——对手不会让你走到那儿去。这正是 Alpha-Beta 要利用的"无用性"。

6. 易错点 + 老师原话

  • 易错①:MAX/MIN 记反。先确认根是谁、谁先走,再定层号奇偶。
  • 易错②:以为 Minimax 输出"所有解里的最好分"。它输出的是对手最优应对下的值(上例为 3,而非 14)。
  • 易错③:评估函数符号不统一。:正数对我有利,所以我是 MAX。
  • 老师原话("看五步"的由来):

「下棋的高手他高在哪儿?他脑子转得比较快,他一般来说叫下棋看五步?」[稿:86]

「他脑子里头快速的就想我,我下一步你下一步,然后都想的是你是很聪明的……然后倒推回来,我现在这一步下在哪是最好的。」[稿:89]

"倒推"就是 Minimax 的自底向上求值。

7. 考试英文答题模板

Minimax assigns a value to each position: a terminal (or depth-limited) position gets its evaluation ; an internal MAX node takes the maximum of its children's values and a MIN node takes the minimum, with MAX and MIN alternating by depth. The root value is thus the game-theoretic value under the assumption that both players play optimally, and the maximizing player's best move is the child attaining that value. Since the full tree has leaves (tic-tac-toe already has game sequences over 19,683 board layouts), the search must be cut off at a depth limit and evaluated by , which is why pruning is essential to reach acceptable depth.


2.1.7 Alpha-Beta 剪枝 【S 级 · 本讲最重要】

1. 一句话本质

我(Max)已经有一个"保底 6 分"的选择了;现在看另一个选择,发现对手能把它压到 6 或更低 —— 那这条分支后面的叶子连看都不用看,因为它注定不会更好。

2. 定义与性质

两个界:

符号 含义 初值 更新规则
MAX 结点当前已能保证的下界("我至少能拿 ") 沿 MAX 路径只增不减
MIN 结点当前已能保证的上界("对手最多让我拿 ") 沿 MIN 路径只减不增

所有结点(含根)进入时窗口都是 ;区别只在于:是 MAX 结点就只更新 ,是 MIN 结点就只更新 。搜索时把窗口往下传。

剪枝条件:

等价单一判据:

为什么 MIN 层 就能剪(判分核心,必须写清):

设该 MIN 结点 的父结点是 MAX 结点 , 的另一个(已搜完的)孩子给出了 。现在 已算出某个孩子使 。由于 还要取 min,其最终值只会更小或不变,即 。而 取 max, 已经能拿到,所以 永远不会严格优于 现有选择。⇒ 剩下的孩子不用看。

对称地,MAX 层 :父 是 MIN 结点, 是 已有的上界; 且 取 max 只会更大 ⇒ ⇒ 不会选 ⇒ 剪。

更新规则:

AlphaBeta(u, α, β):
  if u is terminal / depth-limit: return f(u)
  if u is MAX:
      v = -∞
      for each child c:
          v = max(v, AlphaBeta(c, α, β))
          if v ≥ β: return v          # β-cutoff,后续兄弟不看
          α = max(α, v)               # α 单调不减
      return v
  else:                               # MIN
      v = +∞
      for each child c:
          v = min(v, AlphaBeta(c, α, β))
          if v ≤ α: return v          # α-cutoff
          β = min(β, v)               # β 单调不增
      return v

三条硬性质:

  1. 不改变结果:根值与 Minimax 完全相同,只删"不可能被选中的"分支。
  2. 剪枝量依赖搜索顺序:同一棵树换个孩子顺序,剪掉的枝数可以差很多(→ 2.1.5)。
  3. 最好情况 ,最坏情况 (→ 2.1.9)。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《Alpha–Beta 剪枝》 · https://oi-wiki.org/search/alpha-beta/

OI 的定义是窗口式的:搜索某结点时维护 与 ,分别表示"考虑所有已搜索过的结点"后 Alpha 玩家(求最大分数)能保证的分数下界与 Beta 玩家(求最小分数)能保证的分数上界;初值 、,随路径下传。MAX 结点用子结点返回值更新 ,MIN 结点更新 ;只要出现 ,该结点的剩余分支不必再搜。OI 明确写出等号也可剪:此时对方已有不劣于 的备选,继续搜索不改变上层结果。

与 PPT 的判据对照:MIN 结点当前值 即剪、MAX 结点 即剪。两者等价,因为 MIN 结点更新后 恰等于当前 ( 单调下降),故 ;MAX 侧同理。窗口判据与"当前值判据"是同一步的两种写法。

2.2 OI Wiki 校订|基本性质

  1. 剪枝不改变根值:被剪的子结点在本层已被支配,无法影响上层选择。例:根 MAX 下有两个 MIN 子结点,前者叶值 得 ,后者叶值 在首叶处触发剪枝,根值仍为 。
  2. MIN 结点首子即可触发剪枝:首子给出的值已不大于 时立刻剪。例:上例第二个 MIN 结点只访问整棵树 个结点中的 个(它自己的 片子叶只看了 片)即返回。
  3. 等号必须剪: 时继续搜索不改变结果。例:某 MIN 结点继承 ,首叶使其 , 成立即剪。
  4. 结点数与顺序相关:最好 、最坏 。例: 的树共 个结点,按题给次序只访问 个( 个内部结点加 片叶),剪掉 个。
  5. 结点上记录的未必是真值:OI 的三种情形说明,只有第一种情形下 (或 )等于实际分数。例:某 MAX 结点记录的 为 ,其子结点返回 ,该结点实得 ,父结点按 更新。
  6. 空间为 :只需保存窗口与当前路径,不建立整棵树。

2.3 OI Wiki 校订|与课堂口径的差异

判据一致:OI 的 与 PPT 的"MIN 层 、MAX 层 "在同一步触发,等号在两套写法里都剪。差异有三处:OI 以玩家命名窗口,课堂以层命名;OI 的参考实现返回 或 (当前界),课堂要求返回当前已算出的 ,两者对父结点的更新效果相同,答卷写 更稳;OI 把" 与 相等也剪"单独强调,课堂把它含在 、 里。考试按课堂的两条判据作答,并补一句"等价于 "。

3. 图示

(a) 剪枝(PPT [PPT:2上:280-289])

Amax(α=-∞,β=+∞进入)┌─────┴─────┐BC(均min,α=-∞,β=+∞进入)┌──┼──┐┌──┼──┐44405044??←叶子搜索:B=min(44,40,50)=40⇒A的α更新为40C的第一个叶子44;v=44,44≤α=40?否→继续若C的第一个叶子是30:30≤α=40⇒立刻剪掉C的剩余孩子

PPT 版面给出的版本:

                    44      max     ← α 更新为 44
                   /  \
                 44    40            ← 左 MIN 返回 44;右 MIN 首叶 40
                / \   / \
              44  40 40  X           ← 40 ≤ α=44 ⇒ 剪掉右 MIN 剩余的 X

(b) 剪枝(PPT [PPT:2上:292-300])

                    44      min     ← β=44(Min 已能把值压到 44)
                   /  \
                 44    68            ← 右 MAX 首叶即 68
                / \   / \
              44  60 68  X           ← 68 ≥ β=44 ⇒ 剪掉剩下的 X

根是 MIN,左孩子 MAX 返回 44 ⇒ 。右孩子是 MAX,首叶 68 ⇒ ⇒ 剪掉剩下的 X。

(c) 完整 4 层例(考试标准题型,已用程序逐叶验证)

树:,深 4;根 为 MAX,第 1 层 为 MIN,第 2 层 为 MAX,第 3 层为叶子。

                       A  MAX   (α=-∞, β=+∞)
        ┌──────────────┼──────────────┐
        B              C              D        MIN
    ┌───┼───┐      ┌───┼───┐      ┌───┼───┐
    E   F   G      H   I   J      K   L   M    MAX
   ┌┼┐ ┌┼┐ ┌┼┐    ┌┼┐ ┌┼┐ ┌┼┐    ┌┼┐ ┌┼┐ ┌┼┐
   5 6 4 7 3 9 2 8 1   5 6 3 ✗✗✗    7 2 4 9 4 1 2 3 4
MAX 结点 三个叶子 该结点值
5, 6, 4 6
7, 3, 9 7(读到 7 就 -cut)
2, 8, 1 8(读到 8 就 -cut)
5, 6, 3 6
— 未访问
— 未访问
7, 2, 4 7
9, 4, 1 9(读到 9 就 -cut)
2, 3, 4 4

手算过程(DFS:;;):

步 结点 传入 计算 结果
1 5→6→6, 递升至 6 6
2 MIN ,;? 否
3 MAX 首叶 7 ⇒ ⇒ -cutoff,剪掉 3 和 9 7
4 — ;? 否; 仍 6 —
5 MAX 2 ⇒ ;8 ⇒ ⇒ -cutoff,剪掉 1 8
6 — 6
7 — ;窗口变 —
8 MAX 5→6→6 6
9 MIN ;检查 ⇒ -cutoff,剪掉 、 整两棵 6
10 — ; 仍 6 —
11 MAX 7→7→7 7
12 MIN ;? 否;
13 MAX 首叶 9 ⇒ ⇒ -cutoff,剪掉 4 和 1 9
14 — ;? 否; 仍 7 —
15 MAX 2→3→4 4
16 — ; ⇒ -cutoff( 已全部算完,故此处无剩余兄弟) 4
17 — ,选 (与 并列最优) 6

结论:根值 6,最优着法 ( 同为 6)。

剪掉的枝与剪因:

被剪内容 位置 剪因(一句话)
叶子 3、9 (MAX,) 已得 7 ; 是 MIN,只会要 ≤6 的值, 的值再好也不会被选
叶子 1 (MAX,) 已得 8 ;同上
、 整两棵(6 个叶子) (MIN,) 首子 已给 6,; 取 min 只会 ≤6, 已保底 6, 不可能更好
叶子 4、1 (MAX,) 已得 9 ; 取 min 只会要 ≤7 的值

访问了 16 个叶子 / 27 个,剪掉 11 个。注意 没有被剪—— 当前只有 7, 有可能超过 7(它返回 4,低于 7,故不影响结果);这正是"剪枝必须能证明不可能更好才剪"的体现。

顺序敏感性:若 返回的不是 6 而是 1,则 , 必须多搜才可能剪;若把最优着法排在最前面,剪枝量最大。这就是实战中"把上一步的最优着法排最前"(killer move / iterative deepening)的由来。

4. 复杂度表

情形 时间 说明
无剪枝 Minimax 全展开
- 最坏 顺序最差时一个枝也剪不掉
- 最好 每层只看 个孩子,等效深度翻倍
- 平均 介于两者之间 随机顺序约
空间 DFS,只存一条路径

:同样的时间可以看两倍深。PPT 表述:

α-β pruning: when both techniques are combined.
In practice, it limits the searching to only O(N) nodes, where N is the size
of the full game tree.
                                       [PPT:2上:305-307]

此处 指"与全树结点数 成线性",实际量级 ,不是"线性时间算法"。

5. 典型例题(再练一题,含反向推理)

Problem. In the tree below (root MAX), apply alpha-beta and list the root value, all pruned subtrees, and the window on entry.

                 MAX  A
          ┌───────┴───────┐
        MIN B           MIN C
      ┌──┴──┐         ┌──┴──┐
    MAX D   MAX E   MAX F   MAX G
    ┌┴┐    ┌┴┐     ┌┴┐     ┌┴┐
    3 5    6 9     1 4     2 7

中文逐步:

  1. :。
  2. :。: → 。
  3. 收到 5 ⇒ 。检查 ?否。
  4. :。首叶 6 ⇒ ⇒ -cutoff,剪掉 9, 返 6。剪因: 至少 6,而 只能容忍 ≤5。
  5. 。:。
  6. :。: → 。 收 4 ⇒ 。
  7. 检查 ⇒ -cutoff,剪掉 整棵, 返 4。
  8. ,选 。

剪掉的:叶子 9、整棵 (含 2,7),共 3/8 个叶子。

反向推理题型(老师提到"只有 40% 同学答对"的那类):

已知某 MIN 结点在获得值 9 后被剪枝,其父 MAX 结点的 为 ,求未知叶子 的取值范围。

解法:被剪 ⇒ 该 MIN 结点当前值 ⇒ 。若该 MIN 结点的值由 决定,则需 才能保证剪枝。老师原话( 时可保证):

「它先得到了一个九。他得到了九以后跟这个值一比,他就知道他要减脂了,那只能是什么,他发现他递减了。……那意味着同时意味着 X 大于等于 13 是没错了。」[稿:128]

6. 易错点 + 老师原话

  • 易错①(最高频):/ 的方向记反。正确:MIN 层看 (递减就剪),MAX 层看 (递增就剪)。老师口诀:

「小层上递减就可以减枝,大层上递增就可以减枝。」[稿:101-104]

"小层" = MIN 层,"大层" = MAX 层。递减 = 当前值 ;递增 = 当前值 。

  • 易错②:剪枝后不返回值,或返回 / 而不是当前 。必须返回当前已算出的 ( 就返回 9 而不是 )。
  • 易错③:/ 初值写反。所有结点进入时都是 ,只是 MAX 只更新 、MIN 只更新 。
  • 易错④:把""当成"任意两数比较"。它指同一时刻传下来的窗口。
  • 易错⑤:以为剪枝会改变根值。不会,结果与 Minimax 完全一致。
  • 易错⑥:把" nodes"读成"线性时间"。原意是"与全树结点数成线性",量级 。
  • 易错⑦:给被剪的孩子填值或填 0。被剪子树根本没有值——画树时打叉,不要填数。
  • 易错⑧:以为 MIN 结点自己没有"第一个孩子之前"的剪枝机会。剪枝只发生在已经算出至少一个孩子之后。
  • 老师原话(为什么是 S 级):

「在搜索的过程中,我们有一个 alpha beta 剪枝优化,剪枝优化?考试的时候经常会考,但是记也挺好记的,大家要记住一个规律,它搜索是怎么搜的?」[稿:98]

「我到这儿的时候,我还不知道我的值是多少,直到我搜完了这棵树。返回了这棵树给我返回了 44?那返回 44 以后我接着去搜这边的数……它给我返回了一个 40。我这个值,就先 update 先更新成 40。当我更新成 40 的时候,我发现我在 min 层上……值在递减,那么就意味着什么?意味着我整个后面的数都不用看了,一刀剪掉没区别。」[稿:101]

「那么 beta pruning?是对于 max 来说的,我在 max 层上发现我在递增,这你也好很好记。」[稿:101]

「意味着给我们争取了更多的时间,意味着我们的机器人可以搜更多层,意味着下棋可以多看几步,这个赢面就更大一点。」[稿:104]

7. 考试英文答题模板

Alpha-beta pruning is a refinement of minimax that maintains a search window , where is the best value the maximizing player can already guarantee (a lower bound, initially ) and is the best value the minimizing player can already guarantee (an upper bound, initially ). At a MAX node, after each child is evaluated we set ; if we cut off the remaining children, because the parent MIN node would then never choose this node. Symmetrically, at a MIN node we set and cut off when . Equivalently, prune whenever . Alpha-beta returns exactly the same root value as minimax—it only removes branches that provably cannot affect the decision—but the number of nodes examined changes: in the worst case (no cutoff ever occurs) and in the best case, which is equivalent to searching twice as deep in the same time. The best case requires good move ordering, i.e. examining the most promising child first.


2.1.8 评估函数必须廉价 【A 级】

1. 一句话本质

你一共只有一秒钟,评估一个局面要 0.1 秒,那你只能看 10 个局面——再准也是输,因为你只算了一步。评估函数的价值 = 准确度 ÷ 耗时。

2. 定义与性质

评估函数(evaluation function):把非终局棋面 映射为实数,表示"对 Max 方有多好"。

要求 理由
廉价(cheap) 预算 固定 ⇒ 评估次数 ;深度 需 次评估
符号与胜负一致 大 ⇔ 对 Max 有利,否则 Minimax 取错方向
对旋转/镜像稳定 否则同一局面算出不同值,浪费且矛盾
可增量更新(加分) 落一子只更新受影响项, 而非

量化:设单次评估 、预算 、分支因子 ,则可达深度

深度对 是对数敏感: 降 4 倍,深度多约 2.5 层( 时)。这一层往往就是胜负之分。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《启发式搜索》 · https://oi-wiki.org/search/heuristic/

OI 定义启发式搜索(heuristic search)为在普通搜索上引入启发式函数(heuristic function)的搜索:该函数基于已有信息对每个分支做估价,据此选择分支或删去无效分支;其例题把估价函数记为 ,同时用于可行性剪枝与最优性剪枝。

本课程的评估函数(evaluation function)是另一个对象: 把非终局棋面 映射为实数,表示对 MAX 方的有利程度,只在博弈树被截断处取值。形式定义为 ,且 增大与 MAX 方胜率提高同向;搜索返回 的递归值,叶值由 提供。

两者的数学形式同为"状态到实数",语义不同:启发函数 估计"从 到目标的代价",参与搜索方向;评估函数 估计"当前局面的静态优劣",替代无法到达的终局值。

2.2 OI Wiki 校订|基本性质

  1. 评估次数约为 :预算 固定,单次耗时 越小,能评估的局面越多。例: ms 时 1 秒可评估 次。
  2. 可达深度对 只对数敏感:- 最好情况的结点数约 ,故 。例: 从 ms 降到 ms, 时 。
  3. 必须与胜负同序:否则 MAX 取到最差步。例:若误用 ,根会选出让对手最有利的一步。
  4. 应对称不变:左右镜像的局面应有同一值。例:棋盘翻转后 改变,会使同一搜索在不同分支上给出矛盾判断。
  5. 应可增量更新:落一子只改受影响的项,单次代价从 降到 。例: 棋盘有 格,而落子只影响所在行、列与两条对角线。
  6. 不得包含搜索:一旦 内部再调用 Minimax,深度限制失效。例:把 写成"再搜 3 层",实际深度变成 ,预算不可控。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的 heuristic 指 ,语境是单 agent 的最短路或背包,且未讨论"必须廉价"这一约束:它的估价函数只在剪枝处调用一次,代价不敏感。课堂的 evaluation function 是双人博弈的静态估值,被调用 次,故 直接换算成可搜层数。另一个差异: 需可采纳才保最优, 不保最优,因为深度截断本身已破坏最优性。考试按课堂定义;若题干出现 heuristic,须说明它指 ,不能与 混用。

3. 图示

t_f(单次评估耗时)│┌────────┴────────┐便宜1μs昂贵1ms││1秒可评估10^6次1秒可评估10^3次││深度(α-β,b=3)≈24深度≈12││看得清残局全凭直觉瞎走
质量vs速度的无差异曲线f的准确度↑★★★│←高质量但贵:搜不深,实战弱│★★│←甜点区:够准+够快(AlphaGo的valuenet就在这)│★│←便宜但不准:搜得深也是乱搜└──────────────────→1/t_f(速度)

4. 复杂度表

评估函数类型 单次代价 tic-tac-toe 可行深度 围棋可行深度
数潜在赢法 或 增量 全程(9 层) 不可行
深度网络(AlphaGo value net) 固定,约 FLOPs — 数十层(配 MCTS)
完整搜到终局(准确度 100%) 9 层可 不可

5. 典型例题

Problem. An engine has a 1-second budget per move. With 2 ms per evaluation it reaches depth 8 (branching factor , alpha-beta best case). With 0.5 ms per evaluation, what depth does it reach?

中文逐步:

  1. 最好情况下深度 的结点数 。
  2. 由 :,而 ⇒ 。
  3. 新 ms ⇒ ⇒ 。
  4. ⇒ ,即多两层半。
  5. 公式验证: ✓。
  6. 解读:耗时降到 ,深度增加 2.5 层。评估函数的常数因子被指数放大——这就是所有引擎都把评估函数用位运算/查表实现的原因。

6. 易错点 + 老师原话

  • 易错①:认为"评估函数越准确越好"。错。预算固定时,准确度与可达深度直接对冲;更准但慢 10 倍的函数在实战中通常更弱。
  • 易错②:认为评估函数与剪枝无关。有关—— 是剪枝的输入,/ 比较的就是 的值; 越慢,单位时间内发生的剪枝决策越少。
  • 易错③:把 当成真实胜负。它只是截断处的估计(heuristic),越靠叶子越不可靠。
  • 老师原话(本考点最核心的一段):

「所有机器人的含金量就都在 evaluation function 里面了,这个 function,它不能很复杂。你算一个 function 本身就要用很长的时间,那就不划算了。」[稿:92]

「因为我们下棋的时候又不是让你无限思考的。大家都有时间限制的,你在几秒钟之内必须要下走出这一步,否则你就输了。在有限的时间里头,你算一个值就要算半天,那你一共算不出来几个值,那你还下什么?」[稿:92]

「不行,它首先要简单。它算的时间要非常的少,用时要非常少,这样的我们才可能在有限的时间里头算,尽可能多的局面。……这个函数设计的好坏……就决定了你下棋的时候你机器人可以看几步?」[稿:94-95]

纪要版([纪要:2上:8-9]):「评估函数(Evaluation Function):在博弈中,评估函数用于快速判断局势优劣。该函数必须设计得足够简单,以保证在有限时间内能评估尽可能多的局面。」

  • 易错④(段位):

「聪明程度……取决于你 evaluation function……段位是什么?……有的聪明,有的笨……另外,还有一个你能搜的层数,有多深,搜的越深嘛,段位越高。」[稿:104]

7. 考试英文答题模板

An evaluation function assigns a numeric score to a non-terminal position, estimating how favourable it is for the maximizing player. Crucially it must be cheap: with a fixed time budget per move, the number of positions that can be evaluated is , and the reachable depth under alpha-beta grows only logarithmically with , roughly . A function that takes an order of magnitude longer therefore costs several plies of look-ahead, which in practice matters far more than a marginal gain in accuracy. The design goal is a deliberate trade-off between accuracy and speed: typically a simple, symmetric, incrementally updateable score (e.g. counting potential winning lines) rather than a costly exact analysis. This is also why engine "levels" differ both in the evaluation function and in the depth searched.


2.1.9 剪枝不改变最坏复杂度 【S 级 · 判断题必考】

1. 一句话本质

剪枝是"运气好的时候少干活",不是"保证少干活"。只要问"剪枝能不能把 降到多项式"——不能,最坏情况下一个枝也剪不掉。

2. 定义与性质

必须分清四个层级:

层级 是否被剪枝改变 例子
解空间大小(叶子数 / 解的数量) 不变 皇后仍是 叶; 仍是 2 个解
最坏情况复杂度 不变 子集树仍 ;- 仍
最好情况复杂度 变(变小) ;
实际运行时间 / 常数因子 变(变小,常达几个数量级) 4 皇后 65 → 17 个结点

为什么最坏不变(严格论证):

  • 约束函数:存在实例族使 Check 在几乎每个前缀上都返回 true,直到最深层才失败 ⇒ 树全展开 ⇒ (或 )。
  • 限界函数:best 的更新依赖搜索顺序。若最优解位于最后被访问的叶子,则此前每个结点都有 ,一个枝都剪不掉 ⇒ 。
  • -:若每个 MAX 结点的孩子按递增排列、每个 MIN 结点的孩子按递减排列(最差顺序),则 MIN 结点在最后一个孩子之前不会出现 ,MAX 同理无 ⇒ 零剪枝 ⇒ 。

即

剪枝只把常数变小、把最好情况压低,没有改变 growth rate。这与"快速排序的 pivot 选择不改变最坏 "是同一类命题。

正确的三段式表述:

剪枝只压缩左边两项的差距。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《搜索优化》 · https://oi-wiki.org/search/opt/

OI 把剪枝定位为常数级优化:DFS 的时间复杂度特别高,剪枝只是让程序在实际数据上少走弯路,从未声称降低阶。形式陈述可写为:设解空间树的结点数为 ,剪枝函数为 ,则对任意实例 有 ,且 。前式说明剪枝只会少访问,后式说明存在实例使剪枝完全失效。

后式的证明方式是构造:对每个 给出一个使 永不返回"剪"的实例族。这类实例称为零剪枝实例(no-pruning instance)。

2.2 OI Wiki 校订|基本性质

  1. 约束函数存在零剪枝实例:取 0/1 背包 、、,所有子集可行,Check 恒真。例: 时 个结点全部访问。
  2. 限界函数存在零剪枝实例:最优解最后被访问,则此前每个前缀的界都严格大于当时的 。例:同上 ,按"先不取"的次序,最优 落在第 片叶;在到达它之前,任何前缀的界都是 ,故一个枝也剪不掉。
  3. - 存在零剪枝次序:每个 MAX 结点的最优子最后、每个 MIN 结点的最小子最后。例:,两个 MIN 子结点为 与 ,按此次序第二个 MIN 结点的首叶 , 个结点一个不剪。
  4. 最好情况只降低底数,不降低阶: 变为 。例: 时每层分支从 降到约 ,仍是每层常数个分支的指数。
  5. "指数降到多项式"蕴含 :子集和、TSP 分别是 NP-complete 与 NP-hard,若剪枝能多项式求解即得多项式算法。例:子集和的最坏实例就是第 1 条的全可行实例。
  6. 常数与最好情况确实下降:4 皇后从 个结点降到 个,约 倍。例: 时上界仍是 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 不讨论"剪枝是否改变复杂度",只用"时间复杂度特别高"作定性说明;课堂把它列为 S 级判断题,要求给出零剪枝实例的构造,并区分"解空间大小、最坏、最好、实际"四个层级。术语上 OI 的"骗分"对应课堂的"实际运行时间下降",不能写成"复杂度下降"。考试按课堂:答案必须同时出现"最坏不变"与"最好/实际变小"两句话,缺一即扣。

3. 图示

时间↑│┌──────O(2^n)上界(剪枝前后同一个)│/│/←无剪枝:实际≈上界│┌────────────/←剪枝后:实际贴在下界附近│┌─┘·······│┌─┘┌───────────────O(n)最好情况(剪枝后才可能出现)│┌─┘└──┴──────────────────────────→n信封(envelope)不变=最坏复杂度不变曲线在信封内下移=实际运行时间变短

4. 复杂度表

问题 无剪枝 剪枝后最坏 剪枝后最好 能否降到多项式
子集和 / 0-1 背包 否
N 皇后 否
TSP 分支限界 — 否(NP-hard)
- 否(仍指数)
Turnpike 指数 指数 线性 否

5. 典型例题(判断题专练)

Problem. Mark T/F and justify: (a) "With pruning, backtracking reduces the worst-case complexity of subset-sum from to ." (b) "Alpha-beta pruning reduces the number of nodes searched from to in the best case." (c) "Pruning reduces the size of the solution space." (d) "Since alpha-beta returns the same result as minimax, it must examine the same number of nodes."

中文逐条:

  • (a) F。剪枝不改最坏:存在实例使 Check 几乎总返回 true,树全展开 ⇒ 仍 。且子集和是 NP-complete,若真能多项式求解就证明了 P=NP。
  • (b) T。这是最好情况,条件是每个结点的最优孩子被最先访问。 仍是指数,只是底数从 变成 。
  • (c) F。解空间由问题定义决定(子集树 / 排列树 ),剪枝只是不访问其中一部分。
  • (d) F。"结果相同"(correctness)与"工作量相同"是两件事。结点数可少到 。

6. 易错点 + 老师原话

  • 易错①:把"平均/实际快很多"写成"复杂度降低"。判卷必扣。应写"实际运行时间显著下降,但最坏复杂度仍为 "。
  • 易错②:以为"剪枝不改复杂度"意味着"剪枝没用"。相反——最好/平均情况能把指数底数从 降到 ,等效于深度翻倍。
  • 易错③:把 - 最好情况的 说成"降到多项式"。它依然指数。
  • 老师原话:

「backtracking 的时间复杂度是很难估计的,但是,它跟很多东西有关,同时它还跟你搜索的顺序有关。」[稿:82-83]

「你那个 check 函数尽可能的返回 no 你就剪得快一点。」[稿:83]

注意老师用的是"快",不是"复杂度降低"——这正是本考点的语言陷阱。

  • 平行案例(可作类比记忆):讲 K 路归并时老师反复强调 K 只影响常数、不影响 这个阶:

「相当于它的时间复杂度,也是 N 倍 log n,它只是小了一个很小的常数。」[稿:230]

与本考点是同一种思维:区分"阶(order)"与"常数(constant)"。

7. 考试英文答题模板

Pruning does not change the worst-case complexity of backtracking; it only reduces the actual running time and the constant factor. The solution space is defined by the problem — leaves for a subset tree, for a permutation tree — and pruning merely refrains from visiting part of it, so neither the number of candidates nor the number of solutions changes. Formally, for every pruning function there exist adversarial instances on which no branch is ever cut: for a constraint function, instances where every prefix is feasible until the last level; for a bounding function, instances where the optimum is found last; for alpha-beta, the worst possible move ordering. These give , and respectively. Pruning can therefore only shrink the gap between the best and the worst case: subset-sum stays exponential, -queens stays , and alpha-beta stays in the worst case while improving to in the best case. The claim that pruning reduces to polynomial time is false.


2.1.10 Turnpike 重构问题 【B 级】

1. 一句话本质

有人给了你高速公路上所有两两出口之间的距离(一堆数),让你反推出口在哪——最大那个距离一定是两头,从两头往里剥,剥不动就回溯。

2. 定义与性质

Problem (Turnpike Reconstruction). Given points on the -axis with coordinates ; assume . There are pairwise distances. Given this multiset, reconstruct a point set producing exactly it.

三条关键推理:

  1. 确定 : ⇒ 。
  2. 确定端点: ⇒ 。
  3. 修剪:每确定新点 ,(对所有已定点)必须都在 中,否则不合法。

两选一的分支结构:设当前剩余最大距离 ,新点只能放在

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《回溯法》 · https://oi-wiki.org/search/backtracking/

OI 未收录 Turnpike 重构(Turnpike reconstruction)的独立条目,其回溯页只给出通用过程:构造空间树、遍历、遇边界条件即回退、达目标条件即输出。以下按教材口径补足形式定义。

输入:多重集 ,,元素为两两距离。输出:点集 ,满足 且多重集 与 相等。判定条件是多重集相等(multiset equality),不是集合相等:重复距离必须逐一对应。

搜索把 的两端向中间填:已定 与 ,每步取剩余最大距离 ,令新点落在右端 或左端 ,再用"与所有已定点相容"检验该选择。

2.2 OI Wiki 校订|基本性质

  1. 由 唯一确定:解方程 即可反解。例: 时 。
  2. 两个端点被最大距离锁定:,故 、。例: 时 。
  3. 每步只有两个候选: 属于新右端或新左端。例:剩余最大为 时取 或 。
  4. 剪枝要求与全部已定点相容:新点 须满足 对所有已定 成立,并在多重集中扣减。例:试 需扣 ,而 已被用尽,故失败回退。
  5. 最坏仍为指数:两个端点定下后还剩 个点,每层两个候选,叶数上界为 。例: 时上界为 片叶,实际因剪枝只需数次分支。
  6. 失败分支必须恢复 :删除与恢复必须成对。例:试 失败后要把 与 重新插回。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的回溯页两个例题(八皇后、迷宫)都不需要"删除-恢复多重集",因此完全没有涉及本考点的核心难点: 是多重集,重复元素的计数必须精确。课堂按教材与 PPT 的 Place / Check 伪代码,要求写出两选一的分支与恢复动作。考试按课堂,OI 只能作为"回溯通用过程"的旁证。

3. 图示

0356810●────●────●──●──●───────●x1x2x3x4x5x6两两距离(15个):356810←与x1的距离2357135242⇒排序{1,2,2,2,3,3,3,4,5,5,5,6,7,8,10}✓与题给D一致

PPT 板书([PPT:2上:138-142])标出这条"两选一"链:

   x5 = 8       x2 = 2
   x4 = 7       x2 = 3
   x3 = 6  x2 = 4  x3 = 4  x4 = 6
                       x3 = 5

4. 复杂度表

量 值
输入规模 个距离
由 求 (解方程)或 (遍历)
排序
最大距离优先的搜索 实践中接近
最坏 仍指数( 量级)
空间 递归栈 + 距离多重集

5. 典型例题

Problem. . Find , the extreme points, and a valid point set.

中文逐步:

  1. ⇒ 。
  2. ,;删去 。
  3. 剩余最大 8 ⇒ 或 。 - 试 :删 、。剩 。
  4. 剩余最大 7 ⇒ 或 。 - 试 :删 。剩 。
  5. 剩余最大 6 ⇒ 或 。 - 试 :需删 ,但 1 已删过 ⇒ Check 失败,回溯。 - 试 :需删 ,需两个 4 但只剩一个 ⇒ 失败,回溯。
  6. 回溯到第 4 步,改试 :删 。剩 。
  7. 剩余最大 6 ⇒ 或 。 - 试 :删 。剩 。
  8. 剩余最大 5 ⇒ (两选项 与 重合)。删 ⇒ 恰剩空集 ✓。
  9. 解:。

验算:两两距离 ,排序后与原 一致 ✓。

6. 易错点 + 老师原话

  • 易错①:只试 option 1()。必须也试 option 2()。
  • 易错②:忘记回溯时恢复 。PPT 代码每个分支都有对称的 Insert:

c if ( !Found ) { /* if does not work, undo */ for ( i=1; i<left; i++ ) Insert( |X[right]-X[i]|, D); for ( i=right+1; i<=N; i++ ) Insert( |X[right]-X[i]|, D); }

  • 易错③:只与最近点检查距离。必须与所有已定点检查(PPT 的 Check(D_max, N, left, right) 遍历 i=1..left-1 与 i=right+1..N)。
  • 易错④:想"聪明地"先猜中间点。老师现场纠正:把距离存成矩阵不可行,因为你不知道每个距离是哪两点产生的:

「你脑子里想的是我把它存成一个矩阵?你怎么存一个矩阵?因为你并不知道每一个距离它是哪两个点之间产生的,你没办法把它放到矩阵相应的位置上去。你要能放到相应的位置上,这问题就解决了。」[稿:74]

  • 老师原话(与 2.1.5 共用):

「大的距离它,你可以严格的理论证明,大的距离只对应两种,最多就两种,不可能有第三种。」[稿:80]

7. 考试英文答题模板

In Turnpike reconstruction we are given the multiset of all pairwise distances and must recover the coordinates. First follows from ; then and , because the largest distance can only be realized by the two extreme points. At each subsequent step we take the largest remaining distance and branch on its two possible owners: either or . The choice is validated by a pruning function that checks, for every already-placed point , that ; if not we backtrack, restoring the distances that were deleted. Because each step has only two candidates, a failed branch discards half of the remaining search tree, which is why the largest-distance-first order is essential. The algorithm returns a valid point set such as for the standard example, with a worst case that remains exponential.


2.1.11 AlphaGo 与剪枝 【B 级】

1. 一句话本质

围棋的树大到 - 连一层都搜不完,所以 AlphaGo 换了打法:用神经网络只猜几步值得看(代替分支穷举),再用神经网络直接估局面分(代替搜到底)——剪枝从"证明可以剪"变成"赌它不值得看"。

2. 定义与性质

经典 - AlphaGo
分支处理 穷举所有合法走法 策略网络(policy network)只采样少量高概率走法
叶子估值 手工评估函数 价值网络(value network) 直接预测胜率
搜索框架 Minimax + - MCTS(Monte Carlo Tree Search)
剪枝正确性 sound(不改变结果) heuristic(概率近似,可能漏最优)
复杂度 最好情形 与搜索预算成线性

AlphaGo 的四个组件:

  1. SL policy network(监督学习):从人类棋谱学"人会下哪"。
  2. RL policy network(强化学习):自我对弈提升"哪步赢面大"。
  3. Value network:输入棋面,输出胜率 ,替代深搜到终局。
  4. MCTS + UCT:用 平衡"利用已知好着"与"探索少访问着"。

与 - 的关键区别:- 的剪枝是可证明安全的("该分支上下界已被支配");AlphaGo 的"剪枝"是概率近似("这走法不太可能好"),所以它不保证找到最优解,只是胜率足够高。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按课堂讲义与教材口径整理。

AlphaGo 的形式结构可拆成三个映射。策略网络(policy network) 给出局面 上各着法 的概率,搜索只保留概率最高的 个;价值网络(value network) 直接预测当前局面的胜负期望,替代"搜到终局";蒙特卡洛树搜索(Monte Carlo tree search, MCTS)用大量模拟把两者组织起来,选择时用

其中 为平均回报, 为结点访问次数, 为该着法的访问次数, 控制探索强度。

与 - 的定义性差别在正确性:- 的剪枝有支配关系作保,根值不变;AlphaGo 的取舍只有概率依据,不保证找到最优着法。

2.2 OI Wiki 校订|基本性质

  1. 宽度由 降为 :策略网络截断分支,故每层只展开 个候选。例:围棋 ,取 ,宽度降约 倍。
  2. 叶值由搜索改为估计:价值网络在任意局面给出胜率,故不必到达终局。例:围棋中盘无终局可判,仍可得到 。
  3. 代价与模拟次数成线性:每次模拟走一条路径 , 次模拟为 。例:、 时约 步。
  4. 不保证最优:被先验压低的着法不会被子树证明为劣。例:先验概率 的着法仍可能是最优,只能靠探索项 补救。
  5. 探索项防止过早收敛:访问次数少的着法分母小,因而被优先选中。例:某着法 而兄弟为 ,其探索项约为后者的 倍。
  6. 仍是博弈树搜索:骨架未变,变的是宽度、叶值与调度。例:把 、 代回,MCTS 退化为带评估函数的 Minimax。

2.3 OI Wiki 校订|与课堂口径的差异

OI 无对应条目,故无口径冲突可言;课堂的定位是"用学习到的先验替代可证明的剪枝"。考试只考概念判断:策略网络对应"剪枝/缩小宽度",价值网络对应"评估函数",MCTS 对应"搜索框架",三者都不保证最优。网络结构、训练流程与具体棋局不在考查范围。

3. 图示

经典α-βAlphaGo─────────────────rootroot/|\/|\(b)(b)(b)←全展开(p1)(p2)...(pk)←policynet只给k个/|\/|\/|\|||b^2...MCTS多次采样rollout...valuenet直接给叶子打分v(s)深度d⇒b^d⇒不必搜到底安全性:可证明不漏最优安全性:概率近似,可能漏
   19×19 Go : b≈250, d≈150 ⇒ 全树 ≈ 10^170
   宇宙原子数 : ~10^80
   ⇒ 全树比宇宙原子多 10^90 倍 ⇒ 穷举在物理上不可能

4. 复杂度表

算法 tic-tac-toe 国际象棋 围棋
纯 Minimax
Minimax + - 可行 可行(深 6–10) 不可行
AlphaGo(policy + value + MCTS) 可行 可行 可行(超越人类)

5. 典型例题

Problem. Explain why - pruning alone cannot make Go tractable, and how AlphaGo circumvents each difficulty.

中文逐步:

  1. 宽度问题:,- 最好情形仍需 个分支/层;深 10 层 ⇒ ,单步仍不可行。⇒ 策略网络把宽度压到约 10 个候选。
  2. 深度问题:围棋几乎没有中途可判终局的截断,必须搜到很靠后才知道胜负。⇒ 价值网络在任意局面直接给胜率。
  3. 评估函数问题(对应 2.1.8):手工评估函数在围棋上极不准("厚势""薄味"难量化)。⇒ 用深度网络学习评估函数。
  4. 剪枝安全性问题:策略网络的"剪枝"不可证明,故需 MCTS 用大量采样纠偏——访问少的走法仍有机会被 UCT 探索。
  5. 结论:AlphaGo = "在博弈树搜索的骨架上,把宽度交给 policy net、把叶值交给 value net、把搜索交给 MCTS"。基本思想仍是古老的博弈树搜索:

「它基本原理还是很古老的,它用了很聪明的局势判断的方法。」[稿:83]

6. 易错点 + 老师原话

  • 易错①:说"AlphaGo 用 - 剪枝赢了围棋"。错,它用 MCTS,不是 -。
  • 易错②:说"AlphaGo 靠暴力枚举/更强硬件"。错,它靠评估函数(神经网络)+ 采样。
  • 易错③:把"策略网络"与"价值网络"搞混。策略(policy)回答"下哪儿",价值(value)回答"现在赢面多大"。
  • 易错④:以为 tic-tac-toe 的 是"对局数"。它是局面数();对局序列数是 。
  • 老师原话:

「alphago 强就强在它这个 evaluation function。他做的非常好,然后他会自学习,然后他会自适应,然后越来越强。」[稿:92]

「即使是只有 9 宫格,你如果枚举它所有的可能性的话,数量也是很惊人的,更不用说围棋盘,那是 19×19 的,不是 9×9 的。然后所以曾经人类很乐观,觉得计算机不可能在围棋上打败人类,但是人家后面做到了。」[稿:83]

7. 考试英文答题模板

Go has a branching factor of about 250 and a game length of about 150 moves, giving a full game tree of roughly nodes — far beyond exhaustive search, so alpha-beta pruning alone is useless there: even in its best case it retains about branches per level, which is still enormous, and it additionally requires a reliable evaluation function and a terminal test, neither of which exists for Go. AlphaGo therefore replaces the two components of the classical framework with learned approximations: a policy network provides a prior over moves, restricting the branching factor to a handful of plausible moves (replacing exhaustive expansion), and a value network predicts the win probability from an arbitrary position (replacing search to the end of the game). These are embedded in Monte Carlo Tree Search with UCT rather than in minimax with alpha-beta. The basic idea is thus still the old game-tree search with a very good position-evaluation function; what changed is that the pruning is now heuristic rather than provably safe, so optimality is no longer guaranteed — only very strong play.


附:本讲考点速览与复习路线

编号 考点 级别 题型 必背一句话
2.1.1 回溯框架 A 简答 / 写代码 部分解 + DFS + 剪枝 + 撤销
2.1.2 子集树 vs 排列树 A 画图 选/不选 ;每个恰一次
2.1.3 约束 vs 限界 A 选择 砍不合法 vs 砍不够优
2.1.4 N 皇后 A 画树 列号;
2.1.5 搜索顺序(最大距离优先) S 简答(偏理论) 候选最少者先搜,砍一枝 = 砍半树
2.1.6 Minimax A 画图 Max/Min 交替,自底向上
2.1.7 Alpha-Beta S 计算 / 画树 必考 MIN 层 剪;MAX 层 剪
2.1.8 评估函数必须廉价 A 简答 准确度 ÷ 耗时;深度对耗时对数敏感
2.1.9 剪枝不改最坏复杂度 S 判断 只改常数与最好情况
2.1.10 Turnpike B 了解 最大距离 = 两端点,两选一
2.1.11 AlphaGo B 背景 policy + value + MCTS ≠ α-β

复习路线:先默写 2.1.7 的伪代码与 / 更新规则(S 级、必考),再手算本节 4 层例题并标注每个结点的窗口;然后背 2.1.5 与 2.1.9 的因果链(这两条常在同一道简答里问"剪枝能省多少");最后用 2.1.4 的 4 皇后手算一遍完整剪枝树,确保 不出错。

三句话总结本讲:

  1. 回溯 = 解空间树 + DFS + 剪枝,正确性依赖约束的单调性。
  2. 优化顺序:先做强 Check(约束/限界),再调搜索顺序(候选最少者先),最后才是常数层面。
  3. 无论怎么剪,最坏复杂度不变;能变的只有最好情况与常数。
2.2.1 – 2.2.14

详解 04 · 分治

配套 A0_速查册.md 的「2 下」部分。速查册给结论,本册给推导、图示与手算过程。 证据约定:[PPT:2下:n] = 01_原始素材/PPT_文本/ADS/第二讲(下)Divide and Conquer.txt 行号;[稿:2-逐字稿:n]、[稿:3-逐字稿:n] = 课堂录音文稿行号;[纪要:2-conclusion] = 课堂录音文稿/2-conclusion.md。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

分治三步Divide/Conquer/Combine(2.2.1)→能用的前提:子问题独立+可合并(2.2.2)→但"能分"不等于"乘对数":O(N)的遍历也是分治(2.2.4)→代价落在哪一步决定了算法性格(归并/快排)(2.2.3)→于是全部工作收拢为一件事:解递归式T(N)=aT(N/b)+f(N)├代入法(guess+induction)(2.2.5)├递归树法(非完美树也要会)(2.2.6)└主定理三版(含正则条件、含形状限制)(2.2.7–2.2.9)→综合应用题:K路归并(全课程正答率最低)(2.2.10–2.2.12)

老师对本讲的定位是一句话:「这一章的重点,divide and conquer 的重点就是在于怎么分析时间复杂度。」 [稿:2-逐字稿:170] 也就是说,分治的代码本身往往是三五行递归调用,考的是你会不会写递归式、会不会解递归式。所有 2.2.5–2.2.12 都服务于这一件事。


2.2.1 分治三步 【A 级】

1. 一句话本质

把一个你算不动的大问题,切成若干个同型的小问题,递归让"别人"算,最后把答案拼回去。切与拼是免费的还是收费的,决定复杂度。

2. 定义与性质

PPT 原文 [PPT:2下:10-15]:

Recursively:
  Divide   the problem into a number of sub-problems
  Conquer  the sub-problems by solving them recursively
  Combine  the solutions to the sub-problems into the
           solution for the original problem
      General recurrence: T(N) = aT(N/b) + f(N)

三点必须逐字记住:

  • Divide:把规模 的原问题拆成 个规模为 的子问题。
  • Conquer:递归求解。终止条件(base case)是分治能落地的前提——规模足够小(一般 某常数)时直接算,此时 。
  • Combine:把 个子解合成原解,花 。

由此得到通用递归式(general recurrence):

参数含义与常见取值:

参数 含义 典型值
子问题个数(分叉数 branch factor) 归并/快排 2;Karatsuba 3;K 路归并
规模缩小比例 2、3、4、
子问题规模 —
切 + 拼的代价(divide + combine) 归并排序 ;树遍历

边界细节(PPT 明说可以忽略) [PPT:2下:115-117]:

Details to be ignored:
   if (N / b) is an integer or not
   always assume T( n ) = Θ( 1 ) for small n

即: 还是 不用纠结(不影响渐进阶),小规模一律当 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《分治》 · https://oi-wiki.org/basic/divide-and-conquer/

OI 把分治定义为:把一个复杂问题分成两个或更多的相同或相似的子问题,直到子问题可以直接求解,原问题的解即子问题解的合并。这个定义把三件事说成必须同时成立的条件:分解出的子问题与原文同型(否则递归调用不成立)、分解过程必然触底(存在规模足够小、可直接求解的边界)、以及存在一个合并算子把子解拼成原解。OI 还把递归与分治分开:递归是让函数直接或间接调用自身的编程技巧,分治是基于递归去解决具体问题的算法思想;所以"写了递归"不等于"用了分治","分治"也不必真的递归(归并排序有自底向上的倍增写法)。OI 指出分治的执行顺序是"分解 → 解决(触底)→ 合并(回溯)",与二叉树的后序遍历同构:先左右下潜,回溯时才做合并。

2.2 OI Wiki 校订|基本性质

  1. 规模严格递减(well-foundedness):递归能终止,是因为每层规模由 变为 且 , 使规模构成下降序列,必在有限步到达基例。例: 的规模序列是 ,第 3 层触底。
  2. 代价只落在 Divide 与 Combine:Conquer 的代价由递归式中的 自身描述,不另计;故 就是"切"与"拼"的全部开销。例:归并的切分只看中点,代价 ; 全部出现在 merge。
  3. 回溯顺序是后序(postorder):合并必须在两个子解都返回之后才能做,故合并次数等于内部结点数。例:对 ,先得 与 ,最后才 merge 成 ,合并发生在两次递归返回之后。
  4. 递归与分治不等价:单链表求长度可写成递归,但没有子解合并,只是递归遍历。例:size(head)=size(head->next)+1 的返回值只是计数相加,不构成"子解拼原解"。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的三步只有定性描述, 出现在《复杂度》页的主定理一节,不在《分治》页;本课程把三步与递归式写在一起,是为了让三步直接产出可解的方程。第二处差异是规模:OI 说"两个或更多相同或相似子问题",不要求等规模,也不谈取整;本课程默认等分并明说忽略 是否整除。考试按本课程写 与 ,需要时补一句"取整不影响渐进阶"。

3. 图示

以归并排序 为例,把递归式画成一棵树:

                      cN                     ← 第 0 层,代价 cN
              /                \
          cN/2                cN/2           ← 第 1 层,2 个节点,合计 cN
         /    \              /    \
     cN/4    cN/4        cN/4    cN/4        ← 第 2 层,4 个节点,合计 cN
      ...     ...         ...     ...            每层合计都是 cN
    T(1) T(1) T(1) T(1) ... T(1) T(1) ...    ← 第 log₂N 层,N 个叶子,合计 Θ(N)

每层代价之和恒为 ,层数 ⇒ 。这张图是后面所有递归树方法的母版。

4. 复杂度表

分治算法 结果
Merge sort 2 2
Quick sort(平均) 2 2
树遍历 tree traversal 2 2
二分查找 binary search 1 2
最大子列和(分治解) 2 2
最近点对 2 2
整数乘法(Karatsuba) 3 2

注意最后一行:分治的结果可以是 、、,不一定是 (这就是 2.2.4)。

5. 典型例题

Write the general recurrence for Mergesort and identify the three steps in the code.

中文逐步:

  1. Divide:从中间切成两半 ⇒ ,两个 。
  2. Conquer:递归调用 Mergesort 两次。
  3. Combine:把两个有序子数组合并成一个有序数组,线性扫描,。
  4. 于是 ,。
  5. 解之(2.2.7 case 2):, ⇒ 。

6. 易错点 + 老师原话

  • 易错:只写 Divide/Conquer 两步,漏 Combine。三步缺一不可,尤其 Combine 是复杂度分析里唯一可能有非平凡代价的一步。
  • 易错:把 和 搞混。 是个数(乘在 前面), 是分母(在 里面)。写反了主定理全错。
  • 老师原话(本讲定位):「这一章的重点,divide and conquer 的重点就是在于怎么分析时间复杂度。」 [稿:2-逐字稿:170]

7. 考试英文答题模板

Divide-and-conquer solves a problem recursively in three steps: Divide the instance into subinstances each of size ; Conquer them by recursion (with a base case); Combine the sub-solutions into one solution at cost . This yields the general recurrence , which is the sole object of the complexity analysis.


2.2.2 适用条件:子问题独立 + 可合并 【S 级】

1. 一句话本质

分治的两条准入证:切开的子问题互不干扰(各算各的,答案不变);子问题的最优解能拼出全局最优解(拼得回去)。缺一条就不是分治,而是 DP 或贪心的地盘。

2. 定义与性质

条件一:子问题独立(independent / non-overlapping)。 两组子问题之间不共享数据、不重复求解。若子问题大量重叠,分治会指数级重复计算,此时应该记忆化/填表——那就变成动态规划(Dynamic Programming)。

条件二:最优子结构可合并(mergeable / optimal substructure)。 原问题的最优解必须能由子问题的最优解构造出来。数学上要求目标函数对"拼接"是单调/可加的:

若最优解可能是"左半边取次优 + 右半边取次优"拼成的,这个等式就不成立,分治失效。

两个典型反例([纪要:2-conclusion] 四、1):

反例 能分吗 为什么合不了
找众数(mode) 能分成左右两半 整体的众数不一定是左半或右半的众数——可能由两边都不占多数的元素拼成多数。子问题的多数结论不可继承
硬币找零(coin change) 能按"第一枚硬币"分 不同分支会重复计算同一个子问题(如凑 7 元),形成重叠子问题;且需比较所有分支取 min,属 DP
加权活动安排 能按"是否选某活动"分 子问题最优解之和不等于全局最优(权重让"多选"未必更优),需 DP 决策顺序

对照成功案例:最大子列和虽然需要处理"跨越中线的答案",但它只需 线性时间就能把跨越情形算出来,所以合得回去 —— 这就是"可合并"的具体含义。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《分治》 · https://oi-wiki.org/basic/divide-and-conquer/

OI 列出分治法所解问题的三条特征:规模缩小到一定程度可以容易解决;可以分解为若干规模较小的相同问题,即具有最优子结构性质,且子问题的解可以合并为原问题的解;分解出的各子问题相互独立,即子问题之间不含公共的子问题。第三条之后 OI 加了 warning:若子问题不独立,分治会重复求解公共子问题、做许多不必要的工作,此时虽也可用分治,但一般用动态规划较好。要注意 OI 的"最优子结构"用词与 DP 相同,含义却只是"合并算子存在",并不要求最优性原理;判据是可构造性,不是最优性。

2.2 OI Wiki 校订|基本性质

  1. 合并算子必须存在:形式上要求 对每个合法输入成立,否则递归返回的子解无法组装。例:求众数时左半众数 出现 2 次、右半众数 出现 2 次,而全局众数 出现 3 次,两边都不占多数,combine 无定义。
  2. 独立性决定多项式还是指数:两个子问题共享数据时同一子问题被反复求解,调用数按分叉数指数增长。例:朴素递归 的展开中 被算 2 次、 被算 3 次。
  3. 只对可加或单调的目标才合得回去:最大子列和的答案不能由两半答案直接相加,必须补一次跨中线的 扫描,这一步正是把不可加部分补成可合并。例: 的最优子列 跨中线,两半各自的答案都不是它。
  4. 第三条是效率判据而非正确性判据:OI 明说不独立时仍可用分治,只是做无用功。例:重叠子问题的分治仍给出正确答案,但 是指数级。

2.3 OI Wiki 校订|与课堂口径的差异

OI 把"不独立"归为效率问题(仍可用,DP 更优),本课程把它升级为"分治不可用"的分界线,并用加权活动安排、硬币找零作反例;两种表述的判据其实相同,都要求子问题的解可以合并为原问题的解。差别在评分口径:本课程要答出"独立 + 可合并"两条并给反例,OI 三条中的第一条(规模小到可解)在本课程里并入基例条件。考试按本课程两条作答,再补 OI 的第一条作为基例说明。

3. 图示

P(规模N)┌──────────────┴──────────────┐P₁P₂←必须"干净"地分开┌────┴────┐┌────┴────┐P₁₁P₁₂P₂₁P₂₂子问题之间无共享数据(若P₁₂与P₂₁有交叉→重叠→不能分治)合并判据:OPT(P)==combine(OPT(P₁),OPT(P₂))?成立→分治✓不成立→DP/贪心

4. 复杂度表

子问题关系 典型算法 复杂度
独立(不重叠) 归并排序
独立 + 可在 内合并 最大子列和、最近点对
独立但合并不了 加权活动安排 分治不可用
重叠 硬币找零、矩阵链、LCS 分治指数级 → DP

5. 典型例题

Explain why the maximum-subarray problem can be solved by divide-and-conquer while the 0/1 knapsack problem cannot.

中文逐步:

  1. 最大子列和:任取中间下标 mid,最优子列必为三种之一 —— 完全在左半、完全在右半、跨过 mid。前两种是递归子问题,第三种可以在 内用"从中线向两边扫"穷举端点求出。三者取 max 即可 ⇒ combine 成立、子问题不重叠 ⇒ 分治可用,。
  2. 0/1 背包:若切成"前一半物品/后一半物品",左右各自的最优选择所占用的容量不同,无法独立决策(容量是共享资源)。要合并必须知道每个剩余容量下的最优值 —— 这已经是在枚举/填表,子问题不独立 ⇒ 只能用 DP。
  3. 结论:判断标准不是"能不能切",而是"切完的答案能不能拼回去且不重复算"。

6. 易错点 + 老师原话

  • 易错:以为"能给问题划一道线"就是分治。加权活动安排能划线,但合不了([纪要:2-conclusion] 四)。
  • 易错:把"子问题重叠"与"子问题重复计算"当两件事。它们是同一个问题的两种描述:重叠 ⇒ 朴素递归必然重复算 ⇒ 分治复杂度爆炸。
  • 老师留的讨论题([稿:2-逐字稿:170])就是让大家自己辩"哪些能分哪些不能分",这是 S 级判断题的主要来源。

7. 考试英文答题模板

Divide-and-conquer requires two properties. (1) Independence: the subinstances are disjoint, so solving one never changes the answer of another; if they overlap, a naive recursion re-solves the same subinstance exponentially many times and memoisation/DP should be used instead. (2) Mergeability: an optimal solution of the original instance must be constructible from optimal solutions of the subinstances (optimal substructure). Weighted activity selection and 0/1 knapsack violate (2) because the capacity/weight couples the two halves; mode finding violates it because a non-mode of a half can become the global mode.


2.2.3 归并偏 Combine、快排偏 Divide 【A 级】

1. 一句话本质

同样写 ,归并排序的 花在最后拼,快排的 花在开头切。

2. 定义与性质

维度 Mergesort(归并) Quicksort(快排)
Divide 平凡:直接对半切 ,不看数据 不平凡:选主元 pivot 并 partition,必须扫一遍
Conquer 递归排序两半 递归排序两段
Combine 不平凡:merge 两个有序表, 平凡:return 时已就位,什么都不用做
代价落点 Combine-centric Divide-centric
递归式 确定 ,依赖划分
最坏 稳定保证 (每次切出 0 和 )
稳定性 稳定 不稳定

关键洞察:两者的渐进复杂度都是 ,但"为什么是 "的理由完全不同。归并的难点是"两个有序表怎么线性合并",快排的难点是"怎么选主元让两边均衡"。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《归并排序》 · https://oi-wiki.org/basic/merge-sort/

OI 的定义是:归并排序是高效的、基于比较的稳定排序算法;其核心是合并(merge)过程——把两个有序数组合并为一个有序数组:从左往右枚举两个表头,每次把较小者写入输出,一表耗尽后把另一表剩余元素整体追加。稳定性由一个细节保证:当前段首元素小于或等于后段首元素时取前段(a[i] <= b[j]),相等时前段下标更小,相对次序不变。分治实现要求把数组分成尽量等长的两段,中点取 ;OI 说明这一步是为保证复杂度,而不是为正确性。OI 另给倍增(自底向上)写法:先把数组切成 个长度 1 的段,再两两合并成长度不超过 、 的段,直到只剩一段;递归不是必需品。

2.2 OI Wiki 校订|基本性质

  1. 稳定性由比较符决定:合并时用小于等于而非严格小于,相等元素取前段,而前段元素在原数组中靠前,故相对次序保持。例: 与 合并,两个 中来自前段的先输出,得 。
  2. 等长切分是 的必要条件:若每次切出 与 ,递归式变为 ,逐层相加得 。例: 时退化为 次比较,而等分只需约 次。
  3. 合并代价是两表长度之和:merge 不比较所有配对,只沿两个表头各走一遍,代价 。例:合并长度 4 与 4 的两个表,至多比较 7 次。
  4. 倍增写法与递归写法同阶:段长按 翻倍,层数 ,每层合并总代价 。例: 时为 共 3 层,每层至多写入 8 个元素。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的归并页只讨论二路 merge 与倍增实现,不区分代价落在 Divide 还是 Combine,也不与快排对照;本考点的 combine-centric 与 divide-centric 是本课程补充的口径,与 OI 不冲突——OI 的 merge 过程恰好就是那个不平凡的 Combine。另一处差异是空间:OI 明确归并的空间为 (可做到 辅助空间但不常用),本课程只讨论时间,被问到空间时要补这一句。快排不在该页讨论,其口径按 PPT 作答。

3. 图示

Mergesort —— 先切(免费),后拼(收费)
  [3 1 4 2]
   /      \          Divide: 直接对半,O(1)
 [3 1]   [4 2]
  / \     / \        Conquer: 递归
[3][1]  [4][2]
  \ /     \ /        Combine: merge,O(N) ← 钱花在这
 [1 3]   [2 4]
    \    /
   [1 2 3 4]

Quicksort —— 先切(收费),后拼(免费)
  [3 1 4 2]   pivot = 3
   /      \          Divide: partition,O(N) ← 钱花在这
 [1 2]   [4]
  ...     ...
  (有序后直接返回,Combine = O(1))

4. 复杂度表

Divide Conquer Combine 总计
Mergesort
Quicksort(最好/平均)
Quicksort(最坏)

5. 典型例题

Both Mergesort and Quicksort obey in the average case, yet only Mergesort guarantees . Explain.

中文逐步:

  1. 归并的递归式是确定的:切点永远是 , 恒定 ⇒ 递归树一定是完美二叉树,深度 ,每层 ⇒ 严格 。
  2. 快排的递归式依赖于输入:切点是主元 rank ,递归式应为 。 - 若 pivot 每次都取到最值 ⇒ ⇒ ⇒ 。 - 若每次 1:1 切 ⇒ 退回 ⇒ 。
  3. 结论:"写成 "只是一个平均情形下的简化,不是快排的定义性质。归并的递归式是定理,快排的递归式是期望。

6. 易错点 + 老师原话

  • 易错:把"两者都是 "当成"两者分析一样"。考试让你说明代价落在哪一步,就是考这个区别。
  • 易错:说"快排的 Combine 是交换"。快排的交换发生在 partition/Divide 阶段,Combine 阶段确实什么都不做。
  • 老师原话:归并排序侧重 Combine(合并)的复杂度,快速排序侧重 Divide(划分)的复杂度 [纪要:2-conclusion] 二、1。

7. 考试英文答题模板

Both run in but for opposite reasons. Mergesort performs a trivial split (, no comparisons) and pays in Combine to merge two sorted halves — so it is combine-bound, and its recurrence is deterministic. Quicksort performs an expensive split (partition, ) and a trivial combine — so it is divide-bound; its true recurrence is with data-dependent, hence the worst case.


2.2.4 分治 ≠ O(N log N) 【S 级】

1. 一句话本质

分治只规定"怎么切",不规定"切完要花多少钱"。 才是决定复杂度的那一项 —— 若 ,分治就是线性的。

2. 定义与性质

分治结论完全由 与 的比较决定:

只有当 (即"切/拼"恰好与"叶子数增长"同阶)时,才会多出一个 。 这个因子来自"每层代价相等且层数是 ",条件很苛刻。

三个反例(均在本讲):

算法 真实复杂度 为什么不是
树遍历 tree traversal 2, 2 每个节点只访问常数次, 比 小 ⇒ case 1
二分查找 1, 2 只有一个子问题,
最近点对(Combine 用 ) 2, 2 太大 ⇒ case 3,被 支配

反过来,非分治算法也能做到 (如堆排序、Huffman 编码不递归划分),所以 ""与"分治"之间没有等价关系。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

OI 用五个渐近符号把"阶"写成量词命题: 当且仅当存在 ,使一切 有 ; 当且仅当存在 使 ; 把不等式反向;小写 把"存在某个 "改成"对任意正数 ",即严格小于、严格大于。记忆法是:含等号用大写,不含等号用小写,相等是 ,小于是 ,大于是 。OI 另给三条常用性质: 当且仅当 且 ;;。OI 特别提醒:这里的上下界是函数增长趋势的上下界,不是算法最坏情况的界;常用 只因为常常只能证上界, 更精确。

2.2 OI Wiki 校订|基本性质

  1. 要求两侧同时成立:断言"分治就是 "等于断言 ,一侧失败即可推翻。例: 不成立,因为 要求 ,对 不可能。
  2. 只是上界,不排除更紧的界: 既是 也是 ,两者都真,但只有后者是 。例:,取 即验证。
  3. 对数底数可以省略:由换底公式 , 时 是常数,故一切对数同阶。例:,,比值恒为 2。
  4. 反例的算术: 中 ,,正则条件 取 ,故 。例:逐层和 。这说明分治只决定递归结构,不决定量级。

2.3 OI Wiki 校订|与课堂口径的差异

OI 以 记规模、以 为主,本课程用 并常在结论处写 ;两者不冲突,但证明下界时必须写 ,否则只证了一半。第二处差异是 OI 明说 的界是对函数而非对算法,因此"最坏 、平均 "这类混写在本课程里可接受,在 OI 口径下应写成"最坏 "。考试统一用 写最紧的界最安全。另需注意 与 不可互换:写归并为 也成立,但用它回答"归并的复杂度"是错的,因为问题问的是紧界。

3. 图示

分治递归树的两种极端

(A) f(N) = Θ(1)   ——  树遍历               (B) f(N) = cN²  ——  劣质合并的最近点对
        Θ(1)                                       cN²
      /      \                                  /       \
   Θ(1)      Θ(1)                           cN²/4     cN²/4
   /  \      /  \                           /   \     /    \
 Θ(1) Θ(1) Θ(1) Θ(1)                     ...      ...
   ... 共 N 个叶子                         叶子层代价 Θ(N)
 总代价 = Θ(1)·N = Θ(N)                    等比递减,和 = 2cN² = Θ(N²)

同一副"二叉树骨架",只因每层代价分布不同,一个是 ,一个是 。

4. 复杂度表

相对 主导项 结果 例
更小(case 1) 叶子 树遍历
相等(case 2) 每层 归并
更大(case 3) 根 劣质最近点对

5. 典型例题

True or false: "Every divide-and-conquer algorithm runs in ." Justify.

中文逐步:

  1. False。
  2. 反例一:tree traversal。递归式 。,(取 )⇒ case 1 ⇒ 。直观上,遍历只需把 个节点各看一次。
  3. 反例二:二分查找 , ⇒ ⇒ case 2 ⇒ 。
  4. 反例三:若最近点对的 strip 检查写成双重循环 ,则 , ⇒ case 3 ⇒ ,分治完全白做,还不如暴力 简单(2.2.13)。
  5. 结论: 只在" 与 同阶"时出现,是特定结构的产物,不是分治的标签。

6. 易错点 + 老师原话

  • 易错(最高频):看到 divide-and-conquer 就写 。这是判断题的标准陷阱。
  • 易错:以为"分治 + 排序"必然是 。Karatsuba 是 ,Strassen 是 。
  • 老师原话:「不要一看到分治,马上就想到 N log N。」 [稿:2-逐字稿:140](本讲核心口号,判断题必设)

7. 考试英文答题模板

False. Divide-and-conquer fixes only the shape of the recursion; the complexity is decided by versus . For tree traversal , and , so case 1 gives . For binary search gives . Conversely, is also achieved by non-recursive methods such as heapsort. Hence "" and "divide-and-conquer" are not equivalent.


2.2.5 代入法(Substitution Method) 【A 级】

1. 一句话本质

先猜一个答案,再用数学归纳法(mathematical induction)证明它对;猜错时不是"证不出来",而是要在最后一步发现 压不住 。

2. 定义与性质

三步流程:

  1. Guess(猜):凭递归树或经验猜出 ,即 。
  2. Inductive hypothesis(归纳假设):假设对所有 成立,特别地 。
  3. Verify(验证):代入递推,把右边化简到 ,同时求出使不等式成立的常数 ,并单独验证 base case。

两条判据(PPT 用两个对照例子强调):

  • 成功的证明 [PPT:2下:125-147]:

    关键:残余项的系数 迫使 。同时 base case 需 成立(因为 时 ,这一步是 PPT 特别标注的破绽点)。

  • 失败的证明(Wrong guess,PPT 第 9 页 [PPT:2下:151-162]):

    错在最后一步: 不等于 ,而是 ,形式已经溢出。"" 是偷换,不是归纳完成。

核心纪律:不要引入多余项。若目标形式是 ,你就必须严格得到 ;得到 说明结论形式错了(不是常数没调好)。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录"代入法"的独立条目,以下形式定义按 CLRS 口径整理,性质部分对照《复杂度》页的渐近记号定义。

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

代入法要证的是一条量词命题:存在常数 与 ,使一切 有 。这与 OI 的 定义逐字对应,故"证出 "就等于"给出 与 "。方法分四步:先猜 ;再设对一切 有 (用强归纳,因为递归式引用的是 而不是 );再代入化简,把右边压到 并解出 的可行范围;最后单独验证 之前的小规模基例。四步缺任何一步,命题都不完整。

2.2 OI Wiki 校订|基本性质

  1. 归纳假设必须覆盖所有更小规模:递归式引用 ,而 可能远小于 ,故用强归纳。例:证 用到 ,证 又用到 ,链上每一环都要在假设内。
  2. 残余项的符号给出 的下界:把右边化简成 减去一个表达式,要求该表达式非负即得 的不等式。例: 要求 。
  3. 基例不能省:归纳只从 起有效, 之前要直接验。例: 时 ,右边为 ,只能靠加大 解决,取 后成立。
  4. 猜错的形式调 也救不回来:若猜 ,代入得 ,对任何 都不小于等于 ,差别是常数项而非常数因子,属形式错误。例: 不可能是 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 在《复杂度》页用 定义 ,其"存在 "的次序是先取 、再要求存在 使不等式在 上成立;本课程说"取 足够大"时顺序相反,但结论相同,因为两个量都是待构造的。差别只在书写习惯:本册用 ,OI 用 。考试若被要求证明 ,必须显式写出 与 的取值或取值条件,只写"足够大"会失分。

3. 图示

猜O(g(N))|v假设m<N全部成立:T(m)≤c·g(m)|v代入T(N)=aT(N/b)+f(N)|v化简至≤c·g(N)−(残余项)|┌───────┴────────┐残余项≥0残余项<0(如+(1−c)N)⇒猜对了,⇒不可能,除非调c得常数范围(实在调不动⇒猜小了)|v单独验证basecase(N=2,3常有log0问题)

4. 复杂度表

猜测形式 适用信号 典型反例
每层代价相等 归并、非完美树 2.2.6
树遍历
劣质最近点对
二分查找
步骤 常见卡点 处理
展开 忘
漏用 用于化简上界
base case 时 为 0 PPT 明写「choose large enough so that it is true for and 」[PPT:2下:136-142]

5. 典型例题

Use the substitution method to prove is .

中文逐步:

  1. 猜:()。
  2. 假设:对所有 成立;取 。
  3. 代入:

  4. 收口:要求 ⇒ 。取 或更大即可。

  5. base case: 时 ,取 足够大()使 成立; 同理。
  6. 结论:存在常数 使所有 成立 ⇒ 。配下界 (取 小)得 。

6. 易错点 + 老师原话

  • 易错:猜 然后强行""。 不是常数,不能吸收进 。这就是 PPT 第 9 页的 Wrong guess 演示,老师标注「Must prove the exact form」[PPT:2下:161]。
  • 易错:忽略 base case。 时 ,右边为 0,必须靠加大 救。PPT 有专门的批注「Relax! As long as we can choose sufficiently large so that it is true for and .」[PPT:2下:132-142]
  • 易错: 直接写成 当等式。可以当不等式 用,不能当等式。
  • [纪要:2-conclusion] 二、2:「证明过程中不能引入多余项,必须严格推导出与猜测一致的结论。」

7. 考试英文答题模板

Guess . Inductive hypothesis: assume the bound holds for all , in particular for . Substituting, provided . The base cases hold by choosing sufficiently large. Hence ; the matching lower bound gives . Note that guessing fails, since the inductive step would yield , not — the guessed form itself is wrong, not merely the constant.


2.2.6 非完美递归树: 【S 级 · 高频证明】

1. 一句话本质

递归树长得不对称( 个孩子大小不同),但只要"同一层代价之和有上界",层数取最长路径,结论依旧是 ;代价是常系数变大。

2. 定义与性质

递归式 [PPT:2下:205]:

三个观察:

  1. 这不是 形式——两个子问题规模不同( 与 ),所以 但没有统一的 ,主定理直接套不上(这本身就是 2.2.9 的一个例子)。
  2. 每层代价 ≤ :第 层所有节点规模之和 (因为 ),所以每层总代价 。
  3. 树深不均匀:最左路径 长度 ;最右路径 长度 。最长路径决定层数上界。

于是

PPT 原话直接写「Guess: 」并附「Not complete!」[PPT:2下:214-215]——即递归树只是猜,必须用代入法补完整证明。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

OI 在主定理证明里给出的递归树是:第 层一个规模 的结点,代价 ;第 层 个规模 的结点,层代价 ;第 层 个规模 的结点,层代价 ;到第 层触底,叶子数 。于是总代价被拆成两项之和:叶子项 加内部层和 。这个分解就是递归树法的形式定义:树的形状只决定"每层有几个结点、每个多大规模",性质分析全部落在 与叶子项的比值上。

2.2 OI Wiki 校订|基本性质

  1. 每层代价的上界来自规模守恒:若子规模之和不超过父规模,则同一层所有结点规模之和不超过 ,层代价不超过 。例: 时第 0 层 ,第 1 层 ,第 2 层四个结点规模为 ,合计仍为 。
  2. 层数由最长路径决定:深度不齐时上界取最大深度。例: 时全走 的路径长为 ,全走 的路径长为 ,故层数按约 11 计。
  3. 每层等量加层数对数,就多一个 :层代价恒为 、层数为 ,乘积即 。例:上两式相乘得 ,常数 来自 。
  4. 树只给猜测,底部要单独处理:OI 的证明把底部常数层与叶子另算(取固定 使 达到足够大规模),因为接近基例时 的形状不再受等比规律控制。例:猜出 后仍须用代入法给出 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的递归树只对完美树给出公式(、各子问题等规模), 中同层结点规模相同;本考点 的两个子规模不同,公式不能直接代入,只能借用"逐层求和"这一步。第二处差异是层数:OI 取 为整数,本课程允许非整数并取上取整,两者相差常数层,不影响阶。考试先画树、写层代价、取最长路径,再用代入法收口,三段缺一不可。

3. 图示

                          cN                      ← 第 0 层:和 = cN
                    /            \
                cN/3              c2N/3            ← 第 1 层:和 = cN
              /     \            /      \
          cN/9    c2N/9      c2N/9    c4N/9        ← 第 2 层:和 = cN
          /  \     /  \       /  \     /  \
        ...  ... ...  ...   ...  ... ...  ...      ← 每层和恒为 cN

  最左(全 1/3):cN, cN/3, cN/9 ... 终止于 log₃N
  最右(全 2/3):cN, c2N/3, c4N/9 ... 终止于 log_{3/2}N  ← 最长,取它

为什么猜 而不是 ? 因为最右路径到叶子需要 层,而这些层每一层的代价和都是 (不会被压成 )。层数乘每层代价 ⇒ 。若猜 ,代入后会多出 因子,压不住(见 5. 中的失败尝试)。

4. 复杂度表

量 值
每层代价上界
最短路径(全走 )
最长路径(全走 )
叶子数 (每层总规模不变,直到规模 1)
结论 ,常系数 因子

()

5. 典型例题(完整证明 —— 必须会默写)

Solve with .

中文逐步:

Step 1 — 猜。 由上树,,即目标形式

Step 2 — 归纳假设。 对所有 ,。

Step 3 — 代入(关键代数)。

Step 4 — 拆 。 ,。

Step 5 — 化简那个常数。 注意

所以
数值:,减去 ⇒ 。这个数必须严格为正——这是证明的命门。

Step 6 — 收口。

要 ,只需

Step 7 — base case。 取 足够大,使得 (即 已到 的边界)满足 。因为 可以任意大,这一步总能满足。

Step 8 — 结论。 存在常数 使所有 成立 ⇒ 。 下界:沿最长路径 (每层至少 ,层数 的常数倍)⇒ 。

6. 易错点 + 老师原话

  • 易错(致命):把 写成 或 。必须自己推一遍:。
  • 易错:以为"树不对称 ⇒ 复杂度会掉到 "或"⇒ 会涨到 "。都是错的:每层上界 + 最长路径 就锁死了 。
  • 易错:PPT 上「Not complete!」被误读为"这个方法不行"。实际意思是"递归树只给出了猜测,证明未完成[PPT:2下:214]"。
  • 易错:尝试直接套主定理。 不是 形状,无统一 ,主定理不适用(详见 2.2.9)。
  • 老师原话:「大多数递归式就是 N 的多少次方乘 log N 的多少次方」 [稿:2-逐字稿:167]——这也解释了为什么猜 是安全的第一反应。

7. 考试英文答题模板

The recursion tree is unbalanced: every node of size spawns children of size and . Since , the total size on any level is at most , so each level costs at most ; the longest root-to-leaf path follows the branch and has length . Hence the guess . Proof. Assume for all . Then whenever ; base cases hold by enlarging . Therefore , and the -path gives the matching , so .


2.2.7 主定理(Master Theorem)三情形 + 正则条件 【S 级 · 必考】

1. 一句话本质

递归式的复杂度是两股势力的赛跑:"叶子"势力 与"根/当层"势力 。谁大听谁的;打平就再乘一个 。

2. 定义与性质

【Master Theorem】[PPT:2下:226-239]:设 为常数, 为函数, 由 定义,则

情形 条件 结论 谁赢
1 ,某 叶子
2 平局(乘 log)
3 ,某 ,且 ,某 及充分大 根

的严格含义(易被忽略):case 1/3 要求 多项式地(polynomially)小于/大于 ,即存在正的常数 。只差一个 因子不算——这正是第二个版本的 master 定理存在的理由(2.2.8),也是「三种情况不能覆盖所有递归式」的根源。

正则条件(regularity condition)的含义与作用

  • 含义:从规模 的子问题搬到规模 的父问题,代价至少要放大 倍。也就是 必须"随规模单调增长到足够快"。
  • 作用:在 case 3 中我们要把几何级数 压在 里。展开:

    正则条件就是让这个几何级数收敛的"公比 "担保。没有它, 可能在某些规模上不增长, 就不成立。

  • 实践检验法:(当 )。所以只要 ,即 ,条件自动满足(取 )。case 3 的"多项式大"条件已经蕴含正则条件,只有当 带 log 因子或不是纯幂函数时,才需要单独验。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

OI 的表述是:设 、 为常数, 非负,,并额外假设 ( 为非负整数),递推式 对所有 成立。三情形按 OI 的编号是:一、()⇒ ;二、 ⇒ ,此情形还需正则条件,即存在常数 使足够大的 满足 ;三、()⇒ 。第三式把对数的幂直接纳入比较,因此 这类 在 OI 版里有解,而在只比较"多项式"的版本里无解。三式互斥但不覆盖全部 ,这正是 2.2.9 的入口。

2.2 OI Wiki 校订|基本性质

  1. 叶子数就是 :第 层有 个叶子,而 ,故叶子项与 无关。例: 时 ,叶子 。
  2. 正则条件保证层代价成等比:由 逐层迭代得第 层不超过 ,总和不超过 。例: 时 ,取 ,层和 。
  3. 多项式间隙不能放宽成差一个 log:情形一二都要求 的间隙。例: 中 ,不是 ,只能靠情形三取 得 。
  4. 复算 :, 取 ,正则条件取 ,故为 。例: 时逐层 ,与上界一致。

2.3 OI Wiki 校订|与课堂口径的差异

三处差异都要写清。其一,OI 明设 ,结论只对 的幂成立;PPT 不设此限。补法是对 用 的单调性把 夹在 与 之间,两者只差常数倍,故阶不变。其二,编号不同:OI 把根支配列为第二情形、把带 的打平列为第三情形;PPT 与 CLRS 的顺序是 1 叶子、2 打平、3 根。答题写 PPT 编号并注明 与 的关系。其三,PPT 版带正则条件且只对纯幂 陈述,OI 把 并进第三式更宽;若考试要求写全,按 PPT 与 CLRS 补上 。不满足该条件的构造思路:令 在 的幂处取 、在相邻整数处塌到 ,则 在 时为 ,在 时因 而 变得极大,等比结构消失, 不再成立,只能逐层求和。另有前提差异:OI 明写 ,若基例设在 或 ,只改常数不改阶,答题时说明基例规模即可。

3. 图示

T(N)=aT(N/b)+f(N)|比较f(N)与N^{log_ba}|┌───────────────────────┼───────────────────────┐f更小(case1)f相等(case2)f更大(case3)f=O(N^{log_ba−ε})f=Θ(N^{log_ba})f=Ω(N^{log_ba+ε})───────────────────────────────────────────────────叶子层说了算每层都等量根说了算Θ(N^{log_ba})Θ(N^{log_ba}logN)Θ(f(N))+正则条件af(N/b)≤cf(N)递归树代价分布:case1:↑递增到叶子case3:↓递减到叶子N^0──→N^1──→N^2cN²──→cN²/4──→...和=Θ(叶子)和=Θ(根)

4. 复杂度表(必背六个)

递归式 情形 结果
2 2 1 2
1 2 0 2
2 2 1 1
4 2 2 1
4 2 2 2
3 2 1
2 2 1 3
7 2 1 (Strassen)

5. 典型例题(4 例,含一个不适用)


【例 A · case 1】

  1. 。
  2. ,。取 : ✓ ⇒ case 1。
  3. 。
  4. 直观:叶子数 ,每层代价 递减,和 ,被 压过。

【例 B · case 2】 (归并排序)

  1. 。
  2. ⇒ case 2。
  3. 。

【例 C · case 3】

  1. ,()⇒ "多项式大"✓。
  2. 验正则条件:。取 ✓。
  3. 。
  4. 直观:递归树根 ,下一层 ,再下 ,几何级数公比 ,总和 。

【例 D · 不适用】

  1. ,。
  2. 与 比较: 但不是多项式地大 —— 对任何 ,,所以 。⇒ case 3 不满足。
  3. 也不是 case 2(),也不是 case 1。⇒ 主定理三情形全部不适用。
  4. 正确做法:递归树。每层代价 ,共 层,和 ⇒ 。
  5. PPT 上正是用这个例子展示第一版主定理的第一个漏洞 [PPT:2下:244-247](PPT 标 ? 表示怀疑),而第二形式(2.2.8)能解决它 —— 老师原话:「这个版本不能解决,前一个版本可以解决,也存在前一个版本不能解决,版本可以解决,都有,他们都有漏洞」[稿:2-逐字稿:165]。

6. 易错点 + 老师原话

  • 易错(最高频):漏验 case 3 的正则条件。[高频易错清单] 三、「case 3 需查正则条件 af(n/b) ≤ cf(n)」。
  • 易错:把 与 比大小的时候忘记先算 。顺序永远是:算 、算 、算 、再比 。
  • 易错:认为" 更大就一定 case 3"。必须多项式地大(); vs 只差 log 因子,不够。
  • 易错:以为主定理万能。老师原话:「虽然它叫大师算法,它也不能保证 cover 所有的 case,因为什么?因为 fn 和函数相比,它除了大小和相等之外,还有一种可能无法判断他渐进,他们俩是渐进逼近的关系,你找不到一个常数 epsilon 去把它们彻底的给隔开。」 [稿:2-逐字稿:160]
  • 易错: 时忘记 ,。

7. 考试英文答题模板

For , let be the "watershed". Case 1: if for some constant , the leaves dominate and . Case 2: if , every level contributes equally and . Case 3: if for some and the regularity condition holds for some and all large , the root dominates and . The regularity condition guarantees that the level costs form a geometric series with ratio , so the whole sum is bounded by a constant times the root cost. Example: for , , , with , hence . Note fits no case, since is not polynomially larger than ; the recursion-tree solution is .


2.2.8 主定理另一形式( 版) 【A 级】

1. 一句话本质

把"差一个 "那句模糊话变成可执行的算术:两边都是 带 log 的幂函数时,直接比 与 ,打平就把 log 的指数加一。

2. 定义与性质

【Theorem】[PPT:2下:295-301](简版,同样可与下一定理对照):

  1. 若 (严格相等情形)⇒
  2. 若 , 常数 ⇒
  3. 若 …(PPT 记为 时 )

【Theorem】(更常用、更好算的 版)[PPT:2下:313-322]:

对照关系(必须建立直觉):

  • ⇒ 叶子赢 ⇒ 相当于 case 1。
  • ⇒ 平局,log 指数 ⇒ 相当于 case 2。
  • ⇒ 根赢 ⇒ 相当于 case 3(此时 ,正则条件自动成立)。

为什么这个版本"更宽容":它专门处理 带 因子的情形。 在旧版里三情形都不适用(2.2.7 例 D),在此版里取 即可直接算出。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

本考点对应 OI 主定理的第三式:()⇒ 。要写成 版,只需令 :此时 , 与它的比较退化为 与 的比较;再把 与 相比,是因为 与 等价(两边取 保序)。所以本考点三式与 OI 三式一一对应,只是把"多项式间隙"换成可手算的整数比较:比较 与 ,而不比较两个函数。本版还要求 、、;若 ,则 恒成立,叶子项必然支配,等价于情形一。

2.2 OI Wiki 校订|基本性质

  1. 与 的比较等价于 与 的比较:对 两边取 得 ,反向同理。例: 时 ,而 ,两侧结论一致。
  2. 打平时 的指数加一,不是乘二:每层代价同为 ,共 层,相加得 ;层数用 还是 只差常数倍,不改变 。例: 取 得 ; 取 得 。
  3. 时正则条件自动成立:,取 即可,此时 是有限常数。例: 时 ,层和不超过 。
  4. 时 因子被叶子吞掉:结论只留 , 不影响阶。例: 中 、,,故 。
  5. 时 也会多一层 log:打平就加一层,与 是否含对数无关。例: 中 、,故 ,比 的 多一个因子。

2.3 OI Wiki 校订|与课堂口径的差异

OI 只给 的打平式,没有把三式改写成 与 的比较; 与 是本课程 PPT 的算术化改写,结论一致(把 代入即得)。第二处差异更实质:PPT 把三情形写成 、()、 三档,其中 是推导出来的量, 才等价于 ;若照抄 而不说明 是幂函数,该等式只在特定 上成立。考试先写出 ,再判 与 1 的大小。另有记号差异:PPT 用 表示比值,OI 用 表示多项式间隙,两者不是同一量,不可混写; 本身已经蕴含了间隙存在。

3. 图示

T(N)=aT(N/b)+Θ(N^plog^kN)|比较a与b^p(等价于p与log_ba)|┌─────────────────────┼─────────────────────┐a>b^pa=b^pa<b^p(p<log_ba)(p=log_ba)(p>log_ba)───────────────────────────────────────O(N^{log_ba})O(N^plog^{k+1}N)O(N^plog^kN)叶子支配打平,log指数+1根支配例:2T(N/2)+NlogN⇒a=2,b=2,p=1,k=1,b^p=2=a⇒O(Nlog²N)

4. 复杂度表

递归式 比较 结果
2 0 等
2 2 1 等
3 2 0
2 4 0
4 2
2 0

5. 典型例题

PPT 例 2 [PPT:2下:326-332]:Divide with , for each recursion; conquer takes . If conquer takes then ?

中文逐步:

  1. 情形一:。。 ⇒ 。
  2. 情形二:若 conquer 变成 ,。。 ⇒ 。
  3. 观察:把 conquer 从线性提到平方,结果就从 涨到 —— 但没涨到 (即不是"乘法叠加")。这体现了"谁赢听谁的"。
  4. PPT 另一例 [PPT:2下:333]:、 ⇒ ⇒ ,与 2.2.7 例 D 结论一致 —— 新版能算,旧版算不了。

6. 易错点 + 老师原话

  • 易错:把 写成 。比较对象是 与 ,不是 与 。
  • 易错:打平时忘了 log 指数从 升到 (不是变成 ,也不是乘 2)。
  • 易错: 时写 就对,别写成 。
  • 老师原话:「规律是一样的,如果它大的话,这一项说了算,如果它小和这一项说了算,它俩相等,就在和的基础上多加一个 log n。」 [稿:2-逐字稿:167]

7. 考试英文答题模板

For with , compare with (equivalently with ): (i) ⇒ ; (ii) ⇒ ; (iii) ⇒ . This form subsumes the classical theorem but handles logarithmic factors: e.g. has , , hence — a case the three-case version cannot resolve.


2.2.9 主定理的适用形状限制 【S 级】

1. 一句话本质

主定理的三条结论只对"多项式 × log 的幂"这种形状的 成立; 长得稀奇古怪(指数、阶乘、分段、),三条结论就都套不进去。

2. 定义与性质

限制一: 形状。 三情形(2.2.7)与 版(2.2.8)处理的对象本质上是 这一类。原因:只有这类函数, 才能干净地写成 的常数倍加低阶项:

于是每层代价成等比数列(geometric series),求和可以用 闭式。若 不是幂指型,等比结构立刻崩塌,结论无从谈起。

限制二:递归式形状。 必须是 :统一的分母 、统一的子规模。以下都超出范围:

形状 例子 主定理能套吗 正确做法
子问题规模不同 ✗ 无统一 递归树 + 代入(2.2.6)
规模线性递减 ✗ 不存在 直接展开 ⇒
规模减常数 ✗
除以不同因子 ✗ Akra–Bazzi 或树法
指数型 ✗ 不是 树法:根支配 ⇒
带负幂/分式 ✗ 非多项式间隙 树法 ⇒

限制三: 必须是常数。若 随 变化(如 K 路归并里 是变量),不能直接代入——必须先固定 分析,再把 当参数讨论(2.2.10 全部动机)。

限制四(老师强调的"渐进逼近"漏洞):存在 与 既非多项式大也非多项式小的情形,此时连"情形 1 还是 3"都判不出来。经典例子:

  • 与 比:,所以 ,但对任何 ,,所以 。三情形全不适用。
  • 正解(递归树):每层代价 ,求和 ,得 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

OI 把主定理的前提逐条写明:、 为常数, 非负,,并额外假设 。四条前提就是形状限制的形式化: 为常数意味着每个子问题规模必须是同一个 ; 为常数意味着分叉数不随 变; 意味着每层能整除,不必处理下取整; 非负意味着递归树每层代价可比。三式进一步要求 落在 的多项式邻域或对数幂邻域内,否则" 是 的固定倍数"这一前提不成立,等比结构随之消失。

2.2 OI Wiki 校订|基本性质

  1. 无统一 时前提失效: 的两个子问题规模不同,写不成 , 无定义。例: 时子规模为 与 ,比值为 与 ,不存在公共的 。
  2. 随 变时 记号失效: 路归并有 ,若 则 不是常数,结论里的常数因子含 。例: 时取 与 , 都是 1,但每层代价差 4 倍。
  3. 非幂指型的 落在三式之外: 中 ,但 ,既非多项式小也非多项式大。例: 时 ,情形一不成立。
  4. 规模线性递减时 不存在: 无法写成除法形式,只能展开成 。例: 时为 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 用 回避取整,等于承认结论只在 的幂上成立;PPT 允许任意 并明说忽略是否整除,两种写法在阶上等价,但被追问时要能补出夹逼那一步。第二处差异在 :OI 第二式写 ,严格说应为 ,否则与第三式的 情形重叠( 时 既可判为第二式,也可判为第三式的 )。考试统一写 并给出具体值(如 ),结论不受影响。

3. 图示

f(N)的形状决定能否套主定理能套:f(N)=N^plog^kN┌────────────────────────────────────┐│log│╱│││╱←幂×log幂│f(N/b)=b^{-p}f(N)(1+o(1))││╱│⇒每层成等比⇒闭式求和│└──────────────N│└────────────────────────────────────┘不能套:f=N/logN(多项式间隙不存在,卡在两个case之间)f=2^N(非幂指型,f(N/2)=√f(N),比不是常数)f=1/N,loglogN,sinN,分段函数...递归式形状不能套:T(N)=T(N/3)+T(2N/3)+cN←无统一bT(N)=T(N-1)+N←无bT(N)=T(N/2)+T(N/4)+N←b不唯一

4. 复杂度表

递归式 主定理 实际结果 方法
✗ 树法
✗ 树法 + 代入
✗ 展开
✗ 树法(根支配)
✗ Akra–Bazzi
✓ case 2

5. 典型例题

Can the Master Theorem be applied to ? Justify and solve.

中文逐步:

  1. ,watershed 是 。
  2. 检验 case 1:需 。取比值 。对任何 都发散 ⇒ case 1 ✗。
  3. 检验 case 2:需 ,实际 ⇒ ✗。
  4. 检验 case 3:需 ,实际 ⇒ ✗。
  5. 三情形皆不适用。原因: 与 之间只隔一个 因子,不存在多项式的 间隙。
  6. 用递归树:第 层有 个节点,每个代价 ,层和 。对 求和,主导项来自 较小处( 最小),得 ⇒ 。
  7. 结论:主定理不适用 ≠ 无法解,只是要换工具。

6. 易错点 + 老师原话

  • 易错:把""当成 case 1 的充分条件。必须是多项式地小(差 ),差一个 不够。
  • 易错:套主定理前不看递归式形状。 是本讲 PPT 的例题,必须用树法,很多人硬套 得错误答案。
  • 老师原话(本考点的直接证据):「它就不叫 master 定理了,因为因为它和函数这个时间复杂度的形状太特殊了,它就是一个 N log N 的多少次方乘以 log N 的多少次方,一定要是这种形式的,我才能套结论。」 [稿:2-逐字稿:167]
  • 老师原话(漏洞的存在):「仍然存在一些问题,这个版本不能解决,前一个版本可以解决,也存在,前一个版本不能解决,版本可以解决,都有,他们都有漏洞。」 [稿:2-逐字稿:166]
  • 老师原话(实用性判断):「我们大多数的算法和这一步的时间复杂度很少,有什么稀奇古怪的形状,基本上就是 N 的多少次方乘以 log N 的多少次方。它其实是比较好用的一个定理。」 [稿:2-逐字稿:167]

7. 考试英文答题模板

No. The Master Theorem applies only to recurrences of the form with constant and ; the case analysis relies on so that level costs form a geometric series. For : yet for every , so is neither nor — no case applies, because there is no polynomial gap between and . A recursion-tree summation gives . The theorem also fails for unbalanced splits such as (no single ), and for non-constant such as -way mergesort with .


2.2.10 K 路归并(-way Merge Sort) 【S 级 · 全课程正答率最低】

这是全课程正答率最低的题(单选 22%):[稿:3-逐字稿:23] 老师原话「正答率最低的那道题是单选题里头……k 位 merge」。 错因只有一个:没区分" 是常数"和" 是变量"。本节把四种情形全部推到底。

1. 一句话本质

把归并排序的"一分为二"改成"一分为 ":树变矮了( 层),但每层的合并变贵了( 路找最小)。 到底影不影响总复杂度,取决于这两笔账谁抵消谁,以及 是不是参数的函数。

2. 定义与性质

算法描述:把长度 的数组等分成 段,递归地排好每一段,然后 路归并(-way merge)成一个有序数组。

递归式:

合并代价 取决于"怎么从 个队头里找最小":

找最小的方法 单次操作代价 合并 个元素总代价
线性扫描(linear scan)
最小堆(min-heap)

(合并 个元素 = 次"取最小 + 补一个",所以 单次代价。)

参数身份的三种可能(这是全题的分水岭):

  1. 是常数(,与 无关)⇒ 可以当成 常数,直接套主定理。
  2. 是变量(,如 、、)⇒ 不能先取极限再套主定理,必须保留 做符号推导,最后看 是否被消掉。
  3. ⇒ 退化情形, 等于元素个数,递归只有 层,分治结构消失。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《归并排序》 · https://oi-wiki.org/basic/merge-sort/

OI 把 merge 定义为:输入两个已排序序列 ,输出一个有序序列 ;做法是从左往右枚举两个表头,每次把较小者写入 ,一表耗尽后把另一表剩余元素整体追加,代价 。 路归并是它的直接推广:输入 个已排序序列(总长 ),输出一个有序序列,每步在 个队头中取最小。取最小的代价由数据结构决定,OI 的二叉堆页给出插入与删除根都是 (向上、向下调整各沿一条根到叶的路径走),故"取最小加补入"合计 。这构成本考点 的两种取值:线性扫描 ,最小堆 。

2.2 OI Wiki 校订|基本性质

  1. 单层代价等于输出元素数乘单次取最小代价:合并要输出 个元素,每个元素触发一次取最小与一次补入。例: 时共 8 次取最小;扫描版每次比较 3 次,合计 24 次。
  2. 层数等于 :规模每层除以 ,直到为 1。例: 时 共 3 层; 时需 6 层,正好多一倍。
  3. 堆版的两个 互逆:一个在分母(层数 ),一个在分子(每层 )。例: 时层数 2、每层 ,总 。
  4. 为常数时阶不变:由换底公式, 是与 无关的常数,只改常数因子。例: 的层数是 的一半,而每层扫描代价约 4 倍,乘积同阶。

2.3 OI Wiki 校订|与课堂口径的差异

OI 没有 路归并的独立条目,归并页只讨论二路 merge 与自底向上的倍增写法;四情形的分类是 PPT 的口径,不属于 OI 的结论。差异的实质在于:OI 的 merge 代价 中 是固定的,一旦 变成参数,这个 里的常数就随 变化,不能再当常数吞掉——这正是四情形的分水岭。考试先问 是常数还是变量,再问取最小是否用堆。

3. 图示

        二路归并 (K=2)                    K 路归并 (K=4)
              N                                N
           /     \                       /   /   \   \
         N/2     N/2                   N/4  N/4  N/4  N/4
         / \     / \                    / \  / \  / \  / \
       ...                                ...
   层数 = log₂N                      层数 = log₄N
   每层合并 = O(N) (2 路比较 1 次)   每层合并 = O(4N)(线性扫)或 O(N log 4)

   ⇒ K 变大的双重效应:
      层数 ↓(log_K N 比 log₂ N 小 1/log₂K 倍)
      每层 ↑(合并代价乘 K 或乘 log K)
      乘积能不能比 N log N 大?→ 这就是考题

4. 复杂度表(四种情形,必须背下来)

# 情形 合并方式 递归式 结果 备注
① 为常数 任意( 即 ) 跟 完全一样
② 为变量 线性扫描 比 差
③ 为变量 最小堆 被抵消
④a 线性扫描(无堆) = 选择排序
④b 最小堆 = 堆排序

核心结论一句话: 为常数时不变; 为变量时,朴素实现会变差( 越大越差),堆优化能把 完全消掉; 时退化成两种教科书排序。

5. 典型例题(四种情形的完整推导 —— 考试就考这个)


情形 ① 为常数 ⇒ ,与 无关

推导(两条路,都要会)

路 A:直接套主定理。 ,故

合并代价: 是常数,。 ⇒ case 2 ⇒
注意 对任何常数 都成立,所以 结论完全相同。

路 B:为什么" 与 只差常数因子"? 换底公式:

是与 无关的常数,在渐进记号里可被吸收。所以"层数变成了 "并没有真的减少工作量,只是把常数从 变成 。

验算 :层数 (少一半!),但每层合并要花 (4 路线性扫描,每次比较 3 次取最小,常数因子放大)。总代价 ⇒ 仍是 ,常数因子是 而不是 。这就是" 只影响常数不影响阶"的具体体现。

常见错误:看到 就说"层数变少所以更快"。层数少了,但每层贵了,乘起来不变。


情形 ② 为变量 + 线性扫描 ⇒

推导

递归式:

,watershed 是 。。

这里已经不能用主定理了: 不是常数," 与 的比值"是 而不是常数。但我们可以固定一个 值来看,然后把 当参数保留:

用递归树,每层代价 (还是 路合并 个元素):

第 0 层:1 个节点,规模 N,合并代价 K·N
第 1 层:K 个节点,总规模 N,合并代价 K·N  (每节点 K·(N/K),共 K 个 → K·N)
第 2 层:K² 个节点,总规模 N,合并代价 K·N
...
第 j 层:K^j 个节点,总规模 N,合并代价 K·N

每层代价都是 (因为总元素数守恒为 ,每个元素在合并中比 次)。层数:

故

写成 底 2:

关键观察: 随 单调增大( 时; 极值在 附近)。 时:

即 时是 ,远差于 。 时更糟(见情形 ④a)。

结论: 是变量时,朴素 K 路归并比二路归并差,且 越大越差。


情形 ③ 为变量 + 最小堆 ⇒ , 被抵消

推导(三步,必须写出中间过程)

Step 1 — 堆的代价。 路归并时,维护一个大小为 的最小堆(min-heap)存 个队的队头。每取一个最小:

  • extract-min:
  • 从该队补入下一个元素并 insert:
  • 合计:每次输出一个元素

合并 个元素 ⇒ 一次完整合并

Step 2 — 递归树的层数。 规模每次除以 ,所以

Step 3 — 相乘并化简。

关键一步 —— 用换底公式把 消掉:

代入:

一个在分子、一个在分母,直接约掉。 所以

这就是老师那句"分母上有一个 ,它们会抵消掉"的完整算术 [稿:3-逐字稿:23]:

「我们用个堆的话,那么那一步是个 log k?我们因为它的分叉是有 k 个分叉,你整个的分治的时间复杂度,分母上有一个 log k,它们会抵消掉。最后还是 N log n 那个 k 被抵消掉的不出现的。」

直觉解释:

  • 层数少了 倍(好事);
  • 每层贵了 倍(坏事);
  • 两者恰好互为倒数,正好抵平。

这不可能是巧合吗?不是。 只改变了"树的形状"(分叉数),没改变"每个元素被处理的次数"。堆保证每个元素在每一层只被 处理一次,而总层数 与 的乘积恒为 。总工作量 是分治排序的"不变量"。

再验 :

再验 :
任何 都成立:,,乘积 … 注意这里 是 ,代入 —— 等一下,这里要小心:上式应为 ,因为 。对, 是常数 ⇒ 层数是常数 ⇒ ✓。验证一致。

该题考点原题(22% 正答率):[稿:3-逐字稿:23] 「他就给了你一个具体的 k,他跟你说把它分成 k 等于根号 n。分成根号 n 份。……他还特别强调了,我 merging K sorted lists 然后他用的是 m 乘以 log k,每一步是 log k,然后一共有 m 个元素,他就告诉你这是用一个堆或者用一个 log k 的时间复杂度去做的事情,那就没有 k 了,就 N log n。」

读题关键:题目说了"用堆"(),所以答案是 ,不含 。若题目没说用堆,则答 (情形 ②)。看清题干里有没有"heap"三个字,就是这题的全部难点。


情形 ④ :分治结构消失,退化为两种经典排序

Step 1 — 递归式先退化。 时

⇒ 只有 1 层递归, 个子问题全部规模 1。分治不做任何递归工作,全部代价在唯一一次 路合并上。

Step 2 — 分情况算 。

④a 无堆(线性扫描):

总代价
这是什么算法? 把 个长度 1 的已排序列表(就是 个单元素!)合并成一个 —— 即"从 个元素里反复扫描取最小,放到输出末尾",每次扫描 个、共 次 ⇒ 这正是选择排序(selection sort)。

④b 有堆:

总代价
这是什么算法? 把 个单元素建成堆,反复 extract-min ⇒ 这正是堆排序(heapsort)。

Step 3 — 结论的意义(为什么这个考点重要):

  • 路归并是选择排序和堆排序的"共同祖先"——两者都是 的特例。
  • 选择排序 与堆排序 的差别,恰恰就是"找最小"从 优化到 带来的。
  • 所以「无堆 / 有堆 」不是两个孤立结论,是同一个优化在极端情形下的投影。

四情形汇总对照表(考场速查)

的身份 找最小方式 层数 总复杂度 对 而言
常数() 线性扫描() 不变(常数因子 )
常数 堆() 同上 不变
变量( 等) 线性扫描 变差( ⇒ )
变量 最小堆 不变, 抵消
线性扫描 1 退化 = 选择排序
最小堆 1 退化 = 堆排序

记忆钩子:

是常数,随便你怎么 merge,都是 ; 是变量,不优化就乘个 ; 上了堆, 上下抵消,回到 ; ,堆就是堆排,没堆就是选择排。

6. 易错点 + 老师原话

  • 易错①(决定性):题目给了具体 (如 )就以为要"代入展开算 "。正确做法:先看有没有"用堆 / 取最小"这句话;有 ⇒ ;没有 ⇒ 。
  • 易错②:把 直接当成"层数"然后乘 ,得到 ,但又错误地认为它等于 。二者只在堆优化下才相等。
  • 易错③:以为 越大越好("树更矮")。树矮了但每层贵了,朴素实现下 越大越差(情形 ②, 单调增)。
  • 易错④: 时答 (没堆)或 (有堆),方向搞反。判据是:找最小花 还是 。
  • 易错⑤:忘记 。情形 ① 和 ② 的 watershed 都是 ,不是 。
  • 老师原话(本考点的定义性陈述):

    「1 分为 k 的时候,k 到底怎么样会影响我们最后的时间复杂度?我说了,如果我们用什么,我们用堆去,每次从 k 个最小 k 个头里头找一个最小值 merge 出来,如果是用个堆的话,那么那一步是个 log k?我们因为它的分叉是有 k 个分叉,你,你整个的分治的时间复杂度,分母上有一个 log k,它们会抵消掉。最后还是 N log n 那个 k 被抵消掉的不出现的。」 [稿:3-逐字稿:23]

  • 老师原话(正答率数据):

    「正答率最低的那道题是单选题里头……k 位 merge」[稿:3-逐字稿:23]

  • 老师原话(讨论题布置,说明这是他刻意设的坑):

    「我叫 three way merge……然后进一步,就是三,你 merge sort 的时候默认不是二路归并,现在我说我把三等分,三等分归并会改变它的时间复杂度?那三等分的时候,我们是把三当成常数看的,我把三变成参数,我 K 等分。K 等分的时候时间复杂度会变吗?」[稿:2-逐字稿:170]

  • [纪要:2-conclusion] 三、1-2 的官方结论:

    「当 K 为固定常数(如 2、3)时,时间复杂度仍为 O(N log N),因为 与 仅相差一个常数因子。……若直接扫描 K 路找最小值,合并代价为 O(KN),若不优化,总复杂度会退化为 。……应采用最小堆,每次合并操作为 O(log K),总复杂度为 O(N log N),与 K 无关(因为 )。」

7. 考试英文答题模板(按四种情形分点作答)

Let , so and .

(i) Constant . , hence — Master Theorem case 2 — and . Equivalently differs from by the constant factor ; only the constant changes, not the order.

(ii) Variable , linear scan. Finding the minimum among heads costs , so . Every level costs and there are levels, hence . For this is , worse than .

(iii) Variable , min-heap. Each output element costs (extract-min plus insert), so ; with levels, . The factor in the numerator and in the denominator of the number of levels cancel exactly, so disappears.

(iv) . The recurrence collapses to , i.e. a single -way merge of singleton lists. Without a heap and the algorithm is selection sort; with a min-heap and it is heapsort.


2.2.11 堆优化的推导链条 【S 级】

1. 一句话本质

路归并的全部技术含量就在一句话:"从 个队头取最小"这一步,用堆把 降到 ,降下来的刚好等于分治省下来的。

2. 定义与性质

为什么"取最小"是瓶颈? 路归并,每次输出一个元素,都要在 个当前队头中找最小:

  • 线性扫描:比较 次 ⇒ 。
  • 最小堆:堆顶即最小,extract-min + insert 各 ⇒ 。

整个归并要输出 个元素(分治每一层共处理 个),所以单层代价:

为什么" 会出现两次且互为倒数"?

出现在 来源 位置 效果
分子() 堆的单次操作 每层变贵 乘
分母(层数) ,层数 树变矮 除

这两个 是同一个 的两个不同面向: 既是"树的分叉数"(决定层数),又是"堆的大小"(决定单次代价)。分叉越多,树越矮,但堆越大。 二者天然反向,所以精确抵消。

这个"两个 恰好抵消"的结论对任意 成立,不限于 。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《二叉堆》 · https://oi-wiki.org/ds/binary-heap/

OI 的二叉堆是一棵完全二叉树,每个结点存一个权值,并满足堆性质(大根堆:父权不小于子权;小根堆对称)。用数组表示时根为 ,结点 的儿子是 与 ,故父子可 定位,树的形状由下标唯一确定。三种操作都由一条路径完成:插入是放到末尾再向上调整,删除根是与末尾交换再向下调整,两者都只走一条根到叶的路径,故为 。建堆有两条路:从根开始逐个向上调整是 ;从叶开始逐个向下调整是 。

2.2 OI Wiki 校订|基本性质

  1. 建堆 来自高度的分布:高度 的结点约有 个,每个下沉至多 层,总和 。例: 时高度 1 的 2 个结点各 1 次、高度 2 的 1 个结点 2 次,共 4 次,小于 7。
  2. 每次输出 是两次 之和:一次取顶下沉,一次补入上浮。例: 时下沉至多 3 次交换、上浮至多 3 次,合计 6 次比较。
  3. 堆大小恒为 ,与 无关:堆中只存 个队头, 只决定输出次数,故单次代价不随 增长。例: 时每次至多 4 次比较,总计约 次,仍是 阶。
  4. 总代价等于每层代价乘层数:, 被约掉。例: 时 。

2.3 OI Wiki 校订|与课堂口径的差异

需更正一处常见误记:OI 的二叉堆页没有给出 的展开,它只写递推式 并直接引用主定理得 ;上面的高度求和是本册补的证明,引用时不能记在 OI 名下。另一处差异是堆的方向:OI 以大根堆为例(父权不小于子权), 路归并要小根堆,只需把比较反向,路径长度的结论不变。考试写 min-heap 并说明 的来源即可。

3. 图示

K路归并的一层(规模N的元素在这一层被合并一次)队1:29...┐队2:57...│┌──────────┐队3:14...├───▶│最小堆│──▶输出1──▶从队3补入下一个...││sizeK│每次:extract-minO(logK)队K:63...┘└──────────┘insertO(logK)合计O(logK)/元素共N个元素⇒一层O(NlogK)共log_KN层T=N·logK·log_KN=N·logK·(logN/logK)=N·logN←logK上下约掉

4. 复杂度表(推导链条的中间量)

步骤 量 值 说明
① 单次取最小(扫描) — 比较 个
①' 单次取最小(堆) — sift-down + sift-up
② 单层合并代价(扫描) 个元素
②' 单层合并代价(堆) 个元素
③ 层数 — 分叉
④ 总复杂度(扫描) ②×③
④' 总复杂度(堆) ②'×③, 约掉

5. 典型例题

Show that the total running time of heap-optimised -way mergesort is independent of .

中文逐步:

  1. 树形:while n > 1: merge K sorted lists each of size n/K → one list of size n; n = n/K(自底向上)。层数 。
  2. 每层要处理 全部 个元素各一次(每个元素每层被"取最小"一次并放回堆一次)。
  3. 每次操作 ⇒ 每层 。
  4. 总:。
  5. 代入 (底任意,一致性即可):

  6. 所以 ,与 无关。特别地 、、 都得 。

6. 易错点 + 老师原话

  • 易错:只算"每层 "就停,忘了乘层数。
  • 易错:算成 —— 层数是 不是 。写成 会得出 仍存在的错误结论。
  • 易错:认为堆优化能改善常数因子。堆优化只消除" 带来的额外阶",对 反而略慢(堆操作有常数开销)。堆优化的意义是让 可自由选择。
  • 易错:忘记"每个元素每层被处理一次"这个守恒律。它是"每层 "的来源。
  • 老师原话:「Total = 」 [纪要:2-conclusion] 三、2。

7. 考试英文答题模板

In heap-optimised -way mergesort, the recursion tree has branching factor and therefore height ; on every level exactly elements are processed. Replacing the linear scan over the list heads by a min-heap of size makes each output element cost (one extract-min plus one insert), so a level costs and

The two occurrences of — one from the heap size, one from the number of levels — are mutually inverse and cancel, so the bound is independent of .


2.2.12 的退化:选择排序 vs 堆排序 【S 级 · 易错选择】

1. 一句话本质

把 开到最大(),分治就只剩"一次 路合并";这一次合并就是一次排序:不优化是选择排序,优化是堆排序。

2. 定义与性质

时递归式坍缩:

( 个子问题规模全为 1,)

分治结构只剩 1 层(),因此没有任何递归收益。全部代价在 :

算法身份 总复杂度 每轮做什么
无堆 选择排序 selection sort 扫一遍 个候选找最小,放前面,重复 轮
有堆 堆排序 heapsort 一次 建堆,然后 次 extract-min 各

为什么"无堆"精确对应选择排序? 时" 个队的队头"= 个单元素列表 = 个互不相同的候选。每次在其中找最小 ⇒ 线性扫描 个 ⇒ 与选择排序的内层循环逐字相同。外层共 轮 ⇒ 。

为什么"有堆"精确对应堆排序? 个候选用一个最小堆维护 ⇒ 建堆 (bottom-up heapify),每次 extract-min ,共 次 ⇒ 。这正是 heapsort 的标准流程(heapsort 就是"不断取堆顶放到输出末尾")。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《二叉堆》 · https://oi-wiki.org/ds/binary-heap/

时递归式为 ,递归只有一层,分治的"分"与"合"重合为一次 路合并。这次合并的输入是 个长度为 1 的序列,也就是 个互不相同的元素;输出的有序序列就是排序结果。因此算法身份完全由"如何在 个候选中取最小"决定:若每次线性扫描,就是每轮在未排序部分找最小并前移,即选择排序;若用最小堆维护候选,就是建堆后反复取顶,即堆排序。两者的差别只在取最小这一步的数据结构,不在分治骨架。

2.2 OI Wiki 校订|基本性质

  1. 选择排序的比较次数是 :第 轮在剩余 个候选中找最小需 次比较,累加即得。例: 时 。
  2. 堆排序的比较次数约 :每次取顶下沉至多 次比较(先比两子、再比父),共 次。例: 时每次至多 次,取顶共 48 次,加建堆至多 12 次,合计至多 60 次。
  3. 时选择排序反而更快:,因为 的常数开销要 足够大才被摊薄。这不违反 ,只说明渐进结论不能用于小规模。例: 时选择排序约 次,堆排序约 次。
  4. 层数恰为 ,不能再乘 : 个子问题规模全为 1,树只有根与叶子两层。例: 时若误写 3 层,会把答案放大三倍。

2.3 OI Wiki 校订|与课堂口径的差异

与 PPT 口径一致:无堆是选择排序 ,有堆是堆排序 ;OI 未给这一对照,故本考点没有 OI 侧的差异可校。需要说明的是常数:OI 的下沉实现每层先比较两个儿子再与父比较,故一次下沉的比较次数上界是 ;若把每层只计一次交换,常数就小一半,故" 时选择排序更省比较"只在上述计数模型下成立。考试只比较阶,若题目要求具体次数,必须声明计数模型。

3. 图示

选择排序=K=N归并+线性扫描┌──────────────────────────────────────┐│427193扫描6个找最小→1│第1轮:6次比较│1|27493扫描5个找最小→2│第2轮:5次比较│12|7493...│...│总比较=6+5+4+3+2+1=21=N(N−1)/2│=Θ(N²)└──────────────────────────────────────┘堆排序=K=N归并+最小堆┌──────────────────────────────────────┐│建堆O(N)││取顶1→补入下一个→sift-downO(logN)│共N次│取顶2→...││总=N·logN+N│=Θ(NlogN)└──────────────────────────────────────┘

4. 复杂度表

路归并配置 层数 总 等价的经典排序
,线性扫描 1 选择排序
,最小堆 1 堆排序
常数,任意 归并排序
常数 标准二路归并

5. 典型例题

For in -way mergesort, identify the resulting sorting algorithm with and without a min-heap, and give the complexity.

中文逐步:

  1. 确定递归深度:,只有 1 层。分治不再递归。
  2. 无堆: 个元素 × 每次扫描 个候选 = 。这等价于"每轮从未排序部分线性选最小放到前面" = 选择排序。总 。
  3. 有堆: 个元素 × 每次 = (另加建堆 )。这等价于"建最小堆 + 反复取堆顶" = 堆排序。总 。
  4. 对照: 路归并在无优化时比二路归并差得多( vs ),这说明"分得越细越好"是错的——分得越细,合并越贵。
  5. 一句话总结: 的选择是一个平衡: 太小则层多, 太大则每层贵。 常数时层数只差常数因子; 变量时必须堆优化。

6. 易错点 + 老师原话

  • 易错(最高频):把两个结论对调。记法:"没优化就是最笨的排序(选择 ),优化了就是堆排序"。
  • 易错:认为 时"分治更深所以更慢"。恰恰相反, 只有 1 层,分治完全失效。
  • 易错: 时仍写"层数 ,所以 "。这是把堆优化版的每层代价 误当成了 的唯一形式——必须分情况。
  • 易错:回答" 是 "。,别多乘。
  • 老师原话:「若 K 取值过大(如 K=N)且未使用堆优化,算法将退化为选择排序();若使用堆优化,则退化为堆排序()。」 [纪要:2-conclusion] 三、2

7. 考试英文答题模板

With the recurrence becomes , i.e. a single -way merge of singleton lists — the recursion has depth , so divide-and-conquer contributes nothing. Without a heap, choosing the minimum among the heads costs , giving and : the algorithm is exactly selection sort. With a min-heap, building the heap is and each extraction is , giving and : the algorithm is exactly heapsort. Hence -way mergesort unifies both classic sorts; the gap between and is precisely the gap between an and an arg-min.


2.2.13 最近点对(Closest Pair of Points) 【A 级】

1. 一句话本质

分治的骨架人人会写,难的是"跨越中线的那一对"能不能在线性时间里查完;查不完,整个分治就白做。

2. 定义与性质

问题 [PPT:2下:25-30]:

Given points in a plane. Find the closest pair of points. (If two points have the same position, then that pair is the closest with distance 0.)

分治结构:

  1. Divide:按 坐标排序,用一条竖线 把点集分成左右两半(各 )。排序一次 ,之后递归中可用归并式合并 序,总 。
  2. Conquer:递归求左半的最小距离 、右半的最小距离 。
  3. Combine:令 。跨越中线的点对可能比 更近,必须检查。

Combine 的正确做法(关键):

  • 只取中线条带(strip)内的点:。
  • 把条带内的点按 排序。
  • 对每个点 ,只需与后面至多 7 个点比较(一旦 差 就 break)。

为什么是 7? 条带宽度 ,高度 的 区域内,左右各 点且各自内部距离 。一个 的小格至多 1 点, 区域被分成 格 ⇒ 至多 8 点,扣掉自己 ⇒ 至多 7 个邻居。[PPT:2下:103]:「For any , at most 7 points are considered.」

于是 ,递归式

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最近点对》 · https://oi-wiki.org/geometry/nearest-points/

OI 的问题是:给定 个二维平面点,求一组欧氏距离最近的点对;页中说明该分治算法由 Preparata 于 1975 年提出,Preparata 与 Shamos 证明它在决策树模型下最优。形式化写法是:先把点按 为第一关键字、 为第二关键字排序,取 的 作分界,令 、;递归得两侧最小距离 ,取 ;合并时取条带 ,对每个 只查 。关键在 ,使合并为 。

2.2 OI Wiki 校订|基本性质

  1. 由格子计数得到: 与 都在 的矩形内;把它拆成两个 正方形(分属 ),每个再拆成四个 小格,格内两点最远为对角线 ,故每格至多 1 点。例: 时小格边长 1,对角线 ,一格放不下第二点。
  2. 每侧至多 4 点,合计至多 8 点:一个 正方形含 4 个小格,每格至多 1 点,故至多 4 点;两正方形共 8 点,去掉 本身得 7。例: 时 4 个格心各放 1 点已满,第 5 点必与某点距离小于 2。
  3. 只查 更小的一侧即可: 取 ,使每一对只被较大者检查一次。例:点对 满足 时只在 处被查,不会重复计数。
  4. 序用归并得到才能保持 :递归返回已按 排好的子序列,上层归并两表为 ;若每层重排则为 ,递归式变 ,得 。例: 时每层重排约 3 次归并,多出一个对数因子。

2.3 OI Wiki 校订|与课堂口径的差异

OI 的常数 7 与本册正文的"至多 7 个邻居"一致,用的是同一计数: 矩形含 8 点,扣自身得 7。OI 页与本册正文都没有"每侧最多 6 个"的表述;若课堂幻灯片另给 6,那是把 正方形(4 点)与 网格混用后的另一种估计,计数对象不同,最终上界仍按 7 写。第二处差异是记号:OI 用 表示当前最小距离、 表示分界下标,本课程用 与 mid,含义相同。OI 另指出存在期望 的随机做法与非分治的 扫描线做法,PPT 未涉及。

3. 图示

||←中线x=mid|strip(宽2δ)||┌───────────────┐|·|·│··│·|←条带内按y排序|·│··│·||·│··│||└───────────────┘|||左半:内部最小δ_L右半:内部最小δ_Rδ=min(δ_L,δ_R)只查条带内的点;每个点只需向后看7个⇒O(N)┌────δ────┐│·│·│y│────┼──────││每个δ/2×δ/2小格至多1点│·│·││2δ×δ区域共8格⇒8点⇒扣自己=7└────┴──────┘↓

4. 复杂度表

Combine 的实现 递归式 总复杂度 评价
暴力查所有条带点对 (最坏条带含全部 点) 分治失效,与暴力同阶
条带 + 按 排序 + 查 7 邻居 正解
条带 + 按 排序但每次重排 可接受但次优
完全不按 排序 无法 break,退化为 — 错

暴力对照:穷举所有 对 ⇒ [PPT:2下:29-30]。

5. 典型例题

Design a divide-and-conquer algorithm for the closest pair problem and prove its bound. What breaks if the strip is examined by brute force?

中文逐步:

  1. 预处理:按 排序 ;同时保留按 排序的副本。
  2. Divide: 切分,左右各 。
  3. Conquer:递归得 ,。
  4. Combine: - 取 的点,按 升序。 - 对 ,令 ,若 则 break;否则算距离更新 。 - 每个 至多检查 7 个 ⇒ 条带处理 。
  5. 递归式: ⇒ 主定理 case 2(,)⇒ 。
  6. 若暴力查条带:条带最坏含全部 个点(所有点 都靠近中线),两两检查 ⇒ 递归式 , ⇒ case 3 ⇒ 。分治完全被 Combine 拖垮,甚至比直接暴力多付了递归开销。
  7. 这正是 2.2.4 的实例:同一个分治骨架,Combine 从 变成 ,复杂度从 跌到 。

6. 易错点 + 老师原话

  • 易错(核心):只写出"分成左右 + 递归 + 检查中线附近",没说"为什么检查中线是 "。不证明 7 邻居界,整个算法只是猜的。
  • 易错:遗漏按 排序这一步。没有 序就无法 break,条带检查必然 。
  • 易错:说"条带宽度是 "。是 (中线两侧各 )。
  • 易错:忽略"所有点 相同"的退化。此时条带含全部点,但有 序 + 7 邻居界仍保证 。
  • PPT 第 4 页的原始疑问正是这个考点:「It is so simple, and we clearly have an algorithm. But is it really? Can you find the cross distance in linear time?」 [PPT:2下:49-57]
  • [纪要:2-conclusion] 二、1 的复杂度结论链:分治并不总是 ,需根据具体问题分析。

7. 考试英文答题模板

Divide: sort the points by , split at the median -coordinate into and of size . Conquer: recurse to obtain and set . Combine: only a pair straddling the split line can beat , so keep the strip , sort it by , and for each point scan at most 7 successors (break as soon as ). The 7-bound holds because a rectangle can hold at most 8 points pairwise apart. Hence and by case 2. If the strip is scanned by brute force, , case 3 applies and the whole algorithm degrades to — the divide-and-conquer skeleton buys nothing.


2.2.14 课程定位:重点在复杂度分析,不在实现 【B 级】

1. 一句话本质

分治的代码永远是那五行,区分度全在你能否把它翻译成递归式、再用三种工具解出来。所以这一讲真正要练的是"建模 + 解方程",不是敲代码。

2. 定义与性质

为什么复杂度分析是本讲的全部:

  • 分治的伪代码高度模板化:if (base) return; divide; recurse; combine;。会写归并排序的人,写最近点对、最大子列和的分治版只需换 Combine。
  • 但复杂度可以差出天壤之别:同一副骨架, 得 , 得 , 得 (2.2.4、2.2.13)。
  • 因此"设计"的难度实际在"设计 ":能不能把 Combine 压到与 同阶或更低。这正是最近点对 7 邻居界的意义。

三种方法的定位:

方法 作用 何时用 局限
代入法 Substitution 证明结论 已猜出答案 猜不出就用不了;猜错会证出矛盾
递归树法 Recursion tree 猜结论 + 直观 树不完美、无标准形 只是猜测,需代入补证
主定理 Master 直接算 常数且 形状不符即失效(2.2.9)

实际解题顺序:

读算法 → 写 T(N)=aT(N/b)+f(N)
   ↓
形状是标准形(a,b 常数,f 是幂×log)?
   ├ 是 → 主定理(先算 log_b a,再比 f)        【最快】
   └ 否 → 递归树猜(看每层代价分布、层数)      【2.2.6】
             ↓
          代入法严格证明 + 求常数                 【必须收口】

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按课程讲义口径整理。

来源:OI Wiki《分治》 · https://oi-wiki.org/basic/divide-and-conquer/

本考点要给的是一条判据,而不是学习建议:给定一个问题与它的候选分治方案,能否断言某个复杂度结论,取决于三个可检验的条件同时成立。第一,子问题与原文同型且规模按常数因子缩小,即存在常数 使子规模为 ,否则递归式不成立。第二,合并算子存在且其代价 可算,因为 是递归式里唯一的自由项。第三,递归式的形状落在工具的适用域内: 为常数、 为 时可用主定理,否则必须改用递归树加代入。三条皆满足才能直接引用 结论,缺一条就要显式给出替代论证。

2.2 OI Wiki 校订|基本性质

  1. 判据一:规模按常数因子缩小: 为常数才能保证层数是对数。例: 时层数为 ;若每次只减 1(),层数为 ,得 。
  2. 判据二: 可算且落在某一侧:结论由 与 的比较决定,算不出 就没有结论。例:同一骨架下 得 , 得 , 得 。
  3. 判据三: 为常数: 随 变时结论里会残留 。例: 路归并取 且用线性扫描,得 ;改用堆则回到 。
  4. 三条缺一即须换工具: 非幂指型时三式都不覆盖。例: 三情形全不适用,树法给出 。

2.3 OI Wiki 校订|与课堂口径的差异

OI 无对应条目,故没有可校的口径差异;唯一与 PPT 重合的是那句定位——分治的难点在复杂度分析而非实现,OI 的《分治》页用"相信函数能完成任务"表达同一意思,但不谈代价分析。上面的三条判据是本册由 2.2.1 至 2.2.13 的证据归纳而得,引用时不要记在 OI 名下。考试按 PPT 的三种工具分工作答:先判形状,标准形用主定理,否则树法加代入。

3. 图示

分治学习的三层能力第1层:会写伪代码──一周可掌握,考试几乎不单独考─────────────────────────────────────────────第2层:会写递归式──认出a,b,f(N)(2.2.1)─────────────────────────────────────────────第3层:会解递归式──代入/树/主定理(2.2.5–2.2.9)+会判断工具边界──主定理失效时换树法(2.2.9)─────────────────────────────────────────────第4层:会用结论做设计选择──K路归并该不该上堆(2.2.10–2.2.12)★考试区分度集中在第2–4层

4. 复杂度表

能力 对应考点 考试权重(据题型预测)
三步定义 2.2.1 简答
适用条件判断 2.2.2 判断/简答
归并 vs 快排 2.2.3 简答
反例意识(≠ ) 2.2.4 判断
代入法证明 2.2.5、2.2.6 证明(高频)
主定理计算 2.2.7、2.2.8 计算(必考)
主定理边界 2.2.9 判断
K 路归并 2.2.10–2.2.12 计算/简答(必考、最低正答率)
最近点对 2.2.13 简答

5. 典型例题

"Designing a divide-and-conquer algorithm is easy; analysing it is the hard part." Discuss with examples from this lecture.

中文逐步:

  1. 设计侧:归并排序、最近点对、最大子列和的分治版,代码骨架都相同:切分 + 两次递归 + 一步合并。真正不同的只有 Combine 的三行。
  2. 分析侧:三者结论分别是 、、——看起来一样,但若把最近点对的 Combine 写成暴力,就变成 。结论对 Combine 的实现极其敏感。
  3. 设计选择侧:K 路归并的 该怎么选?这不是代码问题,是复杂度问题——必须比较 与 才能回答"要不要上堆"。
  4. 工具边界侧:主定理看起来万能,但 与 都套不上,必须回到递归树。
  5. 结论:分治的"难"全部集中在建立递归式与求解递归式上,这也与老师把重点放在复杂度分析一致。

6. 易错点 + 老师原话

  • 易错:把复习重心放在"背代码"。代码在考卷上几乎不给分;给分的是推导过程。
  • 易错:只写结论不给过程。[高频易错清单] 五:「复杂度分析必须给推导过程,不能只写结论。」
  • 易错:以为三种方法可以任选。正确顺序是"先判形状":形状标准用主定理,否则树法+代入。用错工具等于没做。
  • 老师原话(本考点直接证据):「那我们这一章的重点,divide and conquer 的重点就是在于怎么分析时间复杂度。」 [稿:2-逐字稿:170]
  • 老师原话(工具的普遍性):「我们大多数的算法和这一步的时间复杂度很少,有什么稀奇古怪的形状,基本上就是 N 的多少次方乘以 log N 的多少次方。它其实是比较好用的一个定理。」 [稿:2-逐字稿:167]

7. 考试英文答题模板

The code of a divide-and-conquer algorithm is almost always the same five lines — test the base case, split, recurse twice, combine — so implementation is not where the difficulty lies. What distinguishes solutions is the analysis: the same skeleton yields (tree traversal), (mergesort, closest pair with a linear combine) or (closest pair with a brute-force combine), and only a careful study of against reveals which. Moreover, design decisions such as "should -way mergesort use a heap?" can only be answered by comparing with . Hence the emphasis of this chapter is on modelling a recurrence and solving it — by substitution, recursion tree, or the Master Theorem — rather than on writing the code.


附录 A:本讲一页速查(考前 5 分钟)

考点 一句话
2.2.1 Divide / Conquer / Combine ⇒
2.2.2 独立(不重叠) + 可合并;否则用 DP
2.2.3 归并 = Combine 贵;快排 = Divide 贵
2.2.4 分治 ≠ ;树遍历
2.2.5 猜 + 归纳;不准引入多余项
2.2.6 ;
2.2.7 三情形 + case 3 正则条件
2.2.8 vs ;打平 ⇒ log 指数 +1
2.2.9 只吃 形状; 无解
2.2.10 常数 ⇒ ; 变量扫描 ⇒ ;堆 ⇒
2.2.11 ( 上下约掉)
2.2.12 :无堆 = 选择排序 ;有堆 = 堆排序
2.2.13 最近点对:Combine ⇒ ; ⇒
2.2.14 本讲重点 = 复杂度分析,不是实现

附录 B:K 路归并四情形决策流程(考试直接用)

题面出现"K-waymergesort",先问三个问题:Q1:K是常数吗(2,3,10…)?└是→答Θ(NlogN)。理由:log_KN=log₂N/log₂K,只差常数因子。Q2:题面/代码里"找最小"用的是堆吗(O(logK))?├是→答O(NlogN)。│推导:f(N)=O(NlogK),层数log_KN│⇒NlogK·logN/logK=NlogN,K消失。└否→答O(KNlog_KN)。推导:f(N)=O(KN),层数log_KN⇒KNlog_KN。(K=√N时为N^1.5,比NlogN差)Q3:K=N吗?(退化检查)├无堆→O(N²),就是选择排序└有堆→O(NlogN),就是堆排序

一句话总纲: 只改树的形状,不改"每个元素每层被处理一次"的守恒律;上升一个 就必须下降一个 ,否则 就漏到答案里了。

3.1.1 – 3.1.10

详解 05 · 动态规划

配套 A0_速查册.md 的「3 上」部分。速查册给结论,本册给推导、图示与手算过程。 证据约定:[PPT:3上:n] = 01_原始素材/PPT_文本/ADS/第三讲(上)Dynamic Programming.txt 行号;[稿:3-逐字稿:n] = 课堂录音文稿行号;[纪要:3-conclusion] / [纪要:4-conclusion] = 对应 conclm 纪要。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

"能算但算得慢"的递归──→重叠子问题爆炸(指数)│└→核心手法:Useatableinsteadofrecursion(3.1.1)├填表要满足两个准入证:最优子结构+重叠子问题(3.1.2)├填表顺序=DP的阶段顺序,写错就全错│├矩阵链乘:按链长len递增填(3.1.3)│├OBST:按区间长度递增+记根r_ij(3.1.4)│└Floyd:k必须最外层(否则反例打脸)(3.1.5/3.1.6/3.1.7)├状态设计决定维度:背包是二维状态f[i][w](3.1.8)└复杂度对"数值"敏感⇒伪多项式,不是多项式(3.1.9)对照:加权活动安排——贪心失效,DP才是正解(3.1.10)

本讲有两条线:一条是"怎么写对 DP"(3.1.1–3.1.4、3.1.8、3.1.10),另一条是"怎么写错 DP"(3.1.5–3.1.7,Floyd 循环顺序)。后者是本讲唯一一个正答率约 20% 的考点 [稿:3-逐字稿:119],也是本科考试最爱出的判断题 + 反例构造题。老师对它的定性是:

「正答率非常低……动态规划的计算顺序,kij 这个三重顺序是不能打乱的,打乱以后就会全错。」 [稿:3-逐字稿:119]

这句话里的 kij 就是答案的骨架: 在最外、 在中、 在最内。


3.1.1 DP 本质:"用表代替递归" 【S 级】

1. 一句话本质

递归之所以慢,不是因为递归,而是因为同一个子问题被算了无数遍;把每个子问题的答案记在一张表里,算过的直接查——指数就变多项式了。

2. 定义与性质

PPT 对 DP 的定义只用了一句话,但这句话是整章的总纲:

Dynamic Programming
  —— Use a table instead of recursion.

严格地说,DP 是把问题分解为重叠子问题、以自底向上(bottom-up)或自顶向下(top-down + memoization)的方式,在表中记录每个子问题的最优解,从而保证每个子问题只求解一次的算法范式。

两个关键等式:

自底向上(bottom-up / tabulation):先算最小的子问题,按依赖顺序逐层填表,最终格子即答案。本讲的矩阵链乘、OBST、Floyd、背包都是这个形态。

自顶向下(top-down / memoization):写递归,但进函数先查表,查到就返回,算完写入表。它与自底向上渐进复杂度相同,区别只是常数与常数因子(递归栈开销)。

斐波那契是最小可复现的例子:。

  • 裸递归:,解为 ,。
  • 填表: 个格子,每格 ,。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《动态规划基础》 · https://oi-wiki.org/dp/basic/

OI Wiki 把动态规划定义为:把原问题拆成若干相互重叠的子问题,按子问题之间的依赖顺序逐个求解并保存结果,使每个子问题只算一次。形式化后有四个必备要素:状态(state)是子问题索引到值域的映射 ;转移(transition)是 上的递推式,用规模更小的状态表示当前状态;初值(base case)给出递推的起点;答案(answer)指定目标状态。四者缺一,递推要么无定义,要么算出的不是原问题的解。状态的依赖关系构成一张有向无环图(DAG):若存在环,递推会自我引用,除非改用最短路式的迭代,否则无法求值。自顶向下(top-down)是 DFS 加记忆化,只计算可达状态;自底向上(bottom-up)是按拓扑序填满整张表,两者渐进复杂度相同。

2.2 OI Wiki 校订|基本性质

  1. 无后效性(Markov property):状态确定后,后续决策只依赖该状态,不依赖到达它的路径。为什么必须成立:否则状态没有编码全部历史,转移式不封闭。例:Floyd 的状态必须带“已允许的中转点集合”这一维,只写 就不满足无后效性。
  2. 最优子结构:最优解由子问题的最优解拼接而成。为什么成立:若某段用了次优解,替换后整体更优,与“已最优”矛盾。
  3. 子问题图是 DAG:状态之间的依赖不能成环。例:区间 DP 按区间长度递增填表,长度小的不会依赖长度大的。
  4. 两种实现的复杂度同阶:均为 。差别只在常数与栈开销:记忆化只算可达状态,填表算全部状态。
  5. 可压缩空间的条件:当转移只依赖前一层(或前若干个固定层)时,可以滚动数组把空间降一维。例:0-1 背包的 只依赖 ,故可压成 。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 用“阶段 / 状态 / 决策”三词描述,强调拓扑序;PPT 用“用表代替递归”一句话概括,强调填表方向。两套说法描述同一件事:拓扑序决定填表方向,填表方向保证依赖先于被依赖。考试按 PPT 的“自底向上填表 / 自顶向下记忆化”作答,再用 OI 的“状态图必须是 DAG”补一句正确性理由即可。另有一处记号差异:OI 常把状态写成 ,PPT 写 ,含义相同。

3. 图示

                 F(5)
              /        \
          F(4)          F(3)          ← F(3) 已经算过一遍
        /     \        /     \
     F(3)     F(2)   F(2)   F(1)      ← F(3)、F(2) 重复出现
     /  \     /  \   /  \
   F(2) F(1) F(1) F(0) ...
    ↑ 裸递归:同一结点在树上出现多次 → 指数

  表:  i  | 0 | 1 | 2 | 3 | 4 | 5 |
        F  | 0 | 1 | 1 | 2 | 3 | 5 |   ← 每个格子只填一次 → 线性

DP 的意思就是:把左边那棵树折叠成右边一行格子。树上有多少重复结点,表里就有多少被省掉的计算。

4. 复杂度表

版本 时间 空间 备注
裸递归 naive recursion 栈 已不可算
自顶向下 memoization 递归 + 查表
自底向上 tabulation 或 滚动 只留前两项即

一般规律:时间 = 状态数 × 单状态转移代价,空间 = 状态数(可滚动优化)。

5. 典型例题

Explain, using Fibonacci numbers, why dynamic programming eliminates the exponential blow-up of naive recursion.

中文逐步:

  1. 写出递推:,。这是合法的递归定义,但不是合法算法。
  2. 画出递归树:第 层最多 个结点,深度 ⇒ 结点数指数级。根结点的两个子树几乎一模一样 ⇒ 重叠子问题(overlapping subproblems)。
  3. 数一数: 在 的树中出现 2 次, 出现 3 次,重复率随 指数增长。
  4. 改进:开数组 F[0..N],从 填到 ,每格一次加法。查表命中即 ,总时间 。
  5. 结论:递归式没变,变的是求解顺序。DP 不是新公式,是新的求值顺序。

6. 易错点 + 老师原话

  • 易错:以为 DP 就是"写递归 + 加个数组"。必须同时说明为什么子问题会重叠(否则表没用)和表按什么顺序填(否则填错)。
  • 易错:把 DP 和分治混为一谈。分治的子问题不重叠,DP 的子问题重叠;两者共同点只是都拆问题。
  • 老师原话:Use a table instead of recursion 是原文定义的唯一一句,简答题开头直接抄这句最稳。

7. 考试英文答题模板

Dynamic programming replaces recursion by a table: instead of recomputing every subproblem along the recursion tree, each subproblem is solved once and its answer is stored, so that later requests are answered by a table lookup. For Fibonacci, naive recursion solves in because the same subproblems recur exponentially often; tabulating bottom-up solves each of the subproblems once in , giving time.


3.1.2 最优子结构 + 重叠子问题 【S 级】

1. 一句话本质

能不能用 DP,只问两件事:大问题的最优解里包着子问题的最优解吗(最优子结构),子问题会被反复问吗(重叠子问题)。第一条决定"对不对",第二条决定"值不值"。

2. 定义与性质

(1)最优子结构(optimal substructure)

An optimal solution to the problem contains within it optimal solutions to subproblems.

形式化:设 为原问题最优值, 是切分出的子问题,则必须存在一个分解使

且 可计算。DP 的转移方程本质上就是这个 的具体写法。

证明套路(cut-and-paste 反证法):若原最优解里某个子问题部分不是该子问题的最优解,把它换成最优解,整体会更优或更差——对最小化问题就是更小,与"原解最优"矛盾。这个"剪切-粘贴"论证是 DP 正确性证明的标准答案。

(2)重叠子问题(overlapping subproblems)

A recursive algorithm revisits the same problem repeatedly.

子问题空间是多项式规模的(如 个区间 ),而递归树展开是指数级的 ⇒ 大量重复。填表把重复折叠掉。

(3)与分治的分界(考试必考对比)

维度 Divide and Conquer Dynamic Programming
子问题是否重叠 不重叠(disjoint) 重叠(overlapping)
求解方向 自顶向下递归 自底向上填表(或 memoized 递归)
是否记录中间结果 不需要 必须记录
典型 归并排序、快排、Karatsuba 矩阵链乘、Floyd、背包、OBST
共同点 都要求最优子结构;都是"拆问题" 同左

(4)无后效性(no after-effect):状态一旦确定,后续决策只依赖当前状态,不依赖"怎么走到这里"。这是第三条隐性准入证——它保证了状态可以只用一个下标描述。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《动态规划基础》 · https://oi-wiki.org/dp/basic/

最优子结构(optimal substructure)的形式定义是:问题 的任一最优解,限制到其子问题 上,必为 的最优解。等价地,存在可计算函数 使 。重叠子问题(overlapping subproblems)指递归展开时同一子问题被反复求解:子问题空间是多项式规模,而递归树的结点数是指数级,两者之比就是被重复计算的倍数。DP 用一张表把“重复”折叠成“查表”,因此总代价从“递归树结点数”降为“不同子问题数 单次转移代价”。

2.2 OI Wiki 校订|基本性质

  1. 剪切—粘贴(cut-and-paste)证明:若最优解中的子问题部分不是最优,换成该子问题的最优解后整体更优,矛盾。为什么这是标准证法:它只用到“最优”的定义,不依赖问题结构。例:矩阵链乘中若某段括号不是该段最优,替换后乘法次数严格下降。
  2. 重叠度决定加速比:加速比 。例: 的裸递归调用 15 次,记忆化只算 共 6 个状态。
  3. 与分治的分界只在“是否重叠”:归并排序的子问题是 个不相交区间,无重复可省;矩阵链乘只有 个子区间,却被递归树指数次调用。
  4. 无后效性是独立条件:它既不是最优子结构的推论,也不是重叠性的推论。例:最长简单路径有最优子结构,但状态需记录“已访问集合”,不满足无后效性,故不能写成多项式 DP。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 把这两条放在“动态规划基础”的“适用条件”里,措辞偏“能不能设计出状态”;PPT 把“最优子结构 + 重叠子问题 + 无后效性”并列为三条准入证。考试按 PPT 的三条作答;若题目要求证明,用 OI 的剪切—粘贴模板写反证即可。注意 OI 有时把无后效性称作“状态转移的无环性”,二者是同一要求的不同表述。

3. 图示

        分治(子问题独立)              DP(子问题重叠)
              P                            P
            /   \                        /   \
          P1     P2                    P1     P2
         / \     / \                   / \     / \
       ... ... ... ...               P3  P3  P3  P3   ← 同一子问题反复出现
       无重复,直接递归                填表折叠 → 每格一次

4. 复杂度表

判定 不满足最优子结构 满足最优子结构
子问题不重叠 只能穷举/搜索 分治(Divide & Conquer)
子问题重叠 只能穷举/近似 DP

(矩阵中左上格几乎没有算法可用,右上是分治,右下是 DP。)

5. 典型例题

For the matrix-chain multiplication problem, state the optimal substructure and give the cut-and-paste proof.

中文逐步:

  1. 设 的最优完全括号化为 ,即最后一次乘法是 。
  2. 断言: 的前缀部分必须是该子链的最优括号化。否则换成更省的方案,总代价 左 右 会更小,矛盾。
  3. 右半部分同理。
  4. 于是 成立(3.1.3)。
  5. 重叠性:不同切分点 会引出同一个子区间 ,例如算 和 都要 。子问题总数仅 个,朴素枚举却是指数级 ⇒ 典型 DP。

6. 易错点 + 老师原话

  • 易错:只说"有最优子结构"就断定能用 DP。重叠子问题才是"该用 DP"的理由;不重叠就该用分治。
  • 易错:最优子结构是对问题说的,不是对算法说的。同一个问题,换一个状态定义可能就失去最优子结构(如把背包状态定义为"剩余容量"以外的量)。
  • 易错:证明最优子结构时必须写"cut-and-paste",只写"显然"不给分。

7. 考试英文答题模板

Dynamic programming applies when the problem has optimal substructure — an optimal solution contains optimal solutions to its subproblems, provable by a cut-and-paste argument — and overlapping subproblems — the recursion tree revisits the same subproblems exponentially often while the subproblem space is only polynomial. Divide-and-conquer shares the first property but requires disjoint subproblems, so it does not need a table; DP's table collapses the overlapping computation from exponential to polynomial time.


3.1.3 矩阵链乘:枚举切分点、填表顺序 【A 级】

1. 一句话本质

矩阵乘法满足结合律,所以括号加在哪里不影响结果、只影响乘法次数;枚举最后一次乘法切在哪里,代价 = 左边 + 右边 + 合并,取最小。

2. 定义与性质

给定 个矩阵 , 的维数为 (即维数数组 )。

定义 = 计算子链 所需的最少标量乘法次数。则

三项的含义:左子链代价 + 右子链代价 + 最后把两个 和 的结果矩阵相乘的代价 。

记录切分点 取到最小的那个 ,用于还原括号方案(与 3.1.7 的"记录决策"同源)。

填表顺序:转移依赖 与 ,两者的长度都严格小于 。所以必须按链长 递增填。PPT 代码用 k = j - i 作外层循环,注释明写 k = j - i [PPT:3上:104]。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《区间 DP》 · https://oi-wiki.org/dp/interval/

给定矩阵链 ,其中 的维数为 ,维数数组为 。一个括号方案(parenthesization)是对乘积加全括号的方式,不同方案对应不同的标量乘法次数。定义 为子链 的最少标量乘法次数, 为取到该最小值的切分点。递推为

状态数是 ,每个状态枚举 个切点,总计 。 与 同步填写,用于还原括号。

2.2 OI Wiki 校订|基本性质

  1. 最优子结构:最优括号必在某个 处把链切成两段,且两段各自取最优。为什么成立:若某段不是最优,替换后总次数下降,与整体最优矛盾。例: 的最优方案是 ,两个子链都取到各自最优。
  2. 按区间长度递增填表: 依赖的 与 长度都严格小于 ,所以长度是天然的拓扑序。
  3. 括号方案总数是 Catalan 数:, 时为 5, 时为 4862;DP 只算 个状态就把指数级枚举压成多项式。
  4. 答案位置:,不是 ;下标从 1 起时维数数组要取到 。
  5. 可复算实例: 时 。验算: 需 , 需 ,再乘 需 ,合计 。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的区间 DP 模板常把区间写成 、断点枚举 ,并从 0 开始存维数;PPT 从 1 开始、断点 、答案 。两者只差一个下标平移,但答题时必须与题给下标一致,否则 会越界。OI 另把“按长度枚举”写成三层循环的第一层 for (len = 2; len <= n; len++),与 PPT 的 k = j - i 外层等价。另外, 表与 表必须同轮填写,否则还原括号时切分点会指向尚未算出的区间,这一条常被忽略。

3. 图示

以 (4 个矩阵 )为例, 表按对角线填入:

        j=1      j=2        j=3          j=4
 i=1   0        10000     1200         2200     ← 对角线方向:len=1,2,3,4
 i=2            0         1000         3000
 i=3                      0            5000
 i=4                                   0

 依赖箭头:m[1][4] ← m[1][k] 与 m[k+1][4],k=1,2,3
          m[1][4] 用到 m[1][3]、m[2][4](len=3),len=3 又用到 len=2 ...
          故 len 必须从 1 涨到 n

4. 复杂度表

量 复杂度 说明
状态数 区间
单状态枚举 切分点
总时间 PPT: T(N) = O(N^3) [PPT:3上:113]
空间 表 + 表

5. 典型例题

Find the minimum number of scalar multiplications needed to compute where the dimension array is .

中文逐步():

len = 1():。

len = 2:

  • ,。
  • ,。
  • ,。

len = 3:

  • :
  • :
  • :
  • ⇒ ,。
  • :
  • :
  • :
  • ⇒ ,。

len = 4:

  • :
  • :
  • :

⇒ ,。

表(上三角,):

— 1 1 3
— 2 3
— 3
—

还原括号: ⇒ ; ⇒ 。最终 。

验算: 是 ,代价 ; 是 ,代价 ;再乘 ()代价 。合计 ✓。

6. 易错点 + 老师原话

  • 易错:合并项写成 。正确的是 ——左子链的行数、切点的列数、右子链的列数。维数下标错一位,整表全错。
  • 易错:外层循环写成 for (i...) for (j...)。这会用到还没算的格子。必须 for (len...) for (i...) { j = i+len-1; ... }(PPT 用 k = j - i 表达同一个意思,注意这个 k 不是切分点,只是长度差,别和平分点符号混淆)。
  • 易错:忘了 的初始化。

7. 考试英文答题模板

Let be the minimum number of scalar multiplications for the chain , with . Splitting at the last multiplication gives

Since both sub-chains are strictly shorter, the table must be filled in order of increasing chain length. There are states and split points per state, so the algorithm runs in time and space; reconstructs the optimal parenthesization.


3.1.4 最优二叉搜索树 OBST: 递推 【A 级】

1. 一句话本质

给定 个有序关键字和各自的查找概率,问把哪个字放根能让平均查找长度(期望深度)最小——枚举根,代价 = 左子树 + 右子树 + 整棵子树的概率和(因为根加深了一层,所有人都多走一步)。

2. 定义与性质

给定有序词 与查找概率 ,构造二叉搜索树使

最小, 为 的深度(根深度 0)[PPT:3上:126-130]。

设 = 由 构成的最优 BST 的期望查找代价(按深度计,单结点时为 )。若根取 (),则

为什么加整段概率和? 根 使左右两棵子树的每个结点深度 ,期望代价增加的就是子树中所有概率之和;空子树 。

记 为最优根,用于重构树形态。这就是Knuth 优化能落地的地方(,可将 降到 ,本课程不要求)。

与矩阵链乘形式几乎同构:都是区间 DP,都把"切点"枚举一遍。区别:矩阵链乘的切点把链切成分离的两段( 与 ),OBST 的根把自身也吃掉(左 、根 、右 )。下标边界千万别抄串。

填表顺序:同样按区间长度递增(长度 1 到 )。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《四边形不等式优化》 · https://oi-wiki.org/dp/opt/quadrangle/

给定有序键 及查找概率 ,要求构造一棵二叉搜索树,使期望查找代价 最小, 为 的深度(根为 0)。设 为由 构成的最优 BST 的期望代价, 为对应的最优根下标,则

与矩阵链乘的区别在于:根 把自身也吃掉,左右子区间是 与 ,不是 与 。

2.2 OI Wiki 校订|基本性质

  1. 为什么要加整段概率和:根使左右子树的每个结点深度加 1,期望代价的增量正是子树中所有概率之和。例:、,取 为根得 ,取 为根得 ,故 。
  2. 空区间约定 :这是递推的基例;漏写会让 的分支访问非法下标。
  3. 决策单调性(Knuth 优化):。为什么成立:四边形不等式 ()保证最优根随区间扩张单调右移。用它把每个状态的枚举范围缩到常数摊还,总时间 。
  4. 与矩阵链乘同构:都是区间 DP、都枚举切点、都按长度填表;区别只在区间切法与“根是否被计入子问题”。
  5. 两个表必须同步:只填 不填 无法重构树形;只填 不填 无法比较代价。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的 OBST 通常同时给成功概率与失败概率(虚拟键 ),递推里多一项 ;本课程只考成功概率,按 PPT 的 与 两张表作答。OI 用 记最优根,PPT 用 ,同一含义。考试若问“为什么 ”,须写出决策单调性的不等式链,不能只写“四边形不等式”五个字。最后, 的值域是 ,取 或 时对应一侧为空区间,需用基例兜住。

3. 图示

7 个词及其概率:break .22、case .18、char .20、do .05、return .25、switch .02、void .08。

                     char(.20)
                    /         \
             break(.22)      return(.25)
                \            /        \
              case(.18)   do(.05)    void(.08)
                                     /
                               switch(.02)

  T[1][7] 的根 r = char(第 3 个词)
  左:[1,2] 的最优根是 break;右:[4,7] 的最优根是 return

4. 复杂度表

量 朴素区间 DP Knuth 优化(了解)
状态数
单状态枚举根 摊销
时间
空间 ( + ) 同

5. 典型例题

Given probabilities break .22, case .18, char .20, do .05, return .25, switch .02, void .08 (in sorted order), build the OBST DP table and give the optimal tree.

中文逐步( 表示 ,下标 1..7):

长度 1:。 ,。

长度 2(例如 ,,和 ):

  • 根 :
  • 根 :
  • ⇒ ,。同理可得 。

长度 3:以 (.18 + .20 + .05 = .43)为例:

  • :
  • :
  • :
  • ⇒ (根 case)。

中长区间关键值(题干给定的可信数据):

区间 说明
0.58 break 为根的情形之一
0.56 长度 2
(长度 3 段) 1.02 三个概率 + 子树
1.17 长度 4 段
1.83 长度 5 段
1.89 长度 6 段
2.15 全区间最优期望代价

其中 对应最优根 (char),因此最优树为:

                char(.20)
          ┌───────┴───────┐
      break(.22)        return(.25)
          └──┐         ┌──┴──┐
          case(.18)  do(.05) void(.08)
                              └─┐
                            switch(.02)

对比 PPT 给出的三种方案 [PPT:3上:137-144]:贪心法(按概率从大到小当根)与 AVL 法(强制平衡)得到的期望代价都高于 2.15——这是本考点存在的理由:平衡不等于最优,概率也要考虑。

6. 易错点 + 老师原话

  • 易错:忘了加 。漏掉这一项时,所有 会退化成"选一个概率最大的根",答案变成贪心解。
  • 易错:把 OBST 的切分误写成矩阵链乘的"切两段"。OBST 的根是独占的:左 、右 ;矩阵链乘是 与 , 属于左边。
  • 易错:只算 不算 。题目若问"画出最优树",没有 表就画不出来。
  • 老师原话(PPT 原注):To record the ordering please refer to Figure 10.46 on p.388 [PPT:3上:116-117]——说的就是必须记录根才能还原树。

7. 考试英文答题模板

Let be the minimum expected search cost of an OBST on and its optimal root. Choosing as root splits the keys into and , and every node in both subtrees gains one level, so

Filling by increasing interval length gives time and space, and reconstructs the tree.


3.1.5 Floyd: 必须在最外层 【S 级 · 本科最重要】

1. 一句话本质

不是一个静态的量,而是一族按阶段(stage)更新的量:第 轮结束时它才代表"只允许用前 个点当中转"的最短路。既然更新是分阶段的,控制阶段的 就必须站在最外层——内层两个循环只是"在同一个阶段里把所有格子刷一遍"。

2. 定义与性质

(1)状态定义(这是全部的关键)

记 为:从 到 、中间顶点只允许取自 的最短路径长度。则

即"不经过 "与"经过 "二选一。初值 边权(无直接边为 ,)。最终答案 。

(2)为什么这一层递推不需要"重复用 "

有人会问: 里, 这一段能不能也经过 ?答案是可以忽略——任何经过 的真环路权非负,去掉它不亏;所以"至多经过 "与"只经过 一次"等价,转移式不需要在同一轮内反复迭代。这保证了每个 恰好一轮。

(3)循环顺序的语义

c
for (k = 0; k < n; k++)        /* ← 阶段:允许用第 k 个中转点 */
  for (i = 0; i < n; i++)
    for (j = 0; j < n; j++)
      if (d[i][k] + d[k][j] < d[i][j])
        d[i][j] = d[i][k] + d[k][j];
  • 在外 = 阶段在外:先把"只准用 中转"的所有 个格子全部更新完,再放开 ,以此类推。每一轮结束时整个矩阵处于同一个 DP 阶段——这正是 的定义所要求的。
  • 与 在第 轮中不会变坏:因为 已是最短,绕经 回到自身不会更短(非负权)。所以原地(in-place)更新是安全的,不需要两个矩阵。

(4)复杂度

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最短路》 · https://oi-wiki.org/graph/shortest-path/

Floyd–Warshall 算法的状态定义是: 表示从 到 、中间顶点只允许取自 的最短路径长度。这里“中间顶点”不含起点与终点。递推为

初值 为边权(无边为 ,),答案 。 是阶段,不是普通循环变量:第 轮结束后,整张矩阵必须同时处于“允许前 个点”的状态。

2.2 OI Wiki 校订|基本性质

  1. 必须最外层:只有 在外,才能保证用到的是完整的 。为什么: 的定义要求所有 同时只放开第 个中转点;若 在内层,同一个 只对部分 生效,矩阵不再对应任何一个阶段。
  2. 原地更新安全:第 轮中 与 不会因本轮更新而变小(非负权下绕回 不划算),所以不需要两个矩阵。负权但无负环时该性质仍成立。
  3. 允许负权,不允许负环:负环存在时最短路无定义;判定条件是算完后存在 使 。
  4. 复杂度 时间、 空间:三重循环各 次;空间可原地压缩到一张矩阵。
  5. 可复算实例:、、, 时 。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 明确定义 并强调“ 表示编号 的点可以作为中转”,与 PPT 的 完全一致,无实质冲突。差异有二:其一,OI 额外给出负环判定与“无解输出”的写法,PPT 只讨论非负权;其二,OI 常把顶点编号从 0 开始,PPT 从 1 开始。考试按 PPT 的三重循环写,若题目出现负权,补一句“算完后检查 ”。OI 还给出一个实用变体:需要输出路径时另开 表,与距离表同轮更新,见下一考点的完整讨论。

3. 图示

以 4 个点、 的语义为例:

      阶段 0            阶段 1             阶段 2             阶段 3
  允许中转 {}      允许中转 {0}       允许中转 {0,1}     允许中转 {0,1,2}
        ↓                ↓                  ↓                  ↓
   d = 原始边权  →  所有经 0 的路径  →  所有经 0/1 的路径 →  全部最短路

  "全部格子完成阶段 k" 是一条水平线,矩阵在这一刻是自洽的(consistent)

阶段内的更新范围:第 轮只会改动第 行和第 列之外的格子(准确说,所有 ,其中 )。这就是为什么正确写法里可以把内两层交换成 j 在外 i 在内——当 在外时,内两层顺序无所谓;真正不能动的是 。

4. 复杂度表

顺序 时间 正确性 说明
k, i, j(标准) ✅ 正确 阶段在外
k, j, i(内两层交换) ✅ 正确 内层顺序无关,见上注
i, k, j ❌ 错误 阶段被打散
i, j, k ❌ 错误 阶段被打散(最常考)
j, i, k 等其余排列 ❌ 错误 同上

关键结论:错的顺序复杂度仍然是对的 ,只是答案是错的。 这一点是判断题的靶心——"复杂度没变"不能证明"顺序没错"。

5. 典型例题

Show that Floyd–Warshall's loop must be the outermost loop, and explain what represents after the -th iteration.

中文逐步:

  1. 读状态定义: = 中间点限于 的最短路。注意"限于"两个字,它把矩阵绑在了轮次上。
  2. 读转移:只用 的两项拼出 。所以第 轮开始时, 必须是完整、自洽的。
  3. 若 在最外层,第 轮开始时前一轮刚跑完整个矩阵 ⇒ 自洽 ✓。
  4. 若 在内层,比如 for i: for j: for k:——跑到 时,所有 都试过了,但每一个 都是用旧的、只允许更少中转点的值算出来的,也就是说"接力"只能接一步,接不了两步以上。
  5. 具体后果见 3.1.6 的逐轮表:A→D 需要 A→B→C→D 三段,而内层顺序下这条路径永远拼不起来。
  6. 结论: 是 DP 的阶段变量,不是普通的循环下标。

6. 易错点 + 老师原话

  • 易错(第一大坑):以为"三个循环都是遍历,顺序无所谓"。完全错。 是阶段。
  • 易错:以为"内两层也不能换"。可以换( 在外时 对称),这一点 PPT 与教材都不强调,但考试见过就写清楚更稳。
  • 易错:初始化漏了 ,或把无边的权设成 0 而不是 。
  • 老师原话:「正答率非常低……动态规划的计算顺序,kij 这个三重顺序是不能打乱的,打乱以后就会全错」 [稿:3-逐字稿:119] —— 记住这句话里是 kij, 打头。

7. 考试英文答题模板

Floyd–Warshall is an interval-style DP over stages. Let denote the length of the shortest path from to whose internal vertices are all drawn from . Then

Because each stage is derived only from the complete previous stage, must be the outermost loop: the outer loop advances the stage while the two inner loops only sweep the matrix at one fixed stage. Swapping the loops keeps time but destroys the DP invariant, so the result is wrong, not merely slower.


3.1.6 为何 不能在内层:反例构造与逐轮矩阵 【S 级 · 本科最重要】

1. 一句话本质

反例要制造"必须有两个以上中转点串联"的路径。 在外时,长路径可以一轮一轮地接力拼起来; 在内时, 跑得飞快而 只能给每个格子补一次,跨两段的接力永远接不上。

2. 定义与性质

构造原则(考试照着这三步走):

  1. 画一条链,链上每段权重小(例如 2),使"沿链走"远比"走捷径"便宜。
  2. 加昂贵的直接边(例如 9),让"贪心地一步到位"给出明显错值,便于阅卷看出差别。
  3. 让正确答案需要至少两个中转点串联(三段以上),这样它就把 外层的"多轮接力"与 内层的"一次性"区分开了。

反例图(5 个顶点,有向边):

  A --2--> B --2--> C --2--> D --2--> E        ← 链,每段 2
  A --9--> C        B --9--> D        C --9--> E  ← 昂贵直接边 9

邻接矩阵 ( 记作 -):

A B C D E
A 0 2 9 - -
B - 0 2 9 -
C - - 0 2 9
D - - - 0 2
E - - - - 0

正确矩阵(Floyd 真值):A 行应为 0 2 4 6 8(A→C 走 A-B-C = 4 < 9,A→D = 6,A→E = 8)。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最短路》 · https://oi-wiki.org/graph/shortest-path/

把 放进内层后,算法的实际语义变成:对每个 ,只尝试“至多一个中转点”的路径,且这个中转点还必须恰好是当前 。形式化地,内层版本计算的是

它只覆盖长度不超过 2 的路径,而不是 所要求的“任意多个中转点”。要得到正确答案,必须让 在外层形成 个阶段,使长度为 的路径在第 轮被逐步拼出。OI 还特别提醒:三重循环中 与 可以互换,唯独 不能挪到内层;交换 只改变遍历顺序,交换 则改变算法语义。

2.2 OI Wiki 校订|基本性质

  1. 内层 只实现“一次中转”:路径 需要两轮接力,内层版本在同一轮内看不到 的更新结果。
  2. 链式反例最直观: 每段权 2,另有直边 、、。正确的最短路 需要四次拼接; 在内层时只能拼一次, 会停在 9 或更大的值。
  3. 判定结果是否出错的方法:算完后检查三角不等式 是否对所有 成立。内层版本一般会违反它,外层版本必然满足。
  4. 复杂度不变:仍是 ,所以“顺序错了”不会表现为超时,只会表现为答案错——这也是它成为高频判断题的原因。
  5. 与 Bellman–Ford 的对照:Bellman–Ford 显式做 轮松弛,每轮允许路径长度加 1;Floyd 的 外层正是同一思想的二维版本。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的所有 Floyd 实现都把 放在最外层,并明确注明“ 表示阶段,不能与 交换”,与 PPT 一致,无口径冲突。差别只在反例的呈现:OI 用“必须经过多个中转点”的一般论述,PPT 用具体链式图逐轮打印矩阵。考试若要求“举反例”,画一条长度 的链并标出直边即可。

3. 图示

(正确顺序kij)(错误顺序ijk)┌───────────────────┐┌───────────────────┐│k固定,整个矩阵││i,j固定,k在里面││同时完成一个阶段││转一圈││⇒第k轮用的是││⇒每个格子只做一次││第k-1轮的完整表││局部的k扫描││⇒可以"接力"││⇒接力断在第一段│└───────────────────┘└───────────────────┘

4. 复杂度表

正确 k-i-j 错误 i-j-k
比较次数
渐进时间
中间状态含义 明确的阶段 无意义(半成品混用)
A 行结果(本反例) 0 2 4 6 8 0 2 4 6 9 等错值

5. 典型例题(核心:逐轮矩阵)

Construct a counterexample showing that placing the loop innermost in Floyd–Warshall yields wrong shortest paths.

(甲)正确顺序 for k / for i / for j

第 1 轮 :允许经 中转。 没有入边,任何路径都拼不出经 的新值 ⇒ 矩阵不变。

        A   B   C   D   E
   A    0   2   9   -   -
   B    -   0   2   9   -
   C    -   -   0   2   9
   D    -   -   -   0   2
   E    -   -   -   -   0

第 2 轮 :允许经 。新值全部来自"到 "+"从 出发"的接续:

  • , ⇒ (改 9 → 4)
  • , ⇒ (先记 11,后面会更好)
        A   B   C   D   E
   A    0   2   4  11   -        ← A→C 接上了两段 A-B-C
   B    -   0   2   9   -
   C    -   -   0   2   9
   D    -   -   -   0   2
   E    -   -   -   -   0

第 3 轮 :允许经 。

  • , ⇒ (改 11 → 6)← 三段接力成功
  • , ⇒ (暂记)
  • , ⇒
        A   B   C   D   E
   A    0   2   4   6  13
   B    -   0   2   9  11
   C    -   -   0   2   9
   D    -   -   -   0   2
   E    -   -   -   -   0

第 4 轮 :允许经 。

  • , ⇒ (改 13 → 8)← 四段接力成功
  • , ⇒ (不变)
        A   B   C   D   E
   A    0   2   4   6   8     ← 正确!
   B    -   0   2   9  11
   C    -   -   0   2   9
   D    -   -   -   0   2
   E    -   -   -   -   0

第 5 轮 :无出边,不变。

关键观察:A→E 的值经历 ∞ → 13 → 8 的三次改写,每次都在"一个新阶段"里完成。这是 在外唯一的、不可替代的作用。

(乙)错误顺序 for i / for j / for k

固定 ,逐个 ,把 在内部转一圈。此时 、 用的仍是大部分阶段的半成品。逐步看:

,, 依次取 A,B,C,D,E:

  • :,不变。
  • :, ⇒ , ✓(这一步侥幸对,因为只需一个中转点)。
  • :无改进。⇒ ✓。

,, 依次取 A,B,C,D,E:

  • :不动。
  • :, ⇒ ,。
  • :检查 ⇒ ✓

这一格又对了!原因是 在 较小时已经被前面那一格算好了—— 相同时 递增,恰好形成了"沿 方向接力"。危险恰恰藏在这里:反例必须让接力的两个中间量不在同一个 行、也不是 递增方向上相邻。

把错误暴露出来: 这一行先跑,而 依赖 、 的成熟度

for i 外层时, 在 之前跑完。 那一轮里:

  • 时(),只用到了 、、、,都是初始值或已成熟值 ⇒ 侥幸对。
  • 时: 这一步要 。但 刚刚在 那步才变成 6, 跑到 时读到的是 6 ⇒ ✓。

于是这个 5 节点链式图在 i-j-k 顺序下居然也对——因为单链恰好让" 递增"正好匹配了"路径沿链前进"。必须改造图,打断这种巧合:把链改成让中转点次序与 次序不一致。

改造后的反例(考试用这个):把链倒着编号,使短路径需要"从大下标往小下标"接续。

  A --2--> D --2--> C --2--> B --2--> E
  A --9--> C        D --9--> B        C --9--> E

按字母序 Floyd( 依次为 A,B,C,D,E),A 行的正确值仍是 A→D=2, A→C=4, A→B=6, A→E=8。

  • 正确 k-i-j: 时把 接成 6; 时接出 。两轮接力,正确。
  • 错误 i-j-k: 时 的循环顺序是 :
  • (目标 ):需要 ,此时 的新值 与 还没算出来( 排在 后面)⇒ 只能得到 或 ,。
  • : 给 ✓(这一步是对的,但它来得太晚,救不了 )。
  • : ✓。
  • : 要 ; 要 ⇒ (正确值是 8)。

⇒ A 行最终为 0 11 4 2 13,而正确答案是 0 6 4 2 8。两格出错,且都是长路径。

结论表:

A B C D E
正确 k-i-j 0 6 4 2 8
错误 i-j-k 0 11 4 2 13

错的都是需要"两个以上中转点、且中转点次序与 次序不一致"的格子。 把这句话写在卷子上,比背具体数字更重要。

6. 易错点 + 老师原话

  • 易错(最致命):反例举了 3 个点。三个点的最短路最多一个中转点,任何一个循环顺序都能算对,反例必然不成立。至少 4 个点,最好 5 个。
  • 易错:举了 4 个点但图是对称的/全连通的,正确值恰好被"侥幸接力"求出,自己没发现(如上文第一版 5 节点链)。
  • 易错:只写"会算错"不给矩阵。阅卷看的是逐轮/逐格的数值证据。
  • 易错:拿"时间复杂度依然是 "当反例——那不叫反例,那叫"没说错复杂度"。
  • 老师原话:「打乱以后就会全错」 [稿:3-逐字稿:119]。注意是全错,不是"部分错"——但具体到格子,是被"阶段未成熟"卡住的那些格子,通常是长路径。

7. 考试英文答题模板

Claim. In Floyd–Warshall the loop must be outermost. Proof by counterexample. Take the 5-vertex digraph below with chain edges of weight 2 and shortcuts of weight 9. The true distances out of are . With outermost the distances are produced by successive stages: leaves only one-intermediate paths available, and each later stage extends an already-complete earlier one, so the 3- and 4-edge paths and are discovered one stage at a time. With innermost, at the moment is processed the entries and are still based on the initial matrix, giving and instead of and . The loop then never revisits these cells, so the error is final. Reason. is a DP stage, not an iteration index: stage may only be computed from the complete stage . Nesting innermost interleaves stages and destroys this invariant. Note the time complexity remains — a correct complexity is no evidence of a correct loop order.


3.1.7 前驱矩阵:路径恢复同样要求 在外层 【A 级】

1. 一句话本质

表只给"多长",要在考试卷上写出"怎么走",必须另存一张账本 (或 )记录"这一步往哪走";既然它得跟着 一起在同一个阶段更新, 自然也得在外层。

2. 定义与性质

方案一:后继矩阵(successor / next)

= 从 到 的最短路上, 的下一个顶点。初始化:若边 存在则 ,否则 nil。更新规则:

c
for (k = 0; k < n; k++)            /* 同样必须最外层 */
  for (i = 0; i < n; i++)
    for (j = 0; j < n; j++)
      if (d[i][k] + d[k][j] < d[i][j]) {
          d[i][j]    = d[i][k] + d[k][j];
          next[i][j] = next[i][k];   /* ← 沿 i→k 的第一跳走 */
      }

注意是 next[i][k] 而不是 k:从 到 的新最短路 = 从 到 的最短路 + 从 到 的最短路,第一跳就是 那条路的第一跳。

方案二:前驱矩阵(predecessor): = 到达 的前一个顶点,更新为 pred[i][j] = pred[k][j]。

恢复路径:

c
void printPath(int i, int j) {          /* next 版 */
    if (i == j) { printf("%d", i); return; }
    if (next[i][j] == NIL) { printf("no path"); return; }
    printf("%d -> ", i);
    printPath(next[i][j], j);
}

为什么 也必须在最外层? 因为 与 是同一次更新的两个副作用。任何让 出错的顺序,都必然让 记下一条不存在的、或非最短的"断头路"。更糟的是: 错了还能肉眼看出数值偏大, 错了表面上看不出来,重建时才发现路径跳到一个没有边的地方。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最短路》 · https://oi-wiki.org/graph/shortest-path/

路径还原需要一张与距离矩阵同规模的决策表。后继矩阵 定义为从 到 的最短路上 的下一个顶点;前驱矩阵 定义为到达 的前一个顶点。初始化:若边 存在,则 、,否则为 nil。Floyd 更新距离时同步更新决策表:

c
if (d[i][k] + d[k][j] < d[i][j]) {
    d[i][j]    = d[i][k] + d[k][j];
    next[i][j] = next[i][k];      /* 不是 k */
    pred[i][j] = pred[k][j];      /* 不是 k */
}

恢复路径时从 出发反复取 ,或从 出发反复取 ,长度不超过 。

2.2 OI Wiki 校订|基本性质

  1. 而不是 :新最短路是“ 的最短路 + 的最短路”,第一跳应继承 的第一跳。例: 之间还有一个中转点 ,则第一跳是 ,直接写 会跳过 。
  2. 决策表必须与距离表同步更新:只在距离变小时改 ,否则会记录一条“旧最短路”的下一跳。
  3. 仍须最外层:任何让 出错的循环顺序都会让 记下不存在的边;而且 出错时数值上看不出来,只在重建路径时暴露。
  4. 空间 :与距离矩阵同阶;若只需一对点的路径,可在算法结束后用 的 重建,时间与路径长度成正比。
  5. 负环时 可能成环:恢复前先检查是否存在 ,否则循环不终止。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 给出 pre 与 nxt 两种写法,并注明“更新时继承的是中转点的决策值,而不是中转点本身”,与 PPT 的 next[i][j]=next[i][k] 一致。差异仅在命名:OI 常写 pre[i][j],PPT 写 pred[i][j]。考试按 PPT 的 next 版本作答,并写清“为什么不是 ”这一句,这是该考点最常见的扣分点。OI 的实现里若同时维护 与 ,两者必须由同一次松弛触发,不能分开更新,否则两表会指向不同版本的最短路。

3. 图示

   0 --4--> 1 --3--> 2        正确 d[0][2]=7, next[0][2]=1
             ↘1↗
   0 --9----------> 2

   若 k 与内层混用:
     d[0][2] 被更新成 7,但 next[0][2] 取的是旧的 next[0][1](仍指向一个
     未成熟中转点的索引)⇒ 重建路径 0 -> ? -> 2 在中途断掉

   正确:  0 → 1 → 2
   错误:  0 → nil(断裂)

4. 复杂度表

内容 时间 空间
表
/ 表 与 同步,无额外渐进代价 (翻倍)
单条路径重建 栈

5. 典型例题

Modify Floyd–Warshall so that the actual shortest path, not just its length, can be reported, and state the required condition on the loop order.

中文逐步:

  1. 新增二维数组 next[n][n],初始化为 -1;对每条存在的边 ,设 next[i][j] = j;d[i][i]=0 时 next[i][i]=i。
  2. 在 if (d[i][k] + d[k][j] < d[i][j]) 的同一分支里写 next[i][j] = next[i][k]。
  3. 强调:加这一行不改变任何循环结构,所以 依然在最外层——反过来说,若有人只在 上做对了顺序而把 next 的更新放到另一个循环里,两张表会在阶段上脱节,重建必断。
  4. 边界: 直接输出;next[i][j]==-1 说明不连通。
  5. 举 PPT 那种"边权大、绕路短"的图,用 走出与 一致的长度,做交叉校验。

6. 易错点 + 老师原话

  • 易错:写成 next[i][j] = k。只有当" 本身是一条边"时才等价,一般情况应为 next[i][k]。
  • 易错:初始化只对边赋值,忘了 next[i][i]=i,导致 i==j 时输出 -1。
  • 易错:next 用 int 存索引而把 nil 写成 0——顶点 0 是合法下标,必须用 -1(或 n 以上的哨兵)。
  • 老师原话(纪要定性):[纪要:3-conclusion] 二、2 把"前驱矩阵"列为 Floyd 顺序的工程补充,即它是同一个 DP 不变量的延伸,不是新算法。

7. 考试英文答题模板

To report the path itself, maintain a successor matrix initialized to for every edge . Inside the same relaxation done by Floyd–Warshall, add next[i][j] = next[i][k], because the first hop of the new shortest path is the first hop of . Since is updated as a side effect of the same transition, it inherits the same staging requirement: must remain the outermost loop, otherwise records hops through intermediate vertices whose own entries are not yet final and the reconstructed path breaks.


3.1.8 0-1 背包 DP(整数重量限制) 【S 级】

1. 一句话本质

每件物品只有"拿/不拿"两种选择,且容量有限;把"前 件物品、容量 "当作状态,逐件物品做一次二选一的转移。

2. 定义与性质

件物品,第 件重量 、价值 ,背包容量 。求容量内可装的最大总价值。

状态: = 只考虑前 件物品、容量为 时的最大价值。

初值 (零件物品)、。答案 。

"0-1"的含义:每件物品至多取 1 次(0 或 1)。若可无限取,是完全背包(complete knapsack),转移变为 ——注意第二项是 而非 。

一维滚动优化:

c
for (i = 1; i <= n; i++)
    for (w = W; w >= w[i]; w--)               /* ← 必须从大到小 */
        f[w] = max(f[w], f[w - w[i]] + v[i]);

为什么必须逆序? 第 件物品的转移要求右边是 的值。从小到大遍历时, 已经在本轮被更新过(可能刚装入了第 件),再拿它转移等于同一件物品被装了两次——那算出来的就是完全背包。从大到小遍历时, 尚未在本轮被触碰,仍是 ✓。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《背包 DP》 · https://oi-wiki.org/dp/knapsack/

0-1 背包的形式定义:给定 件物品,第 件重量 、价值 ,背包容量 ;每件物品只能取 0 次或 1 次,求总重量不超过 时的最大总价值。设 为只考虑前 件、容量为 时的最大价值,则

答案是 。“0-1”指每件物品的选取次数只能是 0 或 1;若允许无限次,则第二项改为 ,即完全背包。

2.2 OI Wiki 校订|基本性质

  1. 最优子结构:第 件要么不取(继承 ),要么取(继承 再加 ),两者取大。为什么穷尽:0-1 的决策只有两支。
  2. 一维滚动必须逆序:从小到大遍历时 已被本轮更新,等于同一件物品被重复选取;从大到小则 仍是第 层的值。例:、,正序会把物品装 3 次。
  3. 时间 、空间 :状态数 ,单次转移 ;滚动后只留一维。
  4. 与分数背包的分界:物品不可分割才需要 DP;可分割时按单位价值贪心即为最优。
  5. 可复算实例:、、、,最优为两件都取,价值 ,重量 。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 把 0-1、完全、多重背包并列,并用“倒序 / 正序”作为一维写法的助记;PPT 只讲 0-1,但把“为什么逆序”作为判断题考点。两者结论一致。考试若要求写递推,按 PPT 的二维式起手,再说明可压成一维;不要直接写一维而不解释逆序,否则会被判为跳步。OI 另给出“恰好装满”的变体:初值改为 、其余置 ,可与“至多”版本对照记忆,两者的差别只在基例,答题时须先声明用哪一种。

3. 图示

              f[i-1][w]           ← 不拿第 i 件
             /
   f[i][w] <
             \
              f[i-1][w-w_i] + v_i  ← 拿第 i 件(容量先让出 w_i)

二维填表方向:由 行推出 行,第 行从左到右都可以(二维不需要逆序,逆序只在一维滚动时才必要)。

4. 复杂度表

版本 时间 空间
二维 DP
一维滚动
回溯"选哪些" 二维必须保留整表,或额外存决策位

5. 典型例题

PTA HW3-03. Given items with weights and values , and knapsack capacity , find the maximum total value.

其中 25.3 与 40 均为本题给定答案数据。

中文逐步( 表,只列关键列 ;物品 ):

\ 0 1 2 3 4 5 6 7 8 9 10 11
0 0 0 0 0 0 0 0 0 0 0 0 0
1 () 0 4 4 4 4 4 4 4 4 4 4 4
2 () 0 4 6 10 10 10 10 10 10 10 10 10
3 () 0 4 6 10 10 18 22 24 28 28 28 28
4 () 0 4 6 10 10 18 22 24 28 28 32 32
5 () 0 4 6 10 10 18 22 25.3 28 29.3 32 40

倒数第二行的关键值对照(,):

  • 不拿:。
  • 拿: ⇒ 。

最后一行(,):拿第 5 件 ⇒ ⇒ 不拿,。

⇒ 。

回溯: ⇒ 不选物品 5; ⇒ 选物品 4(6, 22);转 : ⇒ 选物品 3(5, 18),容量归零。剩余为 0,物品 1、2 放不下。选中 :重量 ,价值 ✓。

6. 易错点 + 老师原话

  • 易错(最高频):一维优化写成 for (w = w[i]; w <= W; w++)。这会让同一件物品被重复使用,算成完全背包。0-1 背包必须逆序。
  • 易错:状态定义漏了"前 件"这个维度,写成 又不知道 在哪一层——面试/卷面上必须显式写 。
  • 易错:容量 列和物品 行没初始化。
  • 易错:题干问"选哪些"却只给最优质值。要做回溯,必须能查到 ,一维滚动版已被覆盖,不可回溯——要么保留二维表,要么另存决策。

7. 考试英文答题模板

Define as the maximum value achievable using a subset of the first items with capacity . Then

where the first term skips item and the second takes it once — hence "0-1". The table has states and work per state, giving time; it can be rolled into one dimension, but then the capacity loop must run downwards, otherwise has already been updated in the current round and the same item would be reused.


3.1.9 参数陷阱:O(nW) 不是多项式 【S 级 · 判断易错】

1. 一句话本质

复杂度里的 是容量的数值,不是"输入里有多少个数"。把 用二进制写出来只占 位,那么 相对于输入长度是指数级的——这种"数值多项式、长度指数"的算法叫伪多项式(pseudopolynomial)。

2. 定义与性质

输入规模(input size) 的定义是编码后的比特数。整数 在二进制下的编码长度为

而算法耗时与 本身成正比:

令输入总长度为 ,则 ——关于 是指数级。

定义:若算法复杂度可写成 ,其中 是输入编码长度,则为多项式时间(polynomial time);若复杂度是 的多项式,则为伪多项式(pseudopolynomial)。

算法 复杂度 关于数值 关于编码长度 性质
冒泡/归并排序 、 — 多项式 多项式
矩阵链乘 — 多项式 多项式
素数判定(试除到 ) 多项式 指数 伪多项式
0-1 背包 DP 多项式 指数 伪多项式
加权活动安排 DP — 多项式 多项式

"既是 NPC 又有 DP 解"如何不矛盾?

0-1 背包是 NP-complete(弱 NP-hard,weakly NP-hard)的一个实例族。这看起来和"它有一个 的 DP"矛盾,其实不然:

  1. NP-completeness 是按输入比特长度衡量的。 在比特长度下是指数级 ⇒ DP 不是多项式算法 ⇒ 不构成 的证明,理论上没有矛盾。
  2. "弱"NP-hard:背包的难度只来自数值变大,不来自组合结构。当 是多项式有界()时, 就是多项式的——这类问题称为 weakly NP-hard。反例是强 NP-hard(strongly NP-hard),如 TSP、3-划分,即便所有数值都很小也没有伪多项式算法(除非 )。
  3. 所以正确的说法是:背包问题不存在已知的多项式算法(当 用二进制给出时),但存在伪多项式算法;两者相容。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算理论基础》 · https://oi-wiki.org/misc/cc-basic/

算法复杂度的“多项式”必须以输入编码长度为尺度。整数 用二进制表示时占 位,故输入长度 。若某算法的运行时间是 ,称多项式时间(polynomial time);若运行时间是某个数值的多项式、但关于 为指数级,称伪多项式时间(pseudo-polynomial time)。形式化地,存在多项式 使 但 。0-1 背包的 正是这种情形。

2.2 OI Wiki 校订|基本性质

  1. 数值多项式不等于比特多项式: 只需约 20 比特编码,而 可达 量级,二者相差多个数量级。
  2. 弱 NP-hard 与强 NP-hard 的分界:弱 NP-hard 问题(背包、子集和、划分)有伪多项式算法;强 NP-hard 问题(TSP、3-划分)在所有数值都被多项式界住时仍无多项式算法,除非 。
  3. “NPC 却有 DP”不矛盾:NP-completeness 按比特长度衡量, 在该尺度下是指数级,因此不构成多项式算法。
  4. 判定伪多项式的操作办法:把数值改成一进制后若算法变成多项式,则原算法是伪多项式。例: 在 用一进制表示时,输入长度含 ,故为多项式。
  5. 强 NP-hard 的判定:即使把所有数值限制在 ,问题仍是 NP-hard,则不存在伪多项式算法(除非 )。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 在“复杂度类”一节给出 、、伪多项式与强弱 NP-hard 的严格定义,还给出“强多项式 / 弱多项式”的区分;PPT 只要求记住“ 不是多项式”这一结论。考试按 PPT 的结论作答,理由写一句“ 是数值,输入长度是 ,故关于输入长度是指数级”即可。注意 OI 的“弱多项式”指运算次数是比特数的多项式、但数值参与运算的位宽不受限,与“伪多项式”不是同一概念。

3. 图示

 W = 1 000 000 000
 输入比特数 ≈ log₂ W ≈ 30 bits     ← 输入的"长度"
 DP 计算量  ≈ W       = 10⁹ 次     ← 与数值成正比
                     ↑
   输入涨了 30 倍(比特),计算量涨了 10⁹ 倍 → 指数关系

 对比:把 W 写成 W = 10⁹ 的"实例",任何解题者只需读 30 个比特;
       而 DP 要跑 10⁹ 步。输入越小、跑得越久 → 这就是伪多项式的脸。

4. 复杂度表

设 结论
( 比特) 很小,与 同阶,看起来"多项式"
(二进制长 bit) ,指数
一般 ,伪多项式

5. 典型例题

"The 0-1 knapsack problem is NP-complete, yet we solved it in time. Is this a contradiction? Justify."

中文逐步:

  1. 先定性: 里的 是物品件数(一个"计数"量, 件物品的编码长度 ,与 同阶); 是容量数值。
  2. 算输入长度:若容量上限为 ,二进制编码需要 位。总输入长度 。
  3. 代换:,所以 ——对 是指数的。
  4. 所以不是多项式算法,不能推出 。
  5. 解释为何有 DP 解:NP-completeness 的难度度量是比特长度;背包的难度随数值膨胀而膨胀,故属弱 NP-hard;一旦 被 的多项式界住(例如 ), 就是多项式了。
  6. 结论句:The algorithm is pseudopolynomial, not polynomial, so there is no contradiction.

6. 易错点 + 老师原话

  • 易错(判断题靶心):看到 就答"多项式时间"。错。必须问 是怎么编码的。
  • 易错:以为"有 DP 就说明不是 NPC"。错,见上文第 2 条。
  • 易错:把 当成"数组长度"就不算数值。 是容量值,不是元素个数——这里正是陷阱所在。
  • 老师原话(纪要):[纪要:4-conclusion] 二、2 把这类问题定性为参数陷阱(parameter trap):复杂度与"数值大小"成正比,而非与输入规模成正比。
  • 加分点:能说出 weakly / strongly NP-hard 的区别,是 S 级题的满分线。

7. 考试英文答题模板

No, there is no contradiction. The running time is measured against the value of the capacity , whereas the input size is the number of bits needed to encode it, namely . Writing shows that is exponential in the input length, so the DP is pseudopolynomial, not polynomial, and it does not imply . The knapsack problem is only weakly NP-hard: its hardness comes from large numeric values, and when is bounded by a polynomial in the same DP becomes polynomial. In contrast, strongly NP-hard problems admit no pseudopolynomial algorithm unless .


3.1.10 加权活动安排只能 DP,贪心失效 【S 级】

1. 一句话本质

不带权时"先结束的先安排"最优;一旦每个活动带上权重,"选得多"不再等于"选得好",贪心就会为了几个小权重活动而丢掉一个大权重活动——只能用 DP。

2. 定义与性质

问题:给定 个活动,第 个活动有开始时间 、结束时间 、权重 。选出两两不重叠的活动子集,使总权重最大。

(1)为什么贪心失效

无权版本()的贪心正确性依赖于交换论证:任意最优解的第一个活动都可以换成"结束最早"的那个,且不减少活动数。有权版本中"个数"不再是目标,这个交换会减少总权重,论证崩溃。

反例:三个活动

活动 区间 权重
1 100
2 1
3 、 相容的两个小活动 各 1

贪心按结束时间最早挑,会得 2 个活动、权重 2;最优是单挑活动 1、权重 100。"早结束优先"给出的是 2,最优是 100。

(2)DP 递推(老师强调的关键公式)

排序后按 递增编号 。定义 = 最大的下标 且 (与 相容的最后一个活动)。

记 = 前 个活动的最优总权重(这里 既是函数名又是数组名,注意与结束时间 区分):

两项含义:不选活动 ⇒ 退化为前 个的最优;选活动 ⇒ 前面只能用到与 相容的最后一个(下标 ),加上 的权重 。

答案 。时间 (排序 + 二分)或 (朴素求 ),空间 。

(3)为什么"只能 DP"

它满足最优子结构(包含 的最优解必然包含 上的最优解——剪切-粘贴)与重叠子问题( 会指向同一个更早的前缀),且决策序列具有后效的传递性: 与 两条知识谱系会合并,贪心的局部选择无法预知。因此 DP 是正解,贪心是陷阱。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《DAG 上的 DP》 · https://oi-wiki.org/dp/dag/

加权活动安排(weighted interval scheduling)的形式定义:给定 个活动,第 个活动占据区间 、权重 ;两个活动相容当且仅当区间不重叠。求两两相容的活动子集,使总权重最大。按 升序编号后,定义 ,即与 相容的最后一个活动;若不存在则 。设 为前 个活动的最优总权重,则

答案是 。该递推等价于在 DAG 上求最长路:结点是活动,边 表示 ,点权为 。

2.2 OI Wiki 校订|基本性质

  1. 贪心在加权版本失效:无权版本按最早结束时间选是安全的;有权版本中“多选一个”可能换来“丢掉一个大权重”。反例: 权 100、 权 1、 权 1、 权 1,最早结束贪心得 3,最优是 100。
  2. 两种决策穷尽:不选 得 ,选 则前面只能用到 ,得 。
  3. 单调不减: 升序时, 不必单调,但 可用二分在 内求出;总时间 。
  4. 与 DAG 最长路的等价性:按结束时间排序后所有边都从前指向后,故图无环;在 DAG 上按拓扑序 DP 即得上式。
  5. 空间可压到 : 只依赖 与 ,保留整个数组即可,无需二维表。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 没有“加权区间调度”的独立条目,但《DAG 上的 DP》给出的“结点带权、按拓扑序求最长路”与本题完全同构。PPT 用 表示结束时间、又用 表示最优值,符号撞车;OI 通常把结束时间记 、DP 数组记 。考试按 PPT 的符号时,务必在卷面上区分“(结束时间)”与“(最优值)”,这是该考点最常见的表述扣分点。

3. 图示

活动(按结束时间排序)12345区间[1,3)[3,5)[5,7)[7,9)[2,6)权重10011150p(j)01231←与j相容的最后一个决策树:f[5]┌──────┴───────┐不选5:f[4]选5:f[1]+50││┌────┴────┐└─100+50=150不选4:f[3]选4:f[3]+1...相比贪心:按"结束最早"给1,2,3,4⇒总权重103;DP给{1,5}⇒150

4. 复杂度表

版本 时间 空间 正确性
无权贪心(early finish first) ✅ 仅 时正确
有权贪心 ❌ 错误
有权 DP(朴素求 ) ✅
有权 DP(二分求 ) ✅

5. 典型例题

Given the activities below (start, finish, weight), find a maximum-weight set of mutually compatible activities, and explain why the earliest-finish-first greedy fails.

四个活动(已按 排序):

1 1 3 100
2 3 5 1
3 5 7 1
4 2 6 50

求 :

  • 。
  • : ⇒ 。
  • : ⇒ 。
  • :?否。。

填表:

  • 。
  • 。
  • 。
  • 。
  • 。

⇒ ,选 (区间 依次相接,总权重 )。

对比贪心:early-finish-first 得到 ,也是 102——这个例子没能打脸贪心。要打脸必须让"多选"反而更差,如下表:

1 1 10 100
2 2 3 1
3 4 5 1
4 6 7 1
  • 贪心取 ,总权重 3。
  • DP:;(都不与 1 相容,彼此相继)。
  • ;;;。
  • ⇒ ,选 。

3 vs 100——这就是"加权后贪心失效"的最小反例,考试直接用它。

6. 易错点 + 老师原话

  • 易错(本考点唯一靶心,老师原话点名的坑):

    「这是个坑,这个公式其实是错的,这个地方得是权重。」 [稿:3-逐字稿:275]

即递推里第二项必须加该活动的权重 ,不能写成 (那是无权版本)。写错成 时, 的含义退化为"最多能选几个活动",遇到上面的反例立刻给出 3 而不是 100。卷面上把 写清楚,就是送分点。

  • 易错: 的定义写成"第一个与 相容的活动"。必须是最后一个(最大下标),否则丢掉前面的活动。
  • 易错: 的比较方向写反(用 而不是 )。
  • 易错:以为"排序后就是线性 DP 了"。排序是必须的预处理, 也是答案的一部分。
  • 易错:说"这是贪心问题"。加权后不是,判断题答"贪心正确"一律零分。

7. 考试英文答题模板

When activities carry weights, the earliest-finish-first greedy is no longer optimal: a single heavy activity may outweigh many light compatible ones, so the exchange argument used for the unweighted case fails. Sort activities by finish time and let be the largest index with . Then, with the maximum total weight over the first activities,

where the second term adds the weight of activity — not the count . The optimum is ; with binary search for the running time is and the space . This DP is correct because the problem has optimal substructure and overlapping subproblems, and a greedy choice cannot anticipate the interaction between the two predecessor chains.


附:本讲一页速背

考点 一句话
3.1.1 Use a table instead of recursion;指数 → 多项式
3.1.2 最优子结构(cut-and-paste)+ 重叠子问题(vs 分治:不重叠)
3.1.3 ,按 len 填,
3.1.4 ,记根 ,
3.1.5 必须最外层: 是阶段,不是下标
3.1.6 5 节点链 + 昂贵直接边; 内层 ⇒ 长路径接不上 ⇒ 错值
3.1.7 next[i][j]=next[i][k],与 同阶段 ⇒ 同样要求 在外
3.1.8 ;一维逆序
3.1.9 中 是数值 ⇒ 对编码长度指数 ⇒ 伪多项式,与 NPC 不矛盾
3.1.10 ;加权重,不是 +1
3.2.1 – 3.2.9

详解 06 · 贪心

配套 A0_速查册.md 的「3 下」部分。速查册给结论,本册给推导、证明与手算过程。 证据约定:[PPT:3下:n] = PPT_文本/ADS/第三讲(下)Greedy.txt 行号;[稿:n] = 课堂录音文稿/3-逐字稿.md 行号;[纪要:3:n] = 3-conclusion.md 行号。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

优化问题:constraints+optimizationfunction→要optimalsolution↓能不能不枚举?DP:枚举所有划分点,O(N²)(每个贪心背后都有一个傻傻的DP)↓直接取"眼前最好的",一步到位Greedy:localoptimum==globaloptimum时才成立↓必须证明(举不出反例≠正确)两个条件:①贪心选择性质(safechoice)②最优子结构↓证明工具只有一个交换论证:取任一最优解→对齐到贪心解→换掉第一个不同处→代价不增↓三个战场活动安排(1间教室,最多活动)→按finish升序最少教室(∞间教室,最少教室数)→按start升序+最小堆;答案=最大重叠数Huffman(最优前缀码)→每次合并两个最小;两个引理↓边界加权重→贪心失效,只能DP

3.2.1 贪心框架 【A 级】

1. 一句话本质

贪心就是放弃枚举:DP 把所有可能的第一步都试一遍再取最优;贪心直接认定某一个第一步是对的,然后一路往前。前提是你必须证明那一步真的对,否则抄近道就变成瞎猜。

2. 定义与性质

PPT 的词汇体系 [PPT:3下:10-19]:

Optimization Problems: Given a set of constraints and an optimization
function. Solutions that satisfy the constraints are called feasible
solutions. A feasible solution for which the optimization function has
the best possible value is called an optimal solution.

The Greedy Method: Make the best decision at each stage, under some
greedy criterion. A decision made in one stage is not changed in a later
stage, so each decision should assure feasibility.
词 含义 活动安排中的例
constraint(约束) 解必须满足的条件 选中活动两两相容
feasible solution(可行解) 满足约束,不要求最好 {a1, a4}
optimal solution(最优解) 目标函数取到最优的可行解 {a1,a4,a8,a11},size=4

关键性质:不可撤销性 [PPT:3下:18-19]。这句话划出贪心与 DP 的分界——DP 可以填完表再回头改主意,贪心选了就不能反悔。因此每步必须同时满足可行性(feasibility)与安全性(safety)。

PPT 的警告 [PPT:3下:30-46]:

Greedy algorithm works only if the local optimum is equal to the
global optimum.
Greedy algorithm does not guarantee optimal solutions. However, it
generally produces solutions that are very close in value (heuristics)
to the optimal, and hence is intuitively appealing when finding the
optimal solution takes too much time.

「local = global」是条件不是结论([稿:242]:「只有 local 跟 global 正好是同一个……运气好的话,它比动态规划还要强大」)。

三要素(PPT [PPT:3下:183-193]):

1. Cast the optimization problem as one in which we make a choice
   and are left with one subproblem to solve.
2. Prove that there is always an optimal solution to the original
   problem that makes the greedy choice, so that the greedy choice is
   always safe.
3. Demonstrate optimal substructure by showing that, having made
   the greedy choice, what remains is a subproblem with the property
   that if we combine an optimal solution to the subproblem with the
   greedy choice we have made, we arrive at an optimal solution to
   the original problem.

第 1 条是结构性要求:问题必须能切成「一步选择 + 一个剩余子问题」。这区分了贪心与分治——分治切出两个独立子问题,贪心切出单边递归([稿:272]):

「贪心相当于什么?我就分成了一步,当前的一步,加上剩下的子问题,它是个单边儿的分治,所以严格来说它不能叫分治了,它是个单边儿的递归。」[稿:272]

结尾名言 [PPT:3下:195-196]:Beneath every greedy algorithm, there is almost always a more cumbersome dynamic-programming solution。这是 3.2.9 的伏笔,也是做题的降级路径——贪心证不出来就退 DP。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《贪心》 · https://oi-wiki.org/basic/greedy/

OI Wiki 把贪心定义为:在每一步决策中,只依据当前已掌握的信息选取局部最优的一步,且此后不再修改该决策,最终得到的解即算法输出。形式化地,一个问题可用贪心求解,需能写成“选择 + 剩余子问题 ”的单边递归:,直到子问题为空。每一步的选择必须满足可行性(feasibility)——不破坏约束;以及安全性(safety)——存在某个全局最优解包含这一步。只有这两条同时成立,局部最优才可能拼成全局最优。

2.2 OI Wiki 校订|基本性质

  1. 不可撤销性:贪心一旦选定就不再回溯,因此它不能像 DP 那样“填完表再改主意”。为什么这是定义性的:若允许回溯,算法就退化成搜索。
  2. “局部最优 = 全局最优”是条件而非结论:满足时贪心既快又对;不满足时贪心只是启发式。例:活动安排按最早结束选成立,按最短持续时间选不成立。
  3. 单边递归 vs 双边分治:贪心每步只留下一个子问题,分治留下两个以上。例:归并排序左右两半都要算,活动安排选完一个只剩一段。
  4. 拟阵(matroid)是可判定的充分条件:若可行解集构成拟阵(遗传性 + 交换性),按权排序的贪心必得最优。例:图的无环边集是拟阵,故 Kruskal 正确;活动安排的相容集不是拟阵,所以必须另写交换论证。
  5. 正确性证明的两条标准路线:交换论证与安全选择归纳,见下一考点。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的《贪心》页偏重“怎么构造反例、怎么用邻项交换证明排序类贪心”,并补充了“反悔贪心”(用堆撤回 earlier 决策)这一竞赛技巧;PPT 只讲经典贪心与三段式证明。考试按 PPT 的三要素作答;若题目问“贪心为什么不一定对”,写“局部最优与全局最优不同向”,并举一个反例即可,不必引入拟阵。

3. 图示

问题P(要最优)│┌───────┴────────┐枚举第一步认定第一步(DP)(Greedy)││P₁P₂…Pₖ只留P_greedy││max/min取最优P_greedy递归││O(N²~N³)O(NlogN)─────────────────────────────代价:必须证明P_greedy中含最优解(safechoice)

4. 复杂度表

方法 时间 正确性来源
枚举/暴力 定义
DP (活动安排) 最优子结构 + 重叠子问题
Greedy (排序主导) 交换论证 + 最优子结构

时 不可接受, 轻松通过——这正是 PTA 题设 的用意。

5. 典型例题

State the two properties a greedy algorithm must satisfy, and explain why the second is not implied by the first.

  1. 贪心选择性质(greedy-choice property):存在一个全局最优解包含贪心的第一步选择,即「贪心选择是安全的」。
  2. 最优子结构(optimal substructure):做了贪心选择后,剩余问题的最优解与贪心选择合并,构成原问题最优解。

为什么第二个不被第一个蕴含?第一个只保证「第一步不亏」,不保证「第一步之后剩下的子问题还是原问题的缩影」。若选完后约束已变形(不再是纯区间相容,而带上额外限制),「local = global」就断链。工程判据很实用:贪心选完后,剩余问题必须仍是同一类、同规模的问题;若问题变了形,就必须上 DP。

6. 易错点 + 老师原话

  • 易错 1:把贪心等同于「每次取最大/最小」。规则是设计出来的:PPT [PPT:3下:121-144] 列了 4 条候选,只有 1 条对。
  • 易错 2:以为「举不出反例 ⇒ 正确」:

「它是最优解,你举不出反例,你说我想不到反例,它就是对的,这话不成立,有可能是因为你想不出来,不是说反例不存在。你得严格地证明它是对的。」[稿:260]

  • 易错 3:只证可行性不证最优性。老师明确摆了顺序:

「你每一步构造的时候,你都得构造的是可行解,你不能给一个不可行的解,就后头就不要讨论了,在可行解的基础上(再证最优)。」[稿:263]

  • 易错 4:把 heuristic 当成近似算法。PPT 说贪心「very close in value」,但 [稿:248] 补刀:

「heuristic 的问题在哪?它没法精确地做分析……你说我希望它充分接近最优解,你怎么知道它接近不接近?它有可能非常差。」[稿:248]

近似算法能证明 ρ 上界,heuristic 不能。判分时这两个词不能混。

7. 考试英文答题模板

A greedy algorithm builds a solution by making, at each stage, an irrevocable locally optimal choice under a fixed criterion. It is optimal only when the local optimum coincides with the global optimum. Two properties must be established: (i) the greedy-choice property — there is always an optimal solution containing the greedy choice, so the choice is safe; (ii) optimal substructure — an optimal solution to the remaining single subproblem, combined with the greedy choice, is optimal for the original problem. Neither may be assumed; both require proof, typically by an exchange argument.


3.2.2 交换论证与安全选择 【S 级 · 证明必考】

1. 一句话本质

你不需要「发明」最优解长什么样,只需证明:随便拿一个最优解来,我都能把它改造成「以我的贪心选择开头」的样子,而且不变差。既然存在一个以贪心开头的最优解,贪心第一步就是安全的;剩下的事交给归纳。

2. 定义与性质

名称 形式 典型用法
交换论证(exchange argument) 取任一最优解 ,与贪心解逐项比对,找第一个不同处,把 中的元素换成贪心元素,证明目标值不变差 活动安排、Huffman
安全选择(safe choice / staying ahead) 归纳:第 步贪心后,存在一个最优解与前 步一致 最少教室、拟阵类问题

[纪要:3:29-30]:「通常采用『交换论证』或『安全选择』策略」。

可复用的三段式模板(考试直接套):

──────────────────────────────────────────────STEP①设最优解设A*是原问题的一个最优解(任意取定,不假设由贪心产生)。STEP②找第一个分歧点设贪心前i-1步与A*一致(i=1时"前0步一致"恒真)。考察贪心第i步的选择g_i。若g_i∈A*,继续;否则在A*中找出与g_i"竞争同一位置"的元素d,并证明d存在、且与g_i一一对应。STEP③交换并证明不变差构造A'=(A*\{d})∪{g_i}。要证:(a)可行性:A'仍是可行解(g_i不与A*其余元素冲突)(b)不变差:value(A')≥value(A*)(最大化)或cost≤(最小化)由(a)(b):A'也是最优解,且前i步与贪心一致。对i归纳⇒存在最优解=贪心解⇒贪心最优。□──────────────────────────────────────────────

三个卡点,踩一个就扣分:

  1. STEP ③(a) 最容易被跳过,而这恰是唯一有技术含量的地方。
  2. value 方向:最大化证 ,最小化(Huffman 代价)证 。写反等于没证。
  3. 「 存在」不能默认:若 里没有可换对象,说明它已包含 ,该走「相同」分支。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《贪心》 · https://oi-wiki.org/basic/greedy/

交换论证(exchange argument)的形式定义:设贪心解为 ,任取一个最优解 。若 ,令 为第一个使 的下标;在 中找出与 竞争同一“位置”的元素 ,构造 ,证明 仍可行且目标值不变差。于是 也是最优解,且它与 在前 步一致。对 归纳,分歧位置严格后移,至多 步后 ,故 最优。安全选择(staying ahead)是它的归纳版表述:第 步后仍存在最优解与前 步一致。

2.2 OI Wiki 校订|基本性质

  1. 三个必答环节:① 存在;② 交换后仍可行;③ 目标值不变差。缺任一环节,证明不成立。
  2. 方向不能写反:最大化证 ,最小化证 。写反等于没证。
  3. 可行性是最有技术含量的一步: 与 其余元素是否冲突,必须逐条检查,不能默认。
  4. 归纳能终止:每轮至少让一个位置与贪心一致,且不回退,故至多 轮。
  5. 失效的典型信号:找不到与 对应的 ,或交换后可行性被破坏。例:活动安排改用“持续时间最短”时,被换掉的短活动可能与后续多个活动冲突,可行性无法保持。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 在排序类问题上把交换论证写成“邻项交换”:任取相邻逆序对,交换后答案不变差 ⇒ 有序排列最优。这与 PPT 的“与最优解逐项比对”是同一论证的两种粒度:邻项交换证的是“排序准则”,逐项比对证的是“选择准则”。考试按 PPT 的三段式模板写;若题目是“按什么关键字排序”,用邻项交换更快。

3. 图示

A*:   [ a₁ ][  a₄  ][   a₈   ][  a₁₁  ]    相容,size = 4
        ↑ 分歧处:贪心要 a₁,A* 排第一的是别的 d

交换前:  A* = { d, x₂, x₃, … }       f(d) 较晚
交换后:  A' = { a₁, x₂, x₃, … }
         f(a₁) ≤ f(d) ⇒ a₁ 与 x₂ 的间隙更大
         ⇒ x₂,x₃,… 仍相容 ⇒ |A'| = |A*| ⇒ A' 也最优,且以贪心开头 ✅

4. 复杂度表

事项 代价
证明本身 不进入程序
排序(前置)
贪心主循环
合计 ,排序主导

5. 典型例题(完整走一遍)

Prove: repeatedly selecting the compatible activity with the earliest finish time yields a maximum-size compatible subset.

数据用 PPT 那张表 [PPT:3下:102-104](已按 升序编号):

1 2 3 4 5 6 7 8 9 10 11
1 3 0 5 3 5 6 8 8 2 12
4 5 6 7 9 9 10 11 12 14 16

贪心: → → → ,size = 4。

① 取最优解。 设 是任一最大相容子集,按结束时间升序记为 , 为最优值。

② 找分歧点。 贪心第一步选 (全集中 最小,题目假设 )。

  • 若 ,前 1 步已一致,转归纳。
  • 否则 。因 是全集中 最小者,必有
    就是要找的替换对象( ⇒ ⇒ 存在)。

③ 交换。 令 。

(a) 可行性。 需证 与 都不冲突。 相容 ⇒ 。由 (★):,故 与 相容; 结束更早,自然不与 冲突。

(b) 不变差。 ,且可行, 已最优 ⇒ 也最优。

(c) 归纳。 是最优解且首元素为 ,与贪心一致。把「第一个」换成「前 个」重复上述三步,取到贪心终止,即得贪心解本身最优。

PPT 三行精华 [PPT:3下:150-158]:

【 Theorem 】 Consider any nonempty subproblem S_k, and let a_m be an
activity in S_k with the earliest finish time. Then a_m is included in
some maximum-size subset of mutually compatible activities of S_k.
Proof: Let A_k be the optimal solution set, and a_ef is the activity in
       A_k with the earliest finish time.
   If a_m and a_ef are the same, we are done! Else ……
   replace a_ef by a_m and get A_k'.
   Since f_m ≤ f_ef, A_k' is another optimal solution.

老师把这套流程拆成两条互补骨架:

「我把我用贪心法得到最早结束的任务换进来,跟你那里头最早结束的第一个任务换出去……我可以保证结果还是一个最优解,解没有变坏。那这一步就证明了贪心是安全的。」[稿:269]

「你要证明我贪心地拿了第一步以后,剩下的问题的最优解,加上我贪心的这一步,合起来是整个问题的最优解。」[稿:272]

这两段正好对应 STEP ③ 与归纳——考试必须都写;只写交换不写归纳,最多得一半分。

6. 易错点 + 老师原话

  • 易错 1:写「贪心选最早结束的,显然最优」。「显然」是零分表达([稿:260])。
  • 易错 2:忘了 是全集中最小,误以为只需对当前子问题成立。PPT 定理的表述正是 any nonempty subproblem S_k [PPT:3下:150-151]。
  • 易错 3:只做 ①② 就停,不写 (a) 可行性。
  • 易错 4:把「交换」理解成「把 删掉就行」。必须替换,否则 立不住。
  • 易错 5:以为交换论证能一步证完整个算法。它只证「第一步安全」,剩下靠归纳。

7. 考试英文答题模板

Let be any maximum-size compatible subset, ordered by finish time as , and let be the activity of with the earliest finish time. If we are done; otherwise set . Since has the minimum finish time in , ; and since is compatible, . Hence , so is compatible with and is feasible with , i.e. is also optimal. Thus some optimal solution contains the greedy choice (the choice is safe). Applying the same exchange argument inductively to the remaining subproblem proves the algorithm is optimal.


3.2.3 活动安排(区间调度) 【S 级】

1. 一句话本质

一间教室,让尽可能多的活动办成。谁先结束谁先进——它腾出教室最早,留给别人的余地最大。

2. 定义与性质

PPT 定义 [PPT:3下:94-100]:

Given a set of activities S = { a1, a2, …, an } that wish to use a
resource (e.g. a classroom). Each ai takes place during a time
interval [si, fi). Activities ai and aj are compatible if si ≥ fj or
sj ≥ fi (i.e. their time intervals do not overlap).
Select a maximum-size subset of mutually compatible activities.
Assume: f1 ≤ f2 ≤ … ≤ fn–1 ≤ fn

三点:

  • 半开区间 :端点相接 算相容( 取等成立)。这与 3.2.4 的闭区间相反。
  • 等价值(unit weight):只数个数。这是贪心成立的前提,加权重后到 3.2.9。
  • 目标是最多活动数(cardinality),不是最长总时长。

四条候选规则(PPT [PPT:3下:121-144]):

规则 内容 对错
Rule 1 选开始最早的 ❌
Rule 2 选持续最短的 ❌
Rule 3 选冲突最少的 ❌
Rule 4 选结束最早的 ✅

Rule 4 的直觉 [PPT:3下:142]:Resource become free as soon as possible。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《贪心》 · https://oi-wiki.org/basic/greedy/

给定活动集 , 占用半开区间 。称 与 相容,当且仅当 或 ,即两区间不重叠(端点相接算相容)。目标是在 中选出一个两两相容的子集,使其基数最大。贪心策略:按 升序排序,依次选取与已选集合相容的活动。该策略也称“最早结束优先(earliest finish first)”。

2.2 OI Wiki 校订|基本性质

  1. 半开区间使端点相接算相容: 时两活动可背靠背安排。这是本题与“最少教室”的关键差异,后者按闭区间处理,相接即冲突。
  2. 三条错误规则各有反例:开始最早会被一个超长活动占死;持续最短会错过“短但位置刁”的活动;冲突最少会在冲突图上选错团。
  3. 交换论证:设最优解首个活动为 ,贪心选 (结束最早)。因 ,把 换成 后, 与最优解中其余活动仍相容,个数不变,故存在包含 的最优解。
  4. 复杂度 :排序主导;扫描为线性。
  5. 贪心解必含全局最早结束的活动:这是第 3 条的直接推论,也是判断题常考点。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的例题把区间写成闭区间 ,此时端点相接算冲突,与 PPT 的半开区间差一个等号。答题前必须先声明区间类型,否则“ 是否相容”会答反。OI 的排序键是右端点 ,PPT 是 ,同一含义。另外,PPT 的 Rule 1–3 各配一个反例图,考试若要求“说明为什么错”,只需指出被挤掉的活动即可,不必写完整证明。

3. 图示

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    |--a1--|
       |--a2-|
 |--------a3---|
             |--a4-|
       |----a5---|
             |---a6---|
                |--a7--|
                   |--a8---|
                   |----a9----|
 |-----a10----|
                            |-a11-|
贪心: a1 → a4 → a8 → a11          ✅ 4
Rule1: a3[0,6) → a11[12,16)      ❌ 2

4. 复杂度表

阶段 时间
按 升序排序 ← 主导
单趟扫描(维护 last_finish)
总计 [PPT:3下:162]
输入已有序

对照 DP [PPT:3下:111-118]:,。

5. 典型例题(三个失败反例 + 一个计算)

Example A (Rule 1 fails). Four activities: , , , .

最早开始的是 ()。选 后 全覆盖,只能得 ,size=1;最优 size=3。根因:开始早 ≠ 结束早, 霸占资源 10 个单位。

Example B (Rule 2 fails). , , .

时长 1,最短。选 后与 、 都重叠( 且 ),size=1;最优 size=2(端点相接,相容)。

L₁:[0────────5]L₂:[5────────10]←首尾相接,size=2S:[4.5─5.5]←最短,却同时切断两个

根因:短不蕴含「腾出空间大」; 恰好横跨切点。(整数端点版本:、、。)

Example C (Rule 3 fails). 冲突数不完备。

教科书标准构造(CLRS 16.1-3):、、、。四者冲突数全为 2(并列),任选 后 就锁死 size=1。它说明「冲突数」不含足够信息:冲突数是一个全局统计量,不反映「选它之后剩余子问题的最优值」;而 直接刻画了「资源何时可用」,这正是决定剩余子问题的唯一参数。这个论证比硬凑数值更值分。

Example D (Rule 4, compute). Apply earliest-finish-time greedy to the table above.

  1. 已按 升序:4,5,6,7,9,9,10,11,12,14,16。选 ,last_finish=4。
  2. 扫 :✗ ✗ ✓ → 选 ,last_finish=7。
  3. ✗ ✗ ✗ ✓ → 选 ,last_finish=11。
  4. ✗ ✗ ✓ → 选 ,last_finish=16。
  5. 答案 4,解集 。

6. 易错点 + 老师原话

  • 易错 1:把半开当成闭区间,端点相接误判为冲突。注意 3.2.4 相反。
  • 易错 2:按 排序后还按 二次排序。不需要,一趟线性扫描即可。
  • 易错 3:把「最多活动」和「最长总时长」混淆。目标是 cardinality([稿:296]:「我们很穷,只有一间教室……我要放尽可能多的人进来」)。
  • 易错 4:以为 Rule 1~3 只是「效率差」。不是——它们产出可行解但非最优解,是正确性错误。

7. 考试英文答题模板

Sort activities by finish time ascending and repeatedly select the compatible activity with the earliest finish time, maintaining last_finish; select iff . The greedy phase is and the total is dominated by sorting. The output is a maximum-cardinality subset of mutually compatible activities. Selecting the earliest start, the shortest duration, or the fewest conflicts is incorrect: none of them characterizes when the resource becomes free, which is the only parameter determining the remaining subproblem. Intervals are half-open, so counts as compatible.


3.2.4 最少教室问题 = 最大重叠数 【S 级 · 必考】

1. 一句话本质

换一个问法:不是「哪几个活动能塞进同一间教室」,而是「时间轴上最多有几件活动同时在进行」。那个数就是答案——少开一间,在重叠最多的那一刻就一定有人没座位。

2. 定义与性质

老师的问题陈述([稿:296]):

「我们有无穷多的教室,教室多得很,但是大家要节约。就所有人,都得安排进来……在这个前提下,我们想用尽可能少的教室。」[稿:296]

形式化:给定区间集 ,求最小 使存在 且同色区间两两不重叠。

核心定理:

下界(为什么不能少于 )

设 达到最大重叠,有 个区间 同时覆盖 。它们在 处两两相交,故任意两个不能共处一间教室,必须占用 间互不相同的教室:

([稿:560]:「最大的重叠个数显然是最后整个需要的房间显然应该大于等于最大重叠个数」。)

上界(贪心恰好做到 )

策略二:按 升序处理,把 放进任一已空出的教室;没有则开新教室([稿:299])。

关键引理(老师给出的证明核心,[稿:317]):

「算法只有在一个活动无法放入任何房间时才会新开房间。设此时需要开第 间。当前活动的开始时间为 。因为这个活动不能放进前面的 间房,每一间已有房间中都有一个尚未结束的活动,其结束时间都大于 。所以在当前时刻, 个活动加上当前的活动一共有 个活动同时进行……此时我们开第 间房是不可避免的,而不是浪费。」[稿:317]

严格化:设贪心为 开第 间。前 间各有一个占位者 ,满足 ,且 (按 升序, 先到)。于是每个 都覆盖 :

连同 共 个区间重叠于 ,故 。(UB)

夹逼:。

教师版直觉表述([稿:599],建议在答卷引用):

「把所有的区间全画出来……找一根垂直于水平轴的线,从左往右慢慢推过去……同时发生的俱乐部个数一会儿变大一会儿变小。最优解就是最大的重叠数。你不可能有比它更小的数了——你少用一个教室,你在那个时间点都肯定至少有一个俱乐部进不去。」[稿:599]

图论等价(课堂补充 [稿:338-350]):建区间图,顶点 = 区间、边 = 重叠。可共处一间 = 独立集,最少教室 = 最小着色数 ,最大重叠 = 最大团 。区间图是完美图,。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《图论相关概念》 · https://oi-wiki.org/graph/concept/

把每个区间看作一个顶点,两区间重叠则连边,得到区间图(interval graph)。给顶点染色使相邻顶点异色,最少需要的颜色数记 (色数);两两相邻的最大顶点集的大小记 (团数)。最少教室数就是区间图的色数,任意时刻的最大重叠数就是团数:。区间图是完美图(perfect graph),对它恒有 ,因此最少教室数恰等于最大重叠数。

2.2 OI Wiki 校订|基本性质

  1. 下界 :取达到最大重叠的时刻 ,覆盖它的 个区间两两相交,必须各占一间教室。
  2. 上界 :按开始时间升序处理,仅当无空教室时新开一间。设此时开第 间,则前 间各有一个未结束的占位者,它们在 时刻都覆盖 ,故 。
  3. 夹逼即最优:,三者相等。
  4. 区间类型影响答案:本考点按闭区间处理,端点相接算重叠、不可共处;若改为半开区间,相接可共用一间,答案可能变小。
  5. 与装箱的区别:区间图着色是多项式可解的;一般的一维装箱(bin packing)是 NP-hard,二者不能混为一谈。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 有团数、色数、完美图的定义,但没有“最少教室”这一应用条目;PPT 用“扫一根竖线”的直观讲法给出同一结论。考试按“下界 + 上界 + 夹逼”三步写证明,这是该考点唯一要求的严格论证;只写“等于最大重叠数”会被判为未证明。OI 的图论记号与 PPT 的教室语言可逐项对应:顶点=区间,颜色=教室,团=同时重叠的区间集。

3. 图示

重叠计数:12332321t:01234567a1[──────)a2[────────)a3[──)a4[──────)a5[──────────)↑K=3⇒最少3间

4. 复杂度表

做法 时间 备注
扫描线(事件排序) 推荐,与坐标值域无关
最小堆模拟教室 顺带给出分配方案
线段树区间加 + 全局 max PTA 中 ,可过
逐时刻暴力 ,TLE

扫描线代码(闭区间口径)

c
for each [s, f]:
    ev[m++] = {s, +1};
    ev[m++] = {f, -1};        /* 闭区间:f 时刻仍计入 */
sort(ev, time asc, then delta asc);   /* 同刻 -1 先于 +1 */
cur = K = 0;
for (i = 0; i < m; i++) {
    cur += ev[i].delta;
    if (cur > K) K = cur;
}

排序器里「同刻 先于 」正好实现「端点相接算重叠」。

最小堆代码(能给出方案)

c
sort(a, start asc);
for each a[i]:
    if (heap not empty && min_finish(heap) < a[i].s)   /* 闭区间用严格 < */
        room = pop(heap).room;
    else room = new_room();
    push(heap, {a[i].f, room});

< 与 <= 的分歧就是闭/开口径的分歧——本题唯一的技术陷阱。

5. 典型例题(PTA「胖达的山头」)

Minimum Number of Hills. Given pandas' active intervals (closed, hh:mm:ss), find the minimum number of hills such that no hill ever hosts two pandas at the same instant.

输入(02_胖达的山头.md:34-40):

4
16:30:00 23:00:00
04:50:00 11:25:59
11:25:59 22:00:00
11:26:00 15:45:23

输出:2

中文逐步:

  1. 转秒(,值域 ):
熊猫 秒区间
P1
P2
P3
P4
  1. 看关键端点 (11:25:59)。闭区间 ⇒ 这一瞬间 P2(含右端点)与 P3(含左端点)同时活跃 ⇒ 重叠。而 P4 从 开始,与 P2 无公共点 ⇒ 不重叠。

  2. 逐段重叠数:

时段 活跃集合 重叠
{P2} 1
{P2,P3} 2
{P3,P4} 2
{P3} 1
{P3,P1} 2
{P1} 1
  1. ,答案 2 ✅。

  2. 对应样例解释(02_胖达的山头.md:47-49):山头 A = {P2, P4, P1}(、,均不重叠),山头 B = {P3}。共 2 个 ✅。

为什么这条样例专门卡端点:P2 结束与 P3 开始都写 。若按开区间口径, 只剩 P3,最大重叠仍为 2( 段),答案不变——所以样例本身不区分口径,必须读题干「闭区间」+「任何一个时间点都不能存在超过一只」。最稳妥写法:把闭区间 转成半开 (整数秒),两端统一按半开处理,既不依赖排序平局规则,也不会错。

6. 易错点 + 老师原话

  • 易错 1(闭/开口径):本题闭区间,PPT 活动安排半开,同一份代码不能套用。闭区间下 = 冲突。
  • 易错 2(排序平局):不指定比较器会出现「先加后减 / 先减后加」之差,答案差 1。
  • 易错 3:把答案算成「最多能共享的人数」。问的是间数。
  • 易错 4:误用「先排满一间」(见 3.2.5)。
  • 易错 5:下界证明只写「显然」。必须写出「 个区间两两相交 ⇒ 两两不能同室 ⇒ ≥ 」。

老师原话([稿:569]、[稿:599]):

「我们最重要的那个证明,就是要证明为什么最大重叠数一定等于最后需要的房间个数。」[稿:569]

「你少用一个教室,你在那个时间点都肯定至少有一个俱乐部进不去,这就是这么一个道理。」[稿:599]

7. 考试英文答题模板

Let . Lower bound: at a time achieving , the intervals covering are pairwise intersecting, hence no two can share a classroom, so at least are necessary. Upper bound: process intervals by increasing start time, placing each into any open classroom whose occupant has already finished, opening a new one only if none is free. If the algorithm opens the -th classroom for , each of the previous classrooms holds an interval with , so and all contain ; hence , i.e. opening it is unavoidable. Thus greedy uses at most , and together with the lower bound the minimum equals . A sweep line over the endpoints computes in ; for closed intervals the endpoint order at equal times must be end before start.


3.2.5 反例:复用轮子策略非最优 【S 级】

1. 一句话本质

「既然一间教室的最优排法我会算了,就先排满第一间,剩下的排第二间」——想法非常自然,但它是错的。错在「局部排满」把活动集中到时间轴上,反而抬高了某一时刻的重叠数。

2. 定义与性质

策略一(复用轮子) [稿:296]:

「我既然一间教室的最大安排方案我已经有了,那好,我就开一间教室,我把尽可能多的俱乐部都排在教室里。剩下的,我再开第二间……就是轮子不要重复造。」[稿:296]

形式化:repeat { 在剩余区间上跑按 finish 排序的贪心,得最大相容集 M; M 全部放进第 j 间; 剩余 \= M; }

策略二(非必要不开新教室) [稿:299]:一次只决定一个活动去向,实在没有空教室才新开。

策略一 策略二
决策单位 一次决定一整间的活动集合 一次决定一个活动
优化的量 最大化本间活动数 最小化总间数
目标一致性 ❌ 子目标是局部代理指标 ✅ 每次开新间可证不可避免
结论 非最优 ([稿:296]) 最优 = ([稿:317])

核心洞察:策略一最大化的「一间教室的满载程度」是代理指标(proxy metric),与真正目标(间数最少)不同向。而间数的下界由任意时刻的重叠决定——让活动集中在时间轴上,恰恰抬高了重叠。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《贪心》 · https://oi-wiki.org/basic/greedy/

“复用轮子”策略的形式定义是:反复在剩余区间集上求最大相容子集 ,把 整体放入一间教室,再从剩余集中删去 ,直到集合为空。它优化的子目标是“当前这间教室装多少个”,而不是“总共需要几间”。与之相对,“非必要不开新教室”策略每次只决定一个区间的去向,优化的子目标与总目标同向。前者是代理指标(proxy metric)优化,后者是原目标优化。

2.2 OI Wiki 校订|基本性质

  1. 代理指标与总目标不同向:把活动尽量塞进第一间,会让后续区间在时间轴上更分散还是更集中并不受控,可能抬高某时刻的重叠峰值。
  2. 策略二有下界保证:开第 间时必存在 个区间同时覆盖当前开始时刻,故任何方案都要 间。
  3. 决策粒度决定可证性:一次决定一个区间,才能把“新开一间”与“某时刻重叠 ”对应起来;一次决定一整间则失去这个对应。
  4. “最优子结构”在这里失效:第一间的最优(装最多)不等于全局最优,因为子问题之间通过时间轴耦合。
  5. 正确做法只需 :按开始时间排序 + 维护每间教室的结束时间(最小堆),每次取最早空出的那间。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 没有“复用轮子”这一提法,它是课堂为讲“贪心子目标必须与总目标同向”而设的反面案例。考试若问“为什么不能一间一间地贪”,答“代理指标不同向 + 子问题耦合”即可;若要求给反例,按正文的反例图指出“第一间装得最多反而使峰值重叠升高”即可。

3. 图示 + 反例构造(必背 · 已程序校验)

区间 时间
重叠:  3  3  2  2  1  1
t:     0  1  2  3  4  5
a   [──2)
b   [────3)
c   [──────4)
d         [────────6)
e            [──────5)
        ↑ K = 3

第一步:算 。

时段 重叠集合 数
{} 3
{} 3
{} 3
{} 2
{} 1

,最优需 3 间。

第二步:跑策略一。 按 升序:。

  • 第 1 轮: → 下一个 且 最小 = ( 的 不合格, 的 )→ 下一个 :无。得 ,size=2,教室 1。
  • 第 2 轮:剩余 。按 : → ? ✗;? ✗。得 ,教室 2。
  • 第 3 轮:, 与 重叠 → 各一间。
  • 合计 = 1 + 1 + 1 + 1 = 4 间。

两种策略对比表

教室 策略一(复用轮子) 策略二(非必要不开新)
1
2
3
4 —
总间数 4 ❌ 3 ✅
3 3

策略二的执行过程(按 升序: 均 ,再 、):

  1. :无空教室 → 开 1 号。
  2. :1 号占至 2, 未空 → 开 2 号。
  3. :1 号(2)、2 号(3)都未空 → 开 3 号。
  4. :1 号已空()→ 放 1 号。
  5. :2 号已空()→ 放 2 号。
  6. 3 间 = ✅

失败机理:策略一第 1 轮选了 ——它把 与 绑在一起,而 本可以和 共享()。于是剩下的 两两重叠( 三重叠),被迫各占一间。「填满」选中的是「数量最多」的集合,而不是「与其余部分最互补」的集合。

正确做法是让 带着 走、 带着 走、 独占——这正是策略二「一个活动一个活动地放」自然产出的结果。

教学提醒:这组数据有点「嵌套」味道( 的起点都是 0),看起来像特殊情形。它之所以有效,正因为它让第 1 轮的最大相容集不唯一( 与 都是 size 2),而按 升序恰好选错。这是策略一失效的本质条件:第 1 轮的最大相容集不唯一,且按 排序的 tie-break 指向错的那个。

4. 复杂度表

策略 时间 正确性
策略一(多轮 finish 贪心) (每轮重排序) ❌ 非最优
策略二(最小堆) ✅ 最优

讽刺之处:错的策略还更慢。

5. 典型例题(原题复述)

Classroom-allocation counterexample. Intervals . (i) Compute the maximum overlap . (ii) Run "fill one classroom completely, then recurse". (iii) Compare with the optimum.

(i) ,达到于 ()。(ii) 策略一 = 4 间。(iii) 最优 3 间,多出 。

构造要点(比背答案更重要):反例成立需要两个条件同时满足——

  1. 第 1 轮的最大相容集不唯一(本例 与 都是 size 2);
  2. 按 升序的 tie-break 恰好指向错的那个(,故取 而非 )。

缺一个条件,策略一就会「侥幸」得到 ——这正是它长期被误认为正确的原因。

6. 易错点 + 老师原话

  • 易错 1:以为「分阶段贪心所以也对」。分阶段贪心仍是贪心,仍要证安全选择;这里子目标(本间最多)与总目标(间数最少)不同向。
  • 易错 2:把「多开教室」误说成「不可行解」。策略一永远产出可行解,只是不最优。
  • 易错 3:手算 出错,导致误判。必须先独立算出 ,否则无法判断「多出的教室」是否真超过最优。
  • 易错 4:以为加个 tie-break 就能修好:

「方法二它是关注了一个实时的活动的重叠情况,从而达到理论上的最优。如果要改方法一的话,单纯地去改进它贪心挑选规则是做不到保证最优的;想要改进最优的话,就得舍弃它,贪心就要用其他的方法。」[稿:413]

策略一的失败是框架错位,不是调参能救的。

7. 考试英文答题模板

"Fill one classroom with a maximum compatible subset, then recurse on the rest" is not optimal. Counterexample: , , , , . The maximum overlap is , attained on where overlap, so 3 classrooms suffice. Sorting by finish time gives ; the strategy takes into classroom 1 (a maximum compatible subset, since ), leaving , which pairwise overlap and each require their own classroom — 4 in total. The correct strategy assigns to room 1, to room 2 and to room 3: it opens a new room only when the current interval fits into no existing one, and it achieves exactly . The failure is structural: maximizing the occupancy of one classroom optimizes a proxy unrelated to the objective, and a maximum compatible subset need not be the one that complements the remainder. No refinement of the selection rule repairs this; the strategy must decide one interval at a time.


3.2.6 Huffman 编码:两个引理 【A 级】

1. 一句话本质

想让总位数最少,就要让出现多的用短码、出现少的用长码。满二叉树里「最长的码」永远长在「最深的叶子」上——所以最低频的两个字符必须被压到最深层并做兄弟。这是贪心第 0 步的安全选择。

2. 定义与性质

问题(PPT [PPT:3下:202-224]):给定字符集 与频率 ,求满二叉树,字符只放在叶子上,使

最小, 为叶子深度。

为什么必须满树 + 叶子([PPT:3下:279-283]):

Any sequence of bits can always be decoded unambiguously if the
characters are placed only at the leaves of a full tree – such kind of
code is called prefix code.
  • 前缀码:任何码字都不是另一码字的前缀 ⇒ 可唯一解码。
  • 满树 + 叶子 是前缀码的等价刻画:字符若在内部结点,其码是后代码的前缀,违反前缀性;某内部结点若只有 1 个孩子,把该孩子提上来使所有深度减 1,成本严格下降 ⇒ 最优树必满。

引理 1(贪心选择性质) [PPT:3下:363-367]:

Let C be an alphabet in which each character c ∈ C has frequency
c.freq. Let x and y be two characters in C having the lowest
frequencies. Then there exists an optimal prefix code for C in which
the codewords for x and y have the same length and differ only in the
last bit.

即:存在一棵最优树,其中最低频的 同深度且互为兄弟。

引理 2(最优子结构,详见 3.2.7) [PPT:3下:382-390]:把最低频 合并成 ()得新字母表 ;若 是 的最优树,则把 中 的叶子换成下挂 的内部结点,所得 是 的最优树。

⚠️ 口径纠正:[PPT:3下:363-366] 的 Lemma 原文明确是 lowest frequencies;[稿:287] 的口误说成了「频率最高的两个」。以 Lemma 文字为准:最低频在最低层,最高频在最浅层。考试写 lowest frequency。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《哈夫曼树》 · https://oi-wiki.org/ds/huffman-tree/

给定字符集 及每字符频率 ,构造一棵满二叉树(每个内部结点恰有两个孩子),字符只放在叶子上,使加权路径长度 最小, 为叶深。这样的编码称为前缀码(prefix code):任何码字都不是另一码字的前缀,故解码无歧义。引理 1(贪心选择性质):设 是频率最低的两个字符,则存在一棵最优前缀码树,使 深度相同且互为兄弟。引理 2(最优子结构):把 合并为 ()得 ,若 是 的最优树,则在 中把 换成内结点、下挂 ,所得 是 的最优树。

2.2 OI Wiki 校订|基本性质

  1. 满树 + 叶子 ⇔ 前缀码:字符在内部结点会使其码成为后代码的前缀;内部结点只有一个孩子时,把该孩子提上来可让所有深度减 1,成本严格下降。
  2. 最低频在最低层:引理 1 的直接推论;最高频在最浅层。课堂口误曾说“最高频的两个合并”,以引理原文 lowest frequencies 为准。
  3. 编码不唯一:左右孩子交换、同频字符互换都得到不同的最优编码,但 WPL 相同。
  4. WPL 等于所有内部结点权之和:每个叶子的频率被计入从根到它的每条边对应的内部结点,恰 次。
  5. 贪心的每一步都可复算:每次取权最小的两棵树合并,新权为二者之和;合并 次后只剩一棵树。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 把同一算法讲成“合并果子”:每次合并两堆,代价为两堆之和,使总代价最小——这与 WPL 完全等价,只是叙事不同。OI 的实现用 priority_queue(小根堆),复杂度 ,与 PPT 的 min heap 一致。差异仅在术语:OI 说“带权路径长度”,PPT 写 WPL 或 Cost(T)。

3. 图示

        原最优树 T                    改造后 T'
            root                         root
           /    \                       /    \
        ...    ...                   ...    ...
         |                             |
        a  (深 d_a)                    x  (深 d_a)
        |                             |
        x  (深 d_x > d_a)             y  (深 d_a, 兄弟)

  ① 取最深两叶 a,b(必为兄弟)
  ② 交换标签 x↔a, y↔b(树形不变)
  ③ 因 f(x),f(y) 最小 ⇒ Cost 不增

4. 复杂度表

步骤 时间
heapify
次「取两次最小 + 插入」
总计 [PPT:3下:307]

5. 典型例题(引理 1 的完整交换论证)

Prove Lemma 1: the two least-frequent characters can be assumed to be deepest siblings in some optimal tree.

① 取最优解。 设 为任一最优前缀码树, 为频率最低的两个字符,设 。

② 定位最深叶子。 ⇒ 有内部结点 ⇒ 存在深度 的叶子。取 中最深的两个叶子 。由满树性质,最深叶子必为兄弟(若 的兄弟是内部结点,则其子树还有更深的叶子,矛盾于 最深)。故 ,于是对任意 : 且 。

③ 交换并证明不变差。 令 为在 中交换字符标签 、 所得(树形不变 ⇒ 仍是合法前缀码树)。代价差:

逐项判号: 且 ⇒ 第一项 (负负得正);同理第二项 。故 。又 最优 ⇒ 。合并得 , 也最优。

④ 收尾。 在 中 位于深度 的两个兄弟叶子,深度相同、码字仅末位不同。

一句话记忆:深度的差必须由频率的差补偿。因 频率最小,把它们挪到最深只可能减少成本。PPT 的结论行写 Cost(T') ≤ Cost(T) [PPT:3下:375]——是 而非 ,因为允许并列。

6. 易错点 + 老师原话

  • 易错 1:写成「频率最高的两个在最底层」。方向反了。
  • 易错 2:漏掉「满树」前提,「最深叶子必为兄弟」就崩了。
  • 易错 3:只换频率不换位置,或只换位置不换标签。正确做法是交换标签(树形不变),否则其他叶子深度会变。
  • 易错 4:认为引理保证「任何最优树都以 为最深兄弟」。只保证存在——这正是安全选择的定义。

老师原话([稿:287]、[稿:290]):

「我们仍然有可能用其他的算法,或者我人工硬凑,凑出另外一组解来,x 和 y 它不在最下面,但是整棵树是最优的。哈夫曼第一步要证明的是什么?即使你有这样一棵最优树、x y 不在最下面,我可以把它俩换到最下面。」[稿:287]

「他要严格地证明说,这棵树的权重不会比这棵树的权重更大。因为这是最优解,而它小于等于最优解,所以它肯定也是个最优解……即使你最优解不是用贪心得到的,我把贪心解换进去,我仍然保证我没有破坏你的最优解。」[稿:290]

7. 考试英文答题模板

Lemma (greedy choice). Let have the two smallest frequencies in . Then some optimal prefix code tree has as siblings of maximum depth. Proof. Let be optimal; since an optimal tree is full, its two deepest leaves are siblings at depth . Exchange labels , to obtain with the same shape. Then

since , and . As is optimal, , so is optimal and contains as deepest siblings.


3.2.7 Huffman 交换论证(引理 2,最优子结构) 【A 级】

1. 一句话本质

引理 1 保证「第一步合并最低频的两个」是安全的;引理 2 保证「合并完以后剩下的问题还是原问题」。两个合起来,贪心的「贪心选择性质 + 最优子结构」就齐了,归纳一跑,Huffman 正确。

2. 定义与性质

先把引理 2 的代数内核写出来(这是证明里唯一需要动脑的等式):

设 是 的一棵前缀码树, 且 。把 中 的叶子换成内部结点、下挂 两个叶子,得到 的树 。则

推导:在 中,深度 ,贡献 ;在 中, 深度都是 ,贡献 。两者之差恰为 。其他叶子深度、贡献完全不变。

这个等式是整个证明的支点:它说明「 上的最小代价」与「 上的最小代价」只差一个常数 。于是最小化 ⟺ 最小化 ——两者的最优解一一对应。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《哈夫曼树》 · https://oi-wiki.org/ds/huffman-tree/

引理 2 的代数内核可以用一个等式概括:设 是字母表 的一棵前缀码树,;把 中 的叶子替换为“内结点 + 两个孩子 ”,得到 的树 。则

推导: 在 中深度为 ,贡献 ; 在 中深度为 ,贡献 。其余叶子深度不变,故差值恰为 。

2.2 OI Wiki 校订|基本性质

  1. 常数差蕴含最优对应: 与 只差常数 ,故最小化其一等价于最小化另一,最优解一一对应。
  2. 规模严格递减:,故对 归纳可证算法正确,基例 时树只有一个叶子,WPL 为 0。
  3. 总合并次数 :每次合并消去一个字符,从 到 1 恰需 次。
  4. 可复算实例:频率 ,合并顺序 、、、、,WPL 。
  5. 引理 1 与引理 2 缺一不可:引理 1 保证“最低频可以放在最低层”,引理 2 保证“合并后问题同型”,二者合起来才是完整的贪心正确性证明。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 以“合并代价之和最小”叙述,直接给出 合并代价 WPL,与 PPT 的引理 2 等价但省略了“存在最优树使 为兄弟”的存在性论证。考试若要求证明正确性,必须两条引理都写;只写“每次合并最小的两个”属于陈述算法,不是证明。另外,引理 2 的归纳基例是 (单叶树,代价 0)与 (两叶同深,代价 ),缺基例的归纳同样不完整;合并过程每步减少一个字符,故归纳必然终止。这两条合起来也解释了为什么“把最低频放在最深层”与“合并后问题同型”必须分开陈述。

3. 图示

C'上的树T'C上的树T(把z的叶子展开)rootroot/\/\............||[z]←叶子,d'=k[z]←内部结点f_z=f_x+f_y/\xy深度k+1f_xf_yCost(T)=Cost(T')+f_x+f_y────────────────────────"只差一个常数"⇒最优性同向平移

PPT 的示意 [PPT:3下:391-397]:

              T'                              T
                   a                              a
         b    z                  b
                    optimal                  x       y
                                                optimal
   By contradiction.

4. 复杂度表

引理 作用 证明工具
引理 1(3.2.6) 贪心选择性质(safety) 交换论证(直接构造 )
引理 2(3.2.7) 最优子结构 反证法 + 常数差等式
两者合并 + 归纳 Huffman 全局最优 —

5. 典型例题(引理 2 的完整反证)

Prove Lemma 2: if is an optimal prefix code tree for with , then the tree obtained by expanding the leaf into an internal node with children is an optimal prefix code tree for .

中文逐步(反证法):

① 反设。 设 不是 的最优树。那么存在 的某棵前缀码树 使

② 归一化 。 由引理 1(它只说「存在」,已证),我们可以不失一般性地假设 中 是深度最大的兄弟叶子。(若不然,用引理 1 的交换把 换成 ,,矛盾更强。这里体现「引理 1 可重复使用」。)

③ 收缩 得到 的树。 在 中,把 的父结点「折叠」成叶子 (删除 两叶,把父结点标记为 ,其频率取 )。得到 的合法前缀码树,记作 。

④ 用常数差等式。 由 3.2.7 的等式(同一论证对 成立):

同理对 (由 展开):

⑤ 推出矛盾。 代入 (1):

这与「 是 的最优树」直接矛盾。故反设不成立, 是 的最优树。

⑥ 另一方向(完备性)。 还要证「 最优 ⇒ 最优」,否则归纳时无法保证子问题取到最优。反设 非最优,则存在 使 。展开得 ,则 ,与 最优矛盾。

注意第 ⑥ 步(双向性):PPT By contradiction 只有一行 [PPT:3下:398],但考试要写全双向——只证一个方向不够(需要「 与 之差是常数」这个双向映射)。

完整归纳串起来(答题时的「总装」):

  1. 若 ,Huffman 直接合并,显然最优(唯一可能的两叶树)。
  2. 归纳假设: 时 Huffman 返回 的最优树 。
  3. 由引理 1,存在 的最优树 以最低频 为最深兄弟。
  4. 把 的 折叠成 ,得 的树 。由引理 2 的双向性, 是 的最优树。
  5. 由归纳假设,(Huffman 在 上的输出)满足 。
  6. Huffman 在 上返回的正是「 展开 」得到的 ,故 。
  7. 所以 是最优树。

6. 易错点 + 老师原话

  • 易错 1:漏掉常数差等式,直接说「 与 一一对应,所以最优性传递」。必须写出 ,否则「传递」是空话。
  • 易错 2:反证时假设 中 不是最深兄弟,就无法折叠成 。必须先用引理 1 归一化(第 ② 步)。
  • 易错 3:只证一个方向。见第 ⑥ 步。
  • 易错 4:把引理 2 当成「引理 1 的推论」。两者独立:引理 1 是结构性的(存在最优树形状),引理 2 是递推性的(子问题等价)。

老师原话([稿:284-293]),这是本节的核心引用:

「算法他把算法想出来了以后,你会觉得这算法超简单,代码也不长,它到底难在哪儿?就难在证明上面。」[稿:284]

「第二步它就要证明什么?证明这个事情:我把两个最高频率的合成了一棵子树以后,就变成了这棵子树,合成一个新的节点 z,然后这个 z 就要跟剩下的那些节点去合并。它 z 跟剩下的节点合并出来了一个最优的子树;把最优子树里这个 z 再换回到 xy 以后,那么它对应的是整个原始问题的最优树。」[稿:290]

「它有最优子结构。我先解决了眼前一步,然后再去递归地解决剩下的问题。剩下的问题是最优解的话,那么放回来跟我第一步合在一起,它是整个问题的最优解。于是证明了哈夫曼算法是正确的。难度不在算法本身,难度在它证明,非常值钱。」[稿:293]

最后一句直接对应考点表 3.2.7 的备注。

7. 考试英文答题模板

Lemma (optimal substructure). Let be a new character with and . If is an optimal tree for , then the tree obtained by replacing the leaf with an internal node whose children are and is optimal for . Proof. For any tree of and its expansion , , because expanding increases the depth of exactly and by one. Suppose is not optimal for ; let be better. Using Lemma 1 we may assume has as deepest siblings; contracting them gives a tree for with . Then implies , contradicting the optimality of . Conversely, a better would expand to a better , contradicting optimality of . Hence the two problems have the same optima, and Huffman's recursive merge is correct by induction.


3.2.8 Huffman 构造与 WPL 计算 【A 级】

1. 一句话本质

把 个字符各看作一棵单结点树扔进最小堆;每次弹出两个最小的合并成新结点(权 = 两者之和)再塞回去;重复 次,就剩一棵树。编码时左 0 右 1。WPL = 。

2. 定义与性质

算法(PPT 原文 [PPT:3下:290-303]):

void Huffman ( PriorityQueue heap[ ], int C )
{ consider the C characters as C single node binary trees,
  and initialize them into a min heap;
  for ( i = 1; i < C; i++ ) {
    create a new node;
    /* be greedy here */
    delete root from min heap and attach it to left_child of node;
    delete root from min heap and attach it to right_child of node;
    weight of node = sum of weights of its children;
    /* weight of a tree = sum of the frequencies of its leaves */
    insert node into min heap;
  }
}

四个要点:

  1. 最小堆(min heap),不是最大堆。
  2. 循环恰好 次(合并 个叶子需要 次内部结点)。
  3. 新结点的权 = 两孩子权之和;注释点明 weight of a tree = sum of the frequencies of its leaves——这个量在计算 WPL 时会被反复用到(见下「两条恒等式」)。
  4. 左/右赋 0/1 是任意的,不影响 WPL;导致同一组频率可能有多组等价最优编码(不唯一性)。

两条恒等式(手算加速器)

  • (I) WPL = 所有内部结点权之和。 因为 ,其中 是 的子树叶子频率之和。证明:每个叶子 的贡献 被计入从根到 的每条边对应的内部结点,恰 次。
  • (II) 合并代价累加 = WPL。 每次合并产生的新权 ,把所有 次合并的 相加,恰等于 (I) 的和。这条让 WPL 手算变成「连加 个数」,几乎不出错。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《哈夫曼树》 · https://oi-wiki.org/ds/huffman-tree/

Huffman 构造:把 个字符各看作单结点树放入小根堆;重复 次:取出权最小的两棵树 ,新建结点 ,令 的左右孩子为 、权 ,把 放回堆。最后堆中唯一的树即最优前缀码树。编码时向左走记 0、向右走记 1(可任意约定),叶子的路径即码字。

2.2 OI Wiki 校订|基本性质

  1. 必须用小根堆:每步取最小两个权,用大根堆会得到完全不同的(且通常更差的)树。
  2. 复杂度 : 次取出/插入,每次 ;若频率已排序,可用两队列做到 。
  3. 左右赋值不影响 WPL:交换任一内部结点的两个孩子,所有叶深不变。
  4. 两条恒等式:,使手算退化为连加。
  5. 退化情形:频率呈 Fibonacci 增长时,树退化为链,最大深度 ,此时前缀码仍需 位,说明 Huffman 只保证在给定频率下最优,不保证深度对数级。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的“合并果子”版本直接用总和计费,不显式建树;PPT 要求画出树并标 0/1,因为考试常考“某字符的编码”。两者结果一致。注意 PPT 例题的频率集合 的 WPL 为 224,可用“内部结点权和”验算:。若考试要求写出每个字符的编码,注意同一频率的字符可互换左右,答案不唯一,但码长分配唯一;阅卷只核对码长与 WPL,不核对具体 0/1 串。最后,建树过程中新结点的权只用于比较,不参与最终编码,理解这一点可避免把内部权误当作码长。

3. 图示(PPT 例,完整手算)

频率表 [PPT:3下:312-313]:

字符 a e i s t sp nl
freq 10 15 12 3 4 13 1

合并过程(按最小堆顺序):

步 弹出两个最小 新权 堆中内容(升序)
0 — — 1, 3, 4, 10, 12, 13, 15
1 nl(1), s(3) 4 4, 4, 10, 12, 13, 15
2 t(4), *(4) 8 8, 10, 12, 13, 15
3 *(8), a(10) 18 12, 13, 15, 18
4 i(12), sp(13) 25 15, 18, 25
5 e(15), *(18) 33 25, 33
6 (25), (33) 58 58

WPL = 4 + 8 + 18 + 25 + 33 + 58 = 146 ✅ 与 PPT Cost = 146 吻合 [PPT:3下:347-357]。

(PPT 的展开式:,两种算法一致。)

建树与编码(按第 1 步 nl 左、s 右;第 2 步 t 左、合并结点右;……左 0 右 1):

                              58
                         0 /      \ 1
                        25         33
                    0 /   \ 1   0/     \1
                   i(12) sp(13) 15     18
                                e     0/  \1
                                      8    a(10)
                                   0/  \1
                                  t(4)  4
                                      0/  \1
                                     nl(1) s(3)

码长:(58→25→18→a?按上图 a 深度 3)、、、、、、。

核对: ✅

PPT 给出的码字([PPT:3下:315-344],不同左右约定,码字不同但长度相同):i:00, t:1100? , e:10?, sp:111?, a:01, nl:11010, s:...。考试只需保证 (a) 前缀码 (b) 长度分布与合并过程一致 (c) WPL 对,具体 0/1 串可任选。

4. 复杂度表

实现 时间 空间
最小堆(标准)
频率已排序 + 两队列法
朴素线性扫描找最小两次

[PPT:3下:307]:T = O( C log C )。

编码/解码总代价:建表 + 编码 + 解码 , 为原文长度。压缩率对比固定长度码:固定码需 位/字符,Huffman 平均码长 。

5. 典型例题

Huffman coding. Given characters with frequencies (from the string aaaxuaxz), construct an optimal prefix code and compute the WPL. Also compare with the fixed-length code.

中文逐步:

  1. 堆:1(z), 1(u), 2(x), 4(a)。
  2. 合并 → 2;堆:2, 2(x), 4(a)。
  3. 合并 (新)与 → 4;堆:4(a), 4。
  4. 合并 与 → 8。
  5. WPL = 2 + 4 + 8 = 14 ✅ 与 PPT Cost = 14 一致 [PPT:3下:257-273]。

编码(一步:新 2 左 = z 或 u,右 = x;新 4 左 = a,右 = 新2):

              8
          0/     \1
        a(4)      4
                0/  \1
              *(2)   x(2)
            0/  \1
          z(1) u(1)

码长:,,。WPL ✅

对比固定长度码:4 个字符需 2 位/字符,原文 8 个字符需 位。Huffman 省 2 位(12.5%)。PPT 的对照 [PPT:3下:250-273] 给了同样的 16 vs 14。

原文 aaaxuaxz 编码(按 a=0, x=10, z=110, u=111):0 0 0 10 111 0 10 110 = 00010111010110,共 14 位 ✅。

6. 易错点 + 老师原话

  • 易错 1:把最小堆写成最大堆。必须最小。
  • 易错 2:循环写成 次(应 次),或忘了「新结点回插堆」。
  • 易错 3:WPL 只算叶子不乘深度,或漏掉某个字符。
  • 易错 4:以为 Huffman 编码唯一。不唯一——左右可换,等频可换,导致不同码字、相同 WPL。考题若问「最优编码有多少种」需按等频计数。
  • 易错 5:所有频率相同时期待压缩。PPT 明确警告 [PPT:3下:226-243]:

Note: If all the characters occur with the same frequency, then there are not likely to be any savings.

此时 Huffman 退化成长度均匀的树,,与固定码无差。

  • 易错 6:拿「频率最高」当合并对象(受 [稿:287] 口误影响)。永远合并频率最小的两个。

7. 考试英文答题模板

Initialize a min-heap with one single-node tree per character, keyed by frequency. Repeat times: extract the two trees with the smallest weights, create a new internal node whose weight is the sum of theirs, attach them as left and right children, and reinsert the new node. The final tree defines the code (left edge = 0, right edge = 1). The cost is , which equals the sum of the weights of all internal nodes, equivalently the sum of the merge weights. With a binary heap the running time is . The code is a prefix code because all characters sit at leaves of a full binary tree, so decoding is unambiguous. The code is not unique (left/right and equal-frequency ties are arbitrary), but every optimal code produced has the same WPL. When all frequencies are equal there is essentially no compression gain.


3.2.9 贪心 vs DP 的边界 【S 级 · 判断】

1. 一句话本质

每个活动值 1 块钱时,「结束最早」这个规则恰好等价于「剩余可安排的活动数最多」,贪心对。一旦给每个活动贴上不同的权重,「结束最早」就不再等于「剩余总价值最大」——贪心失效,只能 DP。

2. 定义与性质

加权活动安排(Weighted Interval Scheduling):每个活动 有权 ,目标不是最大化个数,而是最大化 。

PPT 把两个问题并排放 [PPT:3下:164-179]:

               c1,j = 1                                  if j = 1
               c1,j = max{ c1,j-1 , c1,k(j) + 1 }        if j > 1

If each activity has a weight …

               c1,j = 1                                  if j = 1
               c1,j = max{ c1,j-1 , c1,k(j) + w_j }      if j > 1

   Q1: Is the DP solution still correct?
   Q2: Is the Greedy solution still correct?

答案:Q1 = YES,Q2 = NO。

  • Q1 的 DP 仍正确:把 换成 不改变递推的结构(仍然只有「选 」与「不选 」两个分支,仍然是最优子结构),所以 ( 用二分)或 的 DP 依然对。
  • Q2 的贪心失效:见下反例。

判据(一句话,考试直接写):

贪心成立的前提是「眼前局部最优」与「全局最优」同向。等权时,目标函数对元素个数是线性的、单调的,finish time 早 ⟺ 剩余时间窗口大 ⟹ 剩余可容活动数多,二者同向。加权后,目标变成了「剩余窗口内可容纳的总权重最大」,而「窗口大」不保证「窗口内总权重大」——窗口里可能只躺着一堆小权重活动。同向性破裂,贪心失效。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《贪心》 · https://oi-wiki.org/basic/greedy/

贪心与 DP 的分界可以用一句话形式化:贪心要求目标函数对“局部选择”单调同向,即把当前最优的一步换进任一最优解后目标不变差;DP 只要求最优子结构,允许在多个子问题间比较取优。加权活动安排中,目标从“活动个数”变为“权重和”,局部判据“结束早”不再蕴含“剩余可容纳权重大”,同向性破裂,故贪心失效而 DP 仍成立。

2.2 OI Wiki 校订|基本性质

  1. 等权时同向、加权后破裂:个数对选择是线性的;权重和不是,窗口大不等于窗口内总权重大。
  2. DP 递推只需换一项: 改为 即可,结构不变,故 DP 仍正确。
  3. 贪心失效反例:一个大权重长活动与若干小权重短活动竞争时,最早结束优先会选小的,总权重远低于最优。
  4. 一般降级路径:贪心证不出(找不到交换论证中的 )时,退回 DP;这也是 PPT 名言“每个贪心背后几乎总有一个更笨重的 DP 解”的含义。
  5. 判定顺序:先看能否写成“一步选择 + 单子问题”,再看目标是否与局部判据同向,最后才写交换论证。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的《贪心》页另给出“反悔贪心”(用堆支持撤回),能在部分加权问题上逼近最优,但它不是经典贪心,也不保证最优;PPT 不涉及。考试按 PPT 的结论作答:加权活动安排只能 DP,贪心失效,并给出权重反例。

3. 图示 + 反例

时间轴→A:[0───────────────10)w=100←结束最晚B:[1──2)w=50C:[3──4)w=50D:[5──6)w=50E:[7──8)w=50
  • 等权(每个 ):贪心选最早结束的 → 再接 ,得 ,size=4;最优也是 4。✅
  • 加权(,其余 50):贪心仍选 ( 最小),得 ,总权 ;最优是 ,总权 100?

这是反例吗? ,贪心反而更好 ❌。需要调整:让 的权重压过其余之和。

改:,。贪心得 ;最优 得 300。贪心 200 < 最优 300 ✅ 反例成立。

更简洁的标准反例(3 个活动,必背)

活动 区间 权重
10
3
3

按 finish 升序:。

  • 贪心:选 → 下一个 : ✅ → 选。得 ,总权 。
  • 最优:,总权 10。

6 < 10,贪心失效。 而 结束得并不晚( 与 并列),但它开始得早,占住了整段,把两个 3 都挤掉了。如果只按权重贪心(先选最大权 ):得 ,10,本例对但另例错(见下)。

「只有权重、不看时间」也错:

活动 区间 权重
5
4
4
4
4

按权重贪心选 (5)→ 总权 5;最优 总权 16。贪心 5 << 16。

结论:加权后,任何「按单一标量排序」的贪心都失效,因为没有哪个标量同时编码了「时间占用」和「价值」。

DP 正确写法(必须会):

设活动按 升序编号 。(在 开始前结束的最晚活动,可用二分找)。则

时定义 。答案 。时间 。

PPT 的二维形式 [PPT:3下:166-176] 用 表示「 上的最优」,与上式同义()。

与 0-1 背包的关系:加权活动安排是退化版 0-1 背包——每个活动要么选要么不选(选 = 重量 0),约束是「时间不重叠」。它的 DP 是 1 维的(因为 唯一确定前驱),比背包更简单。这也说明:问题一旦带上权重,即使结构看似简单,也要先想 DP 而不是贪心。

4. 复杂度表

版本 贪心 DP
等权(max count) ✅ 最优 或
加权(max total weight) ❌ 无已知正确贪心 ✅ 最优( 二分)
加权 + 允许拆分活动 — 分数版可贪(按 排序,类似分数背包)

注意最后一行:加了权之后并非所有问题都只能 DP。0-1 背包不能贪,但分数背包(fractional knapsack)能按 贪心——因为可拆分的性质让贪心选择安全(线性松弛)。判据永远是「贪心选择是否安全」,不是「有没有权重」。

5. 典型例题(课堂测验原题)

Q1: Is the DP solution still correct if each activity has a weight? Q2: Is the greedy solution still correct? [PPT:3下:178-179]

中文讲解:

  • Q1:Yes. 递推式由 改为 ,只替换了「选 的收益」,最优子结构与重叠子问题的论证不受影响(子问题仍是「 上最优」)。答案 。
  • Q2:No. 反例见上( 权 10 vs 权 6)。

这道题的陷阱正是 [稿:275] 说的「那个公式其实是错的」:

「如果这些俱乐部它有的重要,有的不重要,我给他活动还定了个性。这个时候我要求说进来的俱乐部的总权重是最大的……这种时候贪心就不行了,你就只能用 DP 了。那我今天给大家测验题里头,我还截了这张图,问大家,好像大部分同学都没有掉到坑里,这是个坑,这个公式其实是错的,这个地方不能是一,这个地方得是权重,是第一个俱乐部的权重放在这。」[稿:275]

即 而非 ; 而非 。

6. 易错点 + 老师原话

  • 易错 1:以为「有权重就一定不能贪心」。分数背包是可贪的反例。判据是安全选择,不是权重有无。
  • 易错 2:以为「贪心偶尔对了」就能推广。加权活动安排上,很多实例贪心确实对(例如所有权重相等、或权重随 finish 递增),但只要存在一个反例,算法就是错的。
  • 易错 3:DP 初值写错( 而非 ),这正是测验的坑。
  • 易错 4:忘了 的定义域—— 是「」(半开区间相容)而不是 。与 3.2.3 的口径一致。
  • 易错 5:把「DP 比贪心慢」当成「DP 不好」。老师的态度很务实([稿:242]):

「贪心运气不好的话,它就只能退回到动态规划。」[稿:242]

即:DP 是保底方案(Beneath every greedy algorithm, there is almost always a more cumbersome DP solution [PPT:3下:195-196])。

7. 考试英文答题模板

With unit weights the earliest-finish-time greedy is optimal. Adding a weight to each activity breaks the greedy-choice property: the DP recurrence remains correct when is replaced by , i.e. with and , which is computable in after sorting by finish time. But the greedy fails: for with , and with each, the greedy picks for a total weight of 6, while alone achieves 10. The general criterion is not "weights present" but whether the greedy choice is safe: it must hold that the locally best choice can always be extended to a globally optimal solution. When the objective is a linear functional over a set with interval-conflict constraints and activities are indivisible, no single scalar ordering encodes both time occupancy and value, so DP is required. (Fractional versions, such as fractional knapsack, remain greedily solvable.)


附录 A:本讲一页速览

考点 一句话 关键数字/公式 证明工具
3.2.1 局部最优 = 全局最优时要证 贪心选择性质 + 最优子结构 —
3.2.2 取最优解 → 找分歧 → 换 → 不变差 三段式 交换论证
3.2.3 按 finish 升序,能放就放 ;半开区间 交换论证
3.2.4 教室数 = 最大重叠数 ; 扫描线 下界 + 上界夹逼
3.2.5 先排满一间 ≠ 最优 反例 :4 vs 3 反例
3.2.6 最低频两字符可做最深层兄弟 交换论证
3.2.7 合并后子问题等价 反证法(双向)
3.2.8 每次取两个最小合并 ;WPL = 内部结点权之和 构造
3.2.9 加权后贪心失效 反例 + 判据

三个口径,不能混:

问题 区间 端点相接
活动安排(PPT) 半开 相容
DP 的 相容
最少教室(PTA 胖达) 闭 冲突

WPL 两条恒等式:


附录 B:关键证据索引

内容 位置
优化问题 / 贪心方法定义;local = global 警告 [PPT:3下:10-19]、[PPT:3下:30-46]
活动安排定义(半开区间)、四条规则 [PPT:3下:94-100]、[PPT:3下:121-144]
活动安排定理与证明; [PPT:3下:147-158]、[PPT:3下:162]
贪心三要素;"背后都有傻傻的 DP" [PPT:3下:183-196]
前缀码 / 满树;Huffman 算法 [PPT:3下:279-283]、[PPT:3下:290-307]
Huffman 例(WPL=146) [PPT:3下:312-357]
引理 1、引理 2 [PPT:3下:363-375]、[PPT:3下:382-398]
加权后 DP 两问(Q1 是 / Q2 否) [PPT:3下:164-179]
local/global 定位;heuristic 不可分析 [稿:242]、[稿:245-248]
「举不出反例 ≠ 正确」;先可行后最优 [稿:260]、[稿:263]
交换论证 + 归纳 [稿:266-272]
加权只能 DP(公式里不能填 1) [稿:275]
Huffman「难在证明上」;两引理的交换 [稿:284-293]
最少教室两策略 [稿:296-299]、[稿:317]
区间图 / 最大团;线段树求最大重叠 [稿:338-365]、[稿:368-371]
「改贪心挑选规则救不了策略一」 [稿:413]
下界 ;上界 ;扫描线直觉 [稿:560]、[稿:569-596]、[稿:599]
交换论证 / 安全选择;两策略对比 [纪要:3:29-30]、[纪要:3:33-35]
PTA 胖达的山头(闭区间样例) 02_胖达的山头.md:34-49
4.1.1 – 4.1.12

详解 07 · NP 完全性

配套 A0_速查册.md 的「4 上」部分。速查册给结论,本册给推导、图示与证明全过程。 证据约定:[PPT:4上:n] = 01_原始素材/PPT_文本/ADS/第四讲(上)NP-Completeness.txt 行号;[稿:4-逐字稿:n] = 课堂录音文稿/4-逐字稿.md 行号;[纪要:4-conclusion:§] = 课堂录音文稿/4-conclusion.md 小节。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

"容易vs难"的分界线=polynomialtime→四个类P/NP/NPC/NP-hard,靠"归约"串起来(4.1.1–4.1.4)└归约方向A≤pB⇒B更难(全讲最高危错点)(4.1.3)→但归约只在判定问题上谈(优化→判定,引入K)(4.1.5–4.1.6)→比可判定更难的:undecidable(停机问题)(4.1.7)→归约链:SAT→3-SAT→Clique→VertexCover/Ham/TSP(4.1.8–4.1.10)→反例教学:最短路P,最长简单路NPC(只差一个约束)(4.1.11)→全部收拢成:NPC证明三步法(必考证明题)(4.1.12)

老师对本讲的定位只有一句话:「我们先说区分容易问题和难问题的一条线……np completeness 是 the hardest,是最难的一类问题。它有限制范围,它是所有 np 问题里面 the hardest。」 [稿:4-逐字稿:17] 换句话说,NPC 的 "hardest" 是带限定语的——它是 NP 里面 最难,不是所有问题里最难;真正的天花板是 undecidable。这一句限定了整个上半讲的世界观,也是 4.1.1 和 4.1.2 的判分依据。


4.1.1 四个类与包含关系 【S 级 · 选择/判断必考】

1. 一句话本质

P 是"能快算",NP 是"能快验",NPC 是"NP 里最快的验也算不快、且所有 NP 都压到它身上",NP-hard 是"所有 NP 都压到它身上,但它自己可以连验都验不了"。

2. 定义与性质

严格定义(推荐直接背英文,考选择/判断就是抠字眼):

类 严格定义 关键词
P 一个算法 ,在多项式时间内 decides decide(判 Yes 也判 No)
NP 两输入多项式算法 与常数 ,使 verify + certificate
NPC (1) 且 (2) 对每个 有 两个条件缺一不可
NP-hard NP-hard 对每个 有 只有 (2),没有 (1)

定义出处:P 的 language 形式 [PPT:4上:311-312];NP 的 certificate 形式 [PPT:4上:329-334];NPC 的两条 [PPT:4上:370-372]。

三点必须掰清:

  • P 要求 decide,NP 只要求 verify。 这是 P ⊆ NP 的唯一来由:能 decide 的算法显然也能当 verifier 用(忽略 certificate 直接跑)。所以 P ⊆ NP 是定理,不是猜想;而 P ⊊ NP 是猜想(PP T 上的 P ⊆ NP ? 打了问号 [PPT:4上:192])。
  • NPC 是"NP ∩"出来的。 NPC 的两个条件正好等于 "∈NP" 与 "∈NP-hard",所以 NPC = NP ∩ NP-hard,这是集合等式而非口号。
  • co-NP 是补集类: [PPT:4上:339-341],即"能快验 No"的问题。PP T 给了四种可能世界的图 [PPT:4上:343-353],考试只考一句:P = NP 与 P = NP ∩ co-NP 都还未知。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 从「语言」出发给出同一组类。字母表 上的语言是 的子集;判定问题就是指示函数 ,,故「语言」与「判定问题」可视为同义词(下文取 )。在此基础上,,,其中 是确定性图灵机在 内停机所判定的语言类, 换成非确定性图灵机。NP-hard 的定义是「所有 类问题都可在多项式时间内规约到 」;NP-complete 是「既是 类问题又是 NP-hard」,即 。补类 ;若 却既不在 也不是 NPC,则称 NP-intermediate。

2.2 OI Wiki 校订|基本性质

  1. :能判定(任何输入都停机并给正确答案)当然能识别(只在 时停机接受)。包含是严格的,停机问题就在 中。
  2. 是定义级包含:确定性图灵机是转移函数单值的非确定图灵机,故 ,取并即得。例: 时两者都含 、 等平凡语言。
  3. 验证机定义与 NTM 定义等价:证书 对应 NTM 的猜测串,验证算法对应随后的确定性校验; 对应多项式步数。例:SAT 的证书是一个 位赋值,验证只需逐句代入,。
  4. NPC 的功能版是 NP-hard 而非 NPC:语言类只收判定问题。例:最大团(输出团的大小)NP-hard,但「最大团」不是 NPC;判定版 CLIQUE 才是。
  5. Ladner 定理:若 ,则 NP-intermediate 非空;图同构、离散对数、整数分解被认为可能属此类。所以「NP 中除 P 外全是 NPC」是错的。
  6. 已知的真包含而非猜想:时间谱系定理给出 ,非确定版给出 。这与 vs 的未决地位不同,答题时不可混为一谈。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 用非确定图灵机定义 NP,PPT 用验证算法 + 证书定义。两者等价,但答卷必须按 PPT 的 certificate 写法,否则「NP 是非确定多项式时间」这句话无法直接回应「证书是什么」。术语上 OI 写「规约」、PPT 写「归约」,英文同为 reduction,不影响作答。OI 另收了 、NP-intermediate、、 等,PPT 只讲到 co-NP:考试不会要求谱系定理,但若判断题说「NP 中不属于 P 的问题都是 NPC」,应判错并援引 Ladner 定理。

3. 图示(四类包含关系)

┌───────────────────────────────────────────┐│所有问题(languages)││││┌──────────────────────┐│││NP-hard││││(所有NP≤p它)│││┌────┼──────┐│││││NPC││││││=NP∩│││││NP│NP-hard│││││┌──┴───┬──┴──┐│││││┌──┴──┐│││││││P│││││││└─────┘││││└─┴───────────┴──┘││││││Undecidable(如Halting)─────┘也是NP-hard│└───────────────────────────────────────────┘要点:①P完整地包在NP里面②NPC恰好是NP与NP-hard的交集(画在NP内部)③NP-hard的右边界伸出NP之外(停机问题就在这块)注意:P⊊NP?未知;NP⊊NP-hard?未知。图只画"包含",不画"真包含"。

4. 复杂度表

类 判定耗时 验证耗时 停不下来的情形
P 多项式 多项式 —
NP \ NPC 未知 多项式 —
NPC 未知(猜想无多项式) 多项式 —
NP-hard \ NP(如 Halting) 未知 不存在 可能永远跑不完
Undecidable 不存在 不存在 一定存在无解实例

5. 典型例题

Which of the following is TRUE? (A) P ⊆ NP; (B) NP ⊆ P; (C) NPC ⊆ P; (D) NP-hard ⊆ NP.

中文逐步:

  1. 由定义,decide 蕴含 verify ⇒ (A) 真。
  2. (B) 是 P = NP 猜想,不能作为定理陈述 ⇒ 假(除非假设)。
  3. (C) 若 NPC ⊆ P,则 P = NP ⇒ 也是未证命题 ⇒ 假。
  4. (D) 停机问题 NP-hard 但不可判定,更不在 NP ⇒ 假。
  5. 答案 (A)。同一道题若改成 "NP-hard 是否属于 NP",答案永远是 不一定(见 4.1.2)。

6. 易错点 + 老师原话

  • 易错:把 NPC 说成"所有问题里最难的"。老师当场纠偏:「它是所有 np 问题里面 the hardest。真正的意义上的 hardest 它不是说 np completeness 的问题,它是根本就没办法判定。」 [稿:4-逐字稿:17]
  • 易错:混用 "solve" 与 "verify"。「npc 是 np 问题里面最难的……所谓最难,是指的所有的 np 问题集合里面的所有的其他问题都可以在多项式时间里面归约到 npc 上面」 [稿:4-逐字稿:29]
  • 易错:以为 NP 是 "not polynomial"。NP = Nondeterministic Polynomial-time [PPT:4上:153],是"非确定图灵机多项式时间",不是"非多项式"。
  • 判断技巧:题目里出现 "NP-hard must be in NP"、"NPC 是最难的问题"、"NP 是 non-polynomial"——三条全是错的。

7. 考试英文答题模板

P is the class of languages decidable in polynomial time. NP is the class of languages for which a polynomial-time verification algorithm exists, i.e. iff there is a certificate with and . A language is NP-complete if (i) and (ii) for every ; hence NP-C NP NP-hard. A language is NP-hard if only condition (ii) holds, so it need not be in NP. Consequently , and whether the inclusion is strict is the open vs question.


4.1.2 NP-hard 不一定在 NP 【S 级 · 判断高频】

1. 一句话本质

"难"和"可验证"是两个独立维度:NP-hard 只说"谁都能压到你身上",完全没说"你的解能被快速检验"。所以 NP-hard 这一块是可以伸到 NP 外面去的。

2. 定义与性质

  • NPC 的定义是两个合取条件 [PPT:4上:370-372];NP-hard 只取了第二个。定义上就少了一条,任何"NP-hard ⇒ ∈NP"的推断都是凭空补条件。
  • 反例的标准构造:停机问题(Halting problem)。它 NP-hard(因为任何 NP 问题都能归约到"程序跑不跑得完"),但它 undecidable,而 NP 里的每个问题都至少是 decidable 的 ⇒ Halting ∉ NP。
  • 因此正确的方向只有:NPC ⊂ NP-hard,以及 NP-hard ∩ NP = NPC。
  • 老师原话把这条讲得最直白:「np hard 至少是 npc,它的难度不低于 npc。它的区别,它根本可能就不在 np 问题里面,它是 hard,它可能根本就不是 np 的。」 [稿:4-逐字稿:32] 纪要同样记:「NP-hard 问题难度不低于 NPC,但可能根本不在 NP 问题集合中。」[纪要:4-conclusion:1]

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 的定义只有一句:若所有 类问题都可在多项式时间内规约到 ,则 是 NP-hard。紧随其后的操作性解释是「如果可以在一单位时间内解决 ,那么所有 类问题都可以在多项式单位时间内解决」——这是 oracle 视角:NP-hard 描述的是求解能力的下界,而不是「 自身是否可验证」。因此定义中既没有 ,也没有「 可判定」,甚至没有要求 是判定问题(功能版 NP-hard 由此而来)。

2.2 OI Wiki 校订|基本性质

  1. 对归约向上封闭:若 是 NP-hard 且 ,则 也是 NP-hard。理由是传递性:。例:HAM-CYCLE TSP 立得 TSP 的判定版 NP-hard。
  2. :把两条定义合取即可,双向都成立。例:CLIQUE 既在 NP 又是 NP-hard,故是 NPC。
  3. NP-hard 既不含于 NP 也不含于 :停机问题是 NP-hard 且不可判定;另一类更细的例子是 NEXP-complete 问题——它可判定,但由 知它不在 NP,而 又使它 NP-hard。所以「不在 NP」不等于「不可判定」。
  4. 不可判定 NP-hard:箭头是单向的。不可判定只说明没有判定器,与「所有 NP 都能归约过来」是两件事;停机问题同时具备两条性质需要单独证明。
  5. 若 NP-hard 与 相交,则 :设 是 NP-hard 且 ,则每个 有 ,故 。因此在 的前提下,NP-hard 中一切可判定问题都落在 之外。
  6. oracle 语义可当判定口诀:「给 一个单位时间的求解器,就能多项式时间解所有 NP」——满足这句话就是 NP-hard,不满足就不是;它是否在 NP 要看另外能否给出多项式证书。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 没有给「NP-hard 不必在 NP」的显式反例,只靠定义缺项隐含;PPT 用停机问题把这条讲成 S 级判断点,答题时应写反例而不只是背定义。术语仍是「规约 / 归约」之别。另需注意 OI 的「一单位时间解决 」是 oracle 模型,不能理解成「 有多项式算法」;后者会直接把 NP-hard 拉进 ,与定义矛盾。

3. 图示

┌─────────────────────────────NP-hard─────────────────────────────┐│┌───────────────NP───────────────┐│││P...┌──────────┐│││││NPC││←NP-hard∩NP=NPC│││└──────────┘│││└──────────────────────────────────┘││↑││Halting、TSP优化版、SAT的"求最少满足子句数"版││(不可判定/输出非Yes-No)→在NP之外│└───────────────────────────────────────────────────────────────────┘

4. 复杂度表

命题 真假 一句话理由
NP-hard ⊆ NP 假 停机问题反例
所有 NP-hard 都不可判定 假 NPC 就是可判定的 NP-hard
NPC = NP-hard ∩ NP 真 定义直接展开
若 NP-hard 且 NP,则 NPC 真 两条定义都满足
若 NPC,则 NP-hard 真 定义的一半

最后两行是本考点的可考推断:判断"某问题既是 NP-hard 又在 NP,问它是不是 NPC" —— 是。

5. 典型例题

True or False: Every NP-hard problem is in NP, because NP-hard is harder than NP-complete and NP-complete problems are in NP.

中文逐步:

  1. 前半句 "Every NP-hard problem is in NP" —— 假。
  2. 后半句的推理链 "比 NPC 难 ⇒ 在 NP" 本身就是错的:难度更高恰恰意味着更可能跑出 NP 之外(NP 是"可验证"的上界)。
  3. 反例:Halting problem —— Halting 对所有 成立(NP-hard 成立),但 Halting 不可判定(∉ NP)。
  4. 所以整句 False,且错误原因要写"NP-hard 的定义不含 这一条"。

6. 易错点 + 老师原话

  • 易错:把 "NP-hard" 理解成 "NPC 的严格更难版 ⇒ 所以也是一种 NP"。「它根本可能就不在 np 问题里面」 [稿:4-逐字稿:32]
  • 易错:反过来也错——不能因为某问题不可判定就断言它 NP-hard(不可判定 ≠ 自动 NP-hard,虽然停机问题是)。考试只考"NP-hard 可否 ∉ NP",不考"不可判定是否都 NP-hard"。
  • 记忆锚:NPC 是"NP 里的刺头",NP-hard 是"全学院的刺头,可能根本不在这学院"。

7. 考试英文答题模板

False. NP-hardness requires only the reducibility condition for all ; membership is a separate, additional requirement that defines NP-completeness. The halting problem is NP-hard yet undecidable, hence certainly not in NP. Therefore NP-hard is not contained in NP, and in fact NP-C NP NP-hard.


4.1.3 归约方向: 不难于 【S 级 · 历年高频错点 · 全讲最高危】

1. 一句话本质

的意思是:给我一个能解 B 的黑箱,我就能解 A。所以会做 B 就会做 A ⇒ B 至少和 A 一样难。箭头从"易"指向"难"。

2. 定义与性质

形式定义 [PPT:4上:361-368]: 当且仅当存在多项式时间可计算函数 ,使对所有 ,

称归约函数(reduction function),算 的算法称归约算法(reduction algorithm)。

PPT 在定义上方标了三个字:no harder than [PPT:4上:359] —— 那是说 no harder than ,即难度沿箭头不减。

由此推出的三条,必须刻进肌肉:

  1. ⇒ 不难于 ( 更难或同难)。这是本考点唯一结论。
  2. 组合律:若 且 ,则 (两个多项式复合仍是多项式)。归约因此可传递,才能"链式搬运"难度。
  3. 方向锁死:难度是从 A 的宇宙流进 B 的宇宙,所以你只能把"已知难的"往下传,不能从"目标未知"往回推。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 只给出 NP-hard 语境下的规约定义:所有 类问题都可在多项式时间内规约到 。把它还原成两语言间的关系,就是 PPT 的 :存在多项式时间可计算函数 ,使对一切 有 。OI 的操作性表述「若能在单位时间解决 ,就能在多项式时间解决 」正好点明方向:归约把解 的能力搬运给 ,所以难度沿箭头不减, 至少与 一样难。

2.2 OI Wiki 校订|基本性质

  1. 是预序而非偏序:自反(恒等函数)、传递(多项式复合)成立;反对称不成立。例:判定「 为偶数」与「 被 3 整除」互相归约(用常数时间算出对方答案再输出固定实例),但两个语言并不相等。
  2. 方向语义: 读作「 归约到 」,即「有 的求解器就能解 」,故 的难度 。箭头从易指向难,这是全讲最高危的记法。
  3. 向下封闭: 且 。例:把「图是否有三角形」归约到「图是否有 」不成立,但反向的平凡归约说明后者更难。
  4. NP-hard 向上封闭: 且 是 NP-hard 是 NP-hard。4.1.12 的第 3 步正是这条的实例化。
  5. 逆否形式最常用: 且 ;同理 也向下封闭(先映射再验证),故 可推出 。
  6. 多项式限制不可删:若 允许指数时间,则可先直接判定 ,再把所有 Yes 映到某个固定 Yes 实例、所有 No 映到固定 No 实例,于是任何可判定 都归约到任何非平凡 ,难度信息被完全抹掉。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 站在 NP-hard 的定义里使用「规约」,没有单独把 不难于 抽成一条命题;PPT 把它标成 no harder than 并反复强调,因为这是选择题最常反着考的点。答题时写英文最稳: means reduces to , hence is at least as hard as 。另注意 OI 的「规约」与 PPT 的「归约」是同义词,不要当成两种不同操作。

3. 图示(含类比)

类比:翻译整本《红楼梦》←这是A要先把它译成英文←归约f,多项式时间再出版发行←解B的算法DA≤pB:A──f(O(n^k))──▶B──D(O(n^m))──▶answer(与A的答案相同)论断:如果我会做"出版发行"(B),再加上免费的翻译(f),我就会做A。反过来:如果我会做A(直接读懂中文),对出版发行(B)毫无帮助。⇒B是那个更难的本事。正确箭头:易──→难A≤pB(Bharder)错误箭头:难──→易B≤pA(✗什么也证不出,除非你已有A的解法)

4. 复杂度表

已知 结论 用途
, 难度向下传播(B 易 ⇒ A 易)
,(业界相信) 难度向上传播(A 难 ⇒ B 难)← 证 NPC 用它
, NP-hard 什么也推不出 方向反了
, NPC NP-hard 经典"搬难"手法

第三行是历年失分的直接来源:用 + B 是 NP-hard 去证 A 是 NP-hard —— 逻辑无输出。

5. 典型例题(正反辨识练习)

练习 A(正向,正确)

We know 3-SAT is NP-complete. To prove that Clique is NP-complete, should we show 3-SAT Clique or Clique 3-SAT?

中文逐步:

  1. 目标是给 Clique 贴上 NP-hard 标签,即"所有 NP 问题 Clique"。
  2. 传递性捷径: 3-SAT(已知 3-SAT 是 NPC)且 3-SAT Clique ⇒ Clique。
  3. 所以必须证 3-SAT Clique:把子句变成图(4.1.10),使"3-SAT 可满足 ⟺ 图有 K 团"。
  4. 若反过来证 Clique 3-SAT,只说明"3-SAT 比 Clique 难",而 3-SAT 已经是 NPC 了,这个结论早就知道、毫无新信息。答案:3-SAT Clique。

练习 B(反向,错误)

"Since Hamilton Cycle TSP, and Hamilton Cycle is NP-complete, TSP is NP-complete." Is the reasoning valid?

中文逐步:

  1. 方向恰好对了:从已知 NPC 的 Hamilton 出发指向 TSP ⇒ 结论成立(PPT 的 TSP 例证就是这个方向 [PPT:4上:217-236])。
  2. 但若有人写成 "TSP Hamilton Cycle,所以 TSP 是 NPC",则方向反了:这条只说明 Hamilton 不比 TSP 易,完全没给 TSP 下界。
  3. 判卷要点:箭头起点的那个问题必须是"已知 NPC",箭头终点的那个才是"待证 NPC"。

练习 C(口头快问)

",谁是更难的那个?"——答 B。老师班上做过这道口头测验:「如果一个问题 a 可以在多项式时间里面归约到问题 b,那么 a 和 b 这两个问题谁更难一点……王云鹤也答了,我们两个同学答过来都是 B」 [稿:4-逐字稿:29-32],并解释 「我们能够在多项式时间里解决 B,那么 a 可以用多项式的时间转化成 b,然后我们就把 a 给解决了……但是反过来不一定」 [稿:4-逐字稿:32]。

6. 易错点 + 老师原话(三次重复)

  • 第一次(结论):「若问题 A 能在多项式时间内归约到问题 B,则 B 的难度高于 A,解决 B 即可顺势解决 A。」 [纪要:4-conclusion:1]
  • 第二次(反证错误用法):「我们多项式时间内解决了 a,它归约到 b,仍然对于 B 没有任何的帮助。我们一定要搞清楚谁归约到谁,谁是比较难的那一个。」 [稿:4-逐字稿:32]
  • 第三次(证 NPC 的正确方向):「我们已知 a 是 npc……然后我们要证明 B 也是 npc,我们就得证明 a 比 B 要容易……你就要证明 a 可以在多项式时间里面归约到 B」 [稿:4-逐字稿:53]
  • 备忘口诀:"箭头指向难处" —— 的箭头朝哪边,哪边就是 B(更难)。
  • 高频错法清单:① 写 却声称证了 是 NPC;② 把 "no harder than" 读成 " 不比 难,所以 更易"(原文是 no harder than ,即 更难);③ 记成 更难。

7. 考试英文答题模板

means there is a polynomial-time computable with . Given any polynomial-time algorithm for , we obtain one for by composing with that algorithm, so solving solves ; the converse gives nothing. Hence implies that is at least as hard as (the reduction arrow points from easier to harder). To prove that a new problem is NP-hard, we must therefore reduce a known NP-hard problem to , i.e. exhibit ; showing proves nothing.


4.1.4 多项式归约的形式定义 【A 级 · 简答】

1. 一句话本质

归约就是一台多项式时间的翻译机:把 A 的每个实例翻成 B 的实例,且答案必须完全一致(Yes 对 Yes,No 对 No)。翻译本身必须便宜,否则归约毫无意义。

2. 定义与性质

定义见 4.1.3 的公式 [PPT:4上:361-368]。拆成三个必答要件:

  1. 是多项式时间可计算的(否则翻译代价吃掉一切,"归约"失去意义);
  2. 答案是等价的双向条件 (不是单向蕴含!);
  3. 归约保留"Yes/No"的结构,因此可以链式复合。

PPT 的算法级叙述 [PPT:4上:200-213]:给定 A 的实例 ,用程序 算出 ,耗时 ;再用程序 解 ,耗时 ;若 的答案等于 的答案,则总时间 仍是多项式。旁注 Decision problem is easier [PPT:4上:204] 提醒:这里的 A、B 都已是判定问题(见 4.1.6)。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 对「多项式时间规约」只给文字定义,没有写出归约函数,因此这里按 PPT 补全: 当且仅当存在多项式时间可计算的 ,使对一切 有 。这种「一次映射、一次回答」的形式叫 Karp 归约(多一归约);若允许算法在多项式时间内多次询问 的 oracle,则叫 Cook 归约(图灵归约)。本课程与 PPT 用的都是 Karp 归约,NPC 定义也建立在其上。

2.2 OI Wiki 校订|基本性质

  1. 复合封闭:若 在 内可算、 在 内可算,则 在 内可算(先算 再算 ,中间规模仍是多项式)。例: 的编码接 的构造,总计 。
  2. 双向等价不可弱化成单向:只证 会放过假归约。反例:把任意 3-SAT 公式都映到同一个可满足公式,单向蕴含成立,但 No 实例被误判成 Yes。
  3. 归约不需要先知道答案: 只是语法变换,构造过程不得调用 的判定器(Karp 归约中);正确性靠事后证明,而不是靠求解。
  4. 补集同归约:同一个 同时见证 与 ,因为 是原式的逆否。这条在讨论 co-NP 时反复用到。
  5. 输出规模自动多项式有界:图灵机每步至多写一个符号, 步的 输出长度不超过 ;反之若允许指数长输出,验证代价立刻失控。
  6. Karp 与 Cook 不可混用:Karp 一次映射,Cook 可多次查询;两者定义的 NPC 类恰好相同,但证明中写成「多次调用 的算法」就已不是 Karp 归约,考试要求的是前者。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 只有「多项式时间内规约」的文字说法,没有函数式定义,也没有区分 Karp 与 Cook;PPT 给出 与双向等价,是本课程的标准写法。答题时必须写全三件事: 多项式时间、、 的输出规模多项式。只写「把 A 转成 B」会丢分。

3. 图示

┌────────L1────────┐┌────────L2────────┐│x∈L1──────┐│f(poly)│f(x)∈L2────┐││x∉L1──────┤│─────────▶│f(x)∉L2────┤│└────────────────┼───┘└────────────────┼───┘│答案相同│└──────────────Yes/No───────────┘两处必须证:x∈L1⇒f(x)∈L2(completeness)f(x)∈L2⇒x∈L1(soundness)

4. 复杂度表

环节 代价 说明
计算 必须多项式
解 黑箱调用
合计 , 多项式对复合封闭

5. 典型例题

Give the formal definition of polynomial-time reducibility, and explain why a reduction must be computable in polynomial time.

中文逐步:

  1. 写出: 当且仅当存在多项式时间可计算 ,使 。
  2. 必要性理由:若 需指数时间,"用 B 解 A"的总代价就已是指数,无法论证"B 的多项式算法 ⇒ A 的多项式算法",整个难度传递机制崩塌。
  3. 补充: 的输出规模必须是多项式(否则连写下来都要超时),这也是 的隐含要求。

6. 易错点 + 老师原话

  • 易错:只证单向()。定义是 ,答题里出现 "if and only if" 四个字才拿满分。
  • 易错:忘写 的多项式性。老师反复强调:「全部都得是 polynomial time,关于 N 是个 polynomial time」 [稿:4-逐字稿:134](虽然原话在为近似比铺垫,但"多项式"作为准入门槛在整讲通用)。
  • 易错:把归约当成"枚举所有实例"。归约是一台机器,逐实例翻译,不允许先求解 A。

7. 考试英文答题模板

A language is polynomial-time reducible to , written , if there exists a function , computable in polynomial time, such that for every , . The polynomial bound is essential: it makes reductions transitive and lets a polynomial algorithm for yield one for with total running time .


4.1.5 优化问题 → 判定问题(引入 ) 【S 级 · 必考简答】

1. 一句话本质

把"求最优值"改成"给定一个门槛 ,问能不能做到"。求最大 ⇒ 问 ;求最小 ⇒ 问 。

2. 定义与性质

为什么必须转? 因为归约的定义(4.1.4)只在 Yes/No 上有意义;"最优值是多少"的两个不同输出之间无法建立逻辑等价。老师的解释:「否则,就不可比了,全部转化成判定问题的好处是所有问题的回答都是 yes or no,我就可以说了,对他也是 yes,他多项式时间转化成它以后回答的 yes 也对应着他的 yes。」 [稿:4-逐字稿:116]

转换法则(死记):

老师原话给出了完整的口诀:「因为我们要求最大值嘛。要求最小值,是不是小于等于 K,至多是 K;如果我们求最大值的就是至少是 K。」 [稿:4-逐字稿:113-116]

三组经典转换:

优化问题(Optimization) 判定问题(Decision) 门槛方向
最长路径:求最长简单路径长度 LONGEST-PATH 有从 到 、长度 的简单路径 max →
最大团:求 CLIQUE 有大小 的团 [PPT:4上:382-383] max →
最小顶点覆盖:求 VERTEX-COVER 有大小 的顶点覆盖 [PPT:4上:385-388] min →
最短路径:求 PATH 有 长度 的路径 [PPT:4上:267-271] min →
TSP:求最短巡回长度 TSP 有代价 的巡回 [PPT:4上:223-225] min →
最长 Hamilton 回路:求最长环长 是否存在长度 的简单回路 [稿:4-逐字稿:71] max →

4.1.11 & 4.1.8 都要用到这张表:Hamilton Cycle 的判定版是"是否有 Hamilton 回路"( 的特例);最长简单路径的判定版才是 NPC 的那个。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 在「功能性问题」一节给出转化的合法性:回答不止 YES/NO 的问题称为功能性问题(functional problem),而「任何功能性问题都可以转化为一个判定问题」,例子是「求两数之和」转化为「判定两数之和是否等于第三个数」;反向也可行——求判定问题的指示函数。把这套说法落到优化问题上,就是引入门槛 :max 型问「是否存在解使值 」,min 型问「是否存在解使值 」,输出统一为 。

2.2 OI Wiki 校订|基本性质

  1. 优化 判定是 的:已知最优值 ,判「」只需一次比较。例:TSP 最优巡回长为 25,则 的答案为 No, 为 Yes。
  2. 判定 优化靠自归约 + 二分:max 型对 单调(有 的解,则对一切 也有),故可在值域上二分,用 次判定还原最优值。例:边权为 位整数时, 次判定即可定出最短巡回长。
  3. 必须写进输入:固定 的团判定只需 枚举三元组,属 ;只有把 作为参数,CLIQUE 才是 NPC。这是「参数化后难度骤降」的最小实例。
  4. 阈值方向决定单调性:max 必须用 、min 必须用 ;若 max 写成 ,解集随 增大而缩小,二分失效。
  5. 构造解也可自归约:对 SAT 逐个变量试 ,每次问一次判定, 次即得一组满足赋值;这说明「判定 搜索」在 NP 完全问题上不需要额外难度。
  6. 等价是多项式意义下的:判定与优化互相多项式归约,但常数与值域有关(二分要 次),不能宣称「同一问题、同一复杂度」。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 的例子是「和等于第三个数」,只说明功能性 判定的一步转化,没有提阈值单调性与二分;PPT 则要求写清 max 、min 并说明为何能还原最优值。答题按 PPT 的方向表写,且必须把 列入输入元组,否则「CLIQUE 是 NPC」这句话不成立。

3. 图示

优化问题:maxf(S)判定问题:f(S)≥K?│││加一个参数K│└──────────────▶Yes/No────────┘等价性(max型):已知ORACLE会答"是否有解≥K"→对K二分(或从n向下试)即可还原最优值,O(logn)次调用,仍是多项式⇒判定问题不难于优化问题(优化≤p判定的反向也成立),难度基本同步

4. 复杂度表

问题 优化版难度 判定版难度
最短路径 P P
最长简单路径 NPC 的优化版 NPC
最大团 NPC 的优化版 NPC
TSP NP-hard(优化版) NPC
Bin packing(能否用 2 箱装下)[稿:4-逐字稿:431] NP-hard NPC

表里 TSP 一行值得注意:严格说 TSP 的优化版是 NP-hard(不是 NPC,因为输出是数值而非 Yes/No),其判定版是 NPC。这正是 4.1.2 的具体实例。

5. 典型例题

Convert the following optimisation problems into decision problems: (a) longest simple path; (b) maximum clique; (c) travelling salesman (minimum tour).

中文逐步:

  1. (a) 输入由 扩为 ,问"是否存在简单路径(不重复顶点)长度 "。注意必须保留 "simple" 与 "" 两个词。
  2. (b) 问是否存在 个两两相邻的顶点(clique of size )。 是"至少"而非"恰好"——因为若存在 的团,其子集也含 团,两者等价,PPT 写作 "(at least) K" [PPT:4上:381]。
  3. (c) 问是否存在经过所有顶点一次的巡回且总代价 (最小化 ⇒ )。
  4. 统一检查:三个都变成 Yes/No,且 的引入没有改变实例规模的多项式性( 用二进制写)。

6. 易错点 + 老师原话

  • 易错:把 max 型写成 、min 型写成 。「我们要求最大值嘛……如果求最大值的就是至少是 K」 [稿:4-逐字稿:113-116]
  • 易错:写"判定最长回路是否最长"。老师点名这是病:「直接判断这个环是最长的,是很难的,你需要把所有环都判定一遍,才能够判断出它是不是最长」 [稿:4-逐字稿:71]。正确写法必须换成门槛 K。
  • 易错:忘掉原问题的关键限定词。最长路径若不写 "simple",由于可以绕负权/正权环,问题性质完全改变(见 4.1.11)。
  • 进阶(学有余力):把" 是最大值"变成判定,还需加一层"不存在 "——即有同学现场提出的思路 [稿:4-逐字稿:92],等价于对 二分。考卷上写主判定版即可。

7. 考试英文答题模板

NP-completeness is defined for decision problems, so every optimisation problem must first be converted by introducing a threshold : for a maximisation problem ask whether a feasible solution with value exists; for a minimisation problem ask whether one with value exists. Thus "find the longest simple path" becomes LONGEST-PATH has a simple – path of length ; "find the maximum clique" becomes CLIQUE has a clique of size ; "find the shortest tour" becomes TSP has a Hamiltonian cycle of cost . The converted instance remains polynomially sized, and the optimum can be recovered from a decision oracle by binary search on .


4.1.6 归约比较必须在判定层面 【S 级 · 判断】

1. 一句话本质

只在 Yes/No 的世界里才能谈"答案一致";优化问题的输出是一堆数,没法说"这两个答案等价",所以只能在判定问题上比难度。

2. 定义与性质

  • 归约要求 ,两边都必须是语言(languages),即 的子集 [PPT:4上:279-292]。优化问题的解集是 这类路径描述 [PPT:4上:263-266],不是 ,无法套进上述定义。
  • PPT 用 SHORTEST-PATH 做了对照展示:抽象问题(abstract problem)的解是路径 ;而判定问题 PATH 的解集是 [PPT:4上:267-271]。同一张图的两种提问方式,只有后者可参与归约。
  • 老师的话把这条动机说得最清:「当我们在说谁归约到谁的时候,我们在说什么,我们都在说判定问题。Decision problem?不是原始问题,有很多 npc,原始问题是 np hard。」 [稿:4-逐字稿:110]

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 的界定很清楚:判定问题只能用 YES/NO 回答,本质是判定一个串是否属于某语言,即 ,;功能性问题的回答可以是一个数或其它结构。文中还说明「语言」和「问题」可以视作同义词(在判定语境下),并指出任何功能性问题都能转成判定问题。归约的定义 两侧都必须是语言,所以只有判定层面才能谈「答案一致」。

2.2 OI Wiki 校订|基本性质

  1. 语言是 的子集,故「答案一致」有定义;优化问题的解是路径、集合这类结构,无法与 对齐。例:SHORTEST-PATH 的输出是顶点序列 ,不是一位答案。
  2. 图问题都能编码成语言:邻接矩阵或边表可写成 01 串,图灵机只读串,所以「图是否有 Hamilton 回路」是合法的语言。这一步是全部图归约的前提。
  3. 只含判定问题:NP 被定义为语言类,因此严格的说法是「TSP 的判定版是 NPC」,而「TSP 是 NPC」是口语省略;功能版只能称 NP-hard。
  4. Karp 归约要求两侧都是语言:功能版之间要用 Cook 归约(可查询 oracle)。本课程用 Karp,所以必须先做 4.1.5 的转化。
  5. 功能版可能是 NP-hard 而非 NPC:最大团的功能版即例——它不在语言类里,因而永远不满足 NPC 的第一条。
  6. 判定 构造不是免费的:需要自归约性(逐位固定变量)。NP 完全问题普遍具备,但并非每个可判定问题都具备,答题不能默认「会判就会造」。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 说「语言与问题同义」,这句话只在判定语境下成立;PPT 用 abstract problem(解是结构)与 decision problem(解是 )的对照把边界划得更清。考试若问「为什么归约必须在判定层面」,答「归约要求 ,两侧必须是语言;优化问题的输出不是布尔值」即可,并可举 SHORTEST-PATH 的输出为路径序列作例。

3. 图示

优化问题(无法比较)判定问题(可比较)┌──────────────────────┐┌──────────────────────┐│输出:一个最优解的值│──引入K──▶│输出:{Yes,No}││Q(x)=s∈S││Q(x)∈{0,1}││两个解"等价"?说不清││Yes=Yes,No=No✔│└──────────────────────┘└──────────────────────┘✗不能写≤p✔可以写≤p

4. 复杂度表

说法 正确性
优化问题 ≤p 优化问题 定义不存在,不能这么写
判定版 ≤p 判定版 正确用法
"TSP 是 NPC"(不加限定) 严格说错,应写 "TSP 的判定版是 NPC,优化版是 NP-hard"
"SAT 是 NPC" 对,SAT 本身就是判定问题

5. 典型例题

Someone claims: "The travelling salesman problem is NP-complete, so the shortest-tour problem cannot be solved in polynomial time." Criticise.

中文逐步:

  1. 严格讲,TSP 的优化版输出是一个代价数值,不是 Yes/No,它属于 NP-hard,不能称 NPC。
  2. 说 "TSP is NP-complete" 时,语境上默认指判定版 TSP [PPT:4上:223-225]。
  3. 后半句的推论在 "P ≠ NP" 猜想下成立,但作为命题必须写前提。
  4. 正确表述:"The decision version of TSP is NP-complete; hence, unless P = NP, no polynomial-time algorithm computes the optimal tour."
  5. 判分关键词:decision version、unless P = NP。

6. 易错点 + 老师原话

  • 易错:直接说 "Knapsack 是 NPC"。0-1 背包的优化版才是 NP-hard;判定版(价值 ?)是 NPC。
  • 易错:把"难度比较"用在两套不同层面的问题上(一个优化、一个判定),等价性无法建立。
  • 老师原话:「否则,就不可比了」 [稿:4-逐字稿:116];「我们所谓的 npc 都是在判定问题之间,去讨论,谁比谁更难一点」 [稿:4-逐字稿:116]

7. 考试英文答题模板

Difficulty comparison and reduction are defined only for languages, i.e. decision problems whose answer is a single bit, because the reduction condition requires the two answers to be logically equivalent. An optimisation problem returns a value or a structure, so there is no canonical notion of "the same answer". Hence every optimisation problem must first be converted to its decision version by introducing a threshold , and all reductions and hardness statements are made between decision versions. (The optimisation version is then called NP-hard rather than NP-complete.)


4.1.7 停机问题不可判定 【B 级 · 选择】

1. 一句话本质

不存在一个程序,能对任意程序 与输入,判定 会不会停。"检测死循环"这件事本身做不到。

2. 定义与性质

PPT 的对角线构造 [PPT:4上:48-65]:

Loop( P ) {
  /* 1 */ if ( P(P) loops )   print(YES);
  /* 2 */ else                infinite_loop();
}
问:Loop( Loop ) 会怎样?
  若它终止  ⇒ 第 2 行被执行 ⇒ 说明 P(P)=Loop(Loop) 会 loop ⇒ 矛盾
  若它死循环 ⇒ 第 1 行被执行 ⇒ 说明 P(P)=Loop(Loop) 会终止 ⇒ 矛盾

两条腿都踩空 ⇒ 假设"存在死循环检测器"不成立 ⇒ Halting 不可判定。

性质(考点就这三条):

  • Halting undecidable(图灵 1936);
  • Halting 是 NP-hard 的(任何 NP 问题都能归约到"跑完给出答案");
  • Halting ∉ NP(NP 里每个问题都可判定)。所以它是 4.1.2 的教科书反例。
  • 相邻史实(可能考选择):Hilbert 1900 提出 23 问中的 Decidability 问题 [PPT:4上:32-38];Gödel 1931 不完备定理 [PPT:4上:40-43]——「we can never know everything nor prove everything we discover」。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 先区分两个动词:对语言 与图灵机 ,若 在任何输入下都有限步停机且 ,称 判定(decide);若只要求对 的输入有限步停机并接受,称 识别(recognize)。 是可被某台图灵机判定的语言类, 是可被识别的语言类,故 。OI 还给了一个可判定但极耗时的例子:给定图灵机编码与正整数 ,判定它是否在 步内停机,直接模拟需 时间,而 的输入长度只有 ,故该问题属 。

2.2 OI Wiki 校订|基本性质

  1. 且严格:停机问题属于 。通用图灵机模拟目标程序,接受即停机输出 1;拒绝时可能永不停,故只能识别、不能判定。
  2. 对角化是标准证法:设 判定「 停机」,构造 :若 为真则死循环,否则停机。问 :停机则 为假 ⇒ 应死循环;死循环则 为真 ⇒ 应停机,两头矛盾。
  3. Halting :(非确定图灵机的多项式步计算可展开成确定性指数时间判定),而 Halting 不可判定,故更不在 NP。
  4. Halting 是 NP-hard:任一 NP 问题的非确定图灵机可在多项式步内模拟,把「是否接受」归约为「模拟过程是否停机并接受」,归约本身是多项式时间。
  5. 「 步内停机」可判定:模拟 步即可,;但 用二进制写出,时间关于输入长度是指数级,所以它属于 而不因此属于 。这是「伪多项式」味道的最早实例。
  6. 不可判定性沿归约上传:若 Halting 且 Halting 不可判定,则 不可判定;否则用 的判定器复合归约即可判定 Halting。方向与 NP-hard 的向上封闭一致。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 只建立 的框架并陈述谱系定理,不证明停机问题不可判定;PPT 给出完整的对角线代码,考试按 PPT 写两行矛盾即可。术语上要分清:OI 的 recognize 是「机器识别」,PPT 的 verify 是「拿证书验证」,两者不是同一概念;Halting 不可判定用的是 decide 的否定,不是 verify 的否定。另外 OI 的 例子中 的编码方式必须写明,否则 会被误当成多项式。

3. 图示

所有问题的难度谱易──────────────────────────────────────────────▶难O(N)PNPNPCUndecidable读输入可判定可验证NP内最难根本无法判定↑Halting在这里───────────PvsNP的"线"───┘└───可判定vs不可判定的"线"

PP T 的 "Easy vs. Hard" 一页就是这张谱:最易 ("since we have to read inputs at least once"),最难是 undecidable [PPT:4上:26-29]。

4. 复杂度表

层次 代表问题 有无算法
读输入、求和 有
P 排序、最短路 有(多项式)
NPC SAT、Clique、TSP 判定版 未知(猜想无)
Undecidable Halting、任意程序等价性 证明不存在

5. 典型例题

Explain why no C compiler can detect all infinite loops.

中文逐步:

  1. 假设存在这样的编译器(等价于存在判定器 Loop)。
  2. 构造自指程序:Loop(P) 在 P(P) 死循环时输出 YES,否则自己死循环。
  3. 令 Loop 自身:若 Loop(Loop) 终止,则按其定义第 2 行生效 ⇒ Loop(Loop) 死循环,矛盾;反之亦然。
  4. 矛盾源自假设 ⇒ 无此编译器。注意论证只用了"程序可作为自己的输入",不需要任何复杂度假设——这是不可判定性,比 NP 难一个层次。

6. 易错点 + 老师原话

  • 易错:把 Halting 归到 NPC。老师明确:「真正的意义上的 hardest……它是根本就没办法判定,叫 undecidable」 [稿:4-逐字稿:17]
  • 易错:以为"不可判定"意味着"实践上难"。不是——它意味着理论上不存在任何算法(连指数时间的都没有)。
  • 易错:混淆"我不知道它停不停"与"不可判定"。前者是能力问题,后者是逻辑不可能。
  • 老师原话(NP 与 undecidable 的关系):「你像那些 undecidable 就你根本无法判定的问题,你都不知道有解没解。np 也解决不了。」 [稿:4-逐字稿:23]

7. 考试英文答题模板

The halting problem is undecidable. Suppose a program Loop(P) existed that prints YES iff P(P) halts, and otherwise loops forever. Then Loop(Loop) halts by construction it loops, and Loop(Loop) loops by construction it halts — a contradiction. Hence no such decision procedure exists, so no compiler can detect all infinite loops. Consequently the halting problem is NP-hard but lies outside NP, since every problem in NP is decidable.


4.1.8 哈密顿回路 【A 级 · 简答】

1. 一句话本质

欧拉回路管边(每条边恰好一次,),哈密顿回路管点(每个顶点恰好一次,NPC)。一字之差,天壤之别。

2. 定义与性质

  • Hamilton cycle problem:给定 ,是否存在一个简单回路(simple cycle),使每个顶点恰好出现一次 [PPT:4上:157-161, 221-222]。
  • 判定版:HAM-CYCLE = { <G> : G has a Hamiltonian cycle }( 的特例,见 4.1.5)。
  • Hamiltonian path 版(不闭合)同样 NPC。
  • 状态:NPC(由 3-SAT / Vertex Cover 归约而来,是 TSP 归约的起点 [PPT:4上:217-236])。
  • 验证思路:证书是顶点序列,只需检查 (a) 恰好 个顶点且两两不同;(b) 相邻(含首尾)都有边 ⇒ ⇒ ∈ NP。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《哈密顿图》 · https://oi-wiki.org/graph/hamilton/

OI Wiki 给出四个逐字定义:通过图中所有顶点一次且仅一次的通路称为哈密顿通路(Hamiltonian path);通过所有顶点一次且仅一次的回路称为哈密顿回路(Hamiltonian cycle);具有哈密顿回路的图称为哈密顿图;具有哈密顿通路但不具有哈密顿回路的图称为半哈密顿图。判定版相应写成 有哈密顿回路,它是 4.1.5 中 型门槛在 处的特例。

2.2 OI Wiki 校订|基本性质

  1. 割点必要条件:若 是哈密顿图,则对 的任意非空真子集 ,有 ,其中 是连通分支数。理由是回路删去 个顶点后至多断成 段。例:星 取 中心,,故非哈密顿图。
  2. 半哈密顿的加一版:,因为通路比回路少一条收尾边。
  3. Dirac 型充分条件: 且任意不相邻顶点满足 ,则 有哈密顿回路(OI 推论 1); 只保证哈密顿通路。条件是充分非必要: 中不相邻点度和为 ,但 本身是哈密顿图。
  4. 竞赛图必有好结构:任意 阶竞赛图都有哈密顿通路(Rédei);强连通的竞赛图还是哈密顿图(Camion)。这说明「稠密 + 定向」可绕过 NPC 难度。
  5. 完全图的边分解: 含 条边不重的哈密顿回路。例: 有 10 条边,可分解为 2 个 5-圈,恰好用尽。
  6. 难在「一般图 + 覆盖全部顶点」两个条件同时出现:DAG 上最长路只需 ,因为无环排除了绕行;一旦允许一般图并要求简单路径,判定就变成 NPC, 时即哈密顿通路。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 这一页只讲图论判定与充分条件,不提复杂度类;PPT 把 Hamilton 当作 NPC 链条的中转站(3-SAT Clique Vertex-Cover Ham-Cycle TSP)。考试若考性质,用割点条件构造反例最快;若考复杂度,按 PPT 写 NPC 与归约方向。另须与欧拉回路区分:欧拉管边(每条边一次,多项式可判),哈密顿管点(每个顶点一次,NPC)。

3. 图示

欧拉(走边)哈密顿(走点)A──BA──B│╲││││╲│││C──DC──D判据:所有顶点度为偶⇒P无局部判据⇒NPCEuler:A-B-D-C-A✔Hamilton:A-B-D-C-A✔(但一般图不可判)

4. 复杂度表

问题 约束对象 复杂度 判据
Euler circuit 每条边一次(可重复顶点) P() 连通且所有度为偶
Euler path 每条边一次 P 恰有 0 或 2 个奇度点
Hamilton cycle 每个点一次(可走任意边) NPC 无已知局部判据
Hamilton path 每个点一次 NPC 同上
TSP Hamilton 回路 + 最小权 NPC(判定版) 见 4.1.10

5. 典型例题

Show that the Hamiltonian cycle problem is in NP, and explain why the Euler-circuit problem is not NP-complete.

中文逐步:

  1. ∈ NP:证书取顶点序列 。验证:① 长度恰为 且是 的一个排列(用布尔数组 );② 对每个 ,,且 ( 次哈希查询);③ 全部通过回答 1。总时间 ⇒ 多项式。
  2. 欧拉不是 NPC:因为它在 P 里(Hierholzer,,充要条件是连通 + 全偶度)。若欧拉是 NPC 且又在 P,则 P = NP。
  3. 答题必须点明"欧拉有局部充要判据、哈密顿没有"——这是两者分居两侧的根源。

6. 易错点 + 老师原话

  • 易错:把欧拉与哈密顿混为一谈。(PPT 首页并列 Euler circuit "touches every edge exactly once" 与 Hamilton cycle "contains every vertex",并只给后者标了 "No known algorithms are guaranteed to run in polynomial time") [PPT:4上:12-21]
  • 易错:说"哈密顿回路是 NP-hard 的优化问题"。它本身就是判定问题,直接是 NPC。
  • 易错:验证时忘了检查"首尾相连"或"顶点不重复"——两个都是证书合法性的一部分。

7. 考试英文答题模板

HAM-CYCLE has a simple cycle containing every vertex exactly once is in NP: a certificate is the vertex sequence , and a polynomial-time verifier checks that it is a permutation of and that for all (with ). It is NP-complete, whereas the Euler-circuit problem is in P because a connected graph has an Euler circuit iff every vertex has even degree — a local necessary-and-sufficient condition that Hamiltonicity lacks.


4.1.9 SAT → 3-SAT 归约 【A 级 · 简答】

1. 一句话本质

Cook 1971:SAT 是第一个 NPC。3-SAT 是"每个子句恰好 3 个字面量"的 SAT;把 SAT 的任意子句按长度拆开,就能在多项式时间内搬到 3-SAT,从而让 3-SAT 继承 NPC 地位。

2. 定义与性质

  • SAT(Circuit-SAT / CNF-SAT):输入布尔表达式,问是否存在赋值使其为 1 [PPT:4上:243-247]。Cook 定理:所有 NP 问题可多项式变换为 SAT(用非确定图灵机在多项式时间内求解该问题来证明)[PPT:4上:248-252]。SAT 是归约链的根(root)。
  • 3-SAT:CNF 形式,每个 clause 恰有 3 个 literals。
  • 归约 SAT 3-SAT(构造):对 CNF 公式的每个子句 ,按 分情况替换( 为新变量,各子句独立):
原 clause 替换为
— 等价于
原样保留

正确性证明(必答的双向):

  • 原式可满足 ⇒ 新式可满足:取满足赋值, 时按"第一个为真的字面量位置"选 :若 或 为真,令 ;若 ()是第一个真字面量,令 。链式检查每条新子句都有真字面量。
  • 新式可满足 ⇒ 原式可满足:反证。若原子句全假,则 。第一条子句 迫使 ;第二条 迫使 ;归纳得所有 ;最后一条 全假,矛盾。
  • 规模:每个 -子句产生 个 3-子句、 个新变量 ⇒ 线性放大 ⇒ 多项式。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录 SAT 与 3-SAT 的独立条目,以下按教材(CLRS / PPT)口径整理;NPC 框架部分依据 OI Wiki《计算复杂性》(https://oi-wiki.org/misc/cc-basic/)。

SAT(Boolean satisfiability)的输入是 CNF 公式 ,每个子句 是若干文字(变量或其否定)的析取,问是否存在赋值使 。3-SAT 要求每个子句恰含 3 个文字。Cook–Levin 定理:SAT 是 NPC——SAT (证书是赋值),且任一 NP 问题的非确定图灵机接受计算都可多项式时间编码成一个 CNF 公式,使「机器接受」「公式可满足」。SAT 3-SAT 的构造是逐子句改写: 时补辅助变量 复制两份, 原样保留, 时链式拆成 个三元子句。

2.2 OI Wiki 校订|基本性质

  1. SAT 是归约链的根:Cook–Levin 不依赖任何已有 NPC 问题,直接对计算历史建公式,故它是唯一需要「从零证明」的 NPC 问题。
  2. 3-SAT 继承 NPC:3-SAT (赋值作证书,逐句验证 ),且 SAT 3-SAT,由 NPC 的定义即得。
  3. 改写必须双向保答案:可满足 可满足(按首个真文字的位置设辅助变量);可满足 原子句可满足(若原子句全假,第一条迫使 ,逐条归纳迫使所有 ,最后一条 为假,矛盾)。
  4. 规模线性:每个 -子句产出 个三元子句与 个新变量,总规模 ,故归约是多项式时间。
  5. 辅助变量按子句独立:不同子句用不同的 ,否则跨子句的赋值耦合会破坏双向性。
  6. 的分界很 sharp:2-SAT 属 (蕴含图 + SCC),3-SAT 是 NPC;同一族问题只差一个字面量,难度却跨过 P/NPC 边界。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 无 SAT 条目,只在《计算复杂性》里列 NPC 经典问题清单,故本节的形式定义与改写表全部按 PPT。措辞差异:OI 写「规约」,PPT 写「归约」;OI 未给 Cook 定理的证明思路,PPT 用「非确定图灵机的计算历史」一句话概括,答卷写这句即可。考试重点是三件事:构造规则、双向证明、线性规模,缺一不可。

3. 图示

SAT子句(l1∨l2∨l3∨l4∨l5)│拆成3-链▼(l1∨l2∨x1)∧(¬x1∨l3∨x2)∧(¬x2∨l4∨l5)└──────┬──────┘└──────┬──────┘└──────┬───────┘每条都恰3个字面量直观:x_i是一根"接力棒",把"真"从左端传到右端原子句至少一个字面量为真⟺存在接力方案让每条3-子句都真

4. 复杂度表

归约 构造耗时 规模变化 结论
SAT 3-SAT 子句数 原文数 3-SAT 是 NPC
2-SAT — — 多项式可解(有向图 SCC)
Horn-SAT — — 多项式可解

最后两行是必考的反差:3 与 2 一字之差,一边 NPC,一边 P。

5. 典型例题

SAT is NP-complete. Show that 3-SAT is NP-complete.

中文逐步:

  1. 3-SAT ∈ NP:证书是赋值 ,代入每个子句检查是否为真, ⇒ 多项式验证。
  2. 3-SAT 是 NP-hard:给出 SAT 3-SAT。按上表逐子句替换,用新变量串联,构造时间是 规模的线性函数。
  3. 双向证明:如上。
  4. 结论:3-SAT ∈ NP 且 NP-hard ⇒ NPC。以后证别的问题时,直接用 3-SAT 作起点(比 SAT 更规整,因为每个子句都是 3 个字面量,便于建图)。

6. 易错点 + 老师原话

  • 易错:说"3-SAT 是 SAT 的子集所以更容易"。子集关系不等于难度关系(3-SAT 与 SAT 一样是 NPC)。
  • 易错:归约方向写 SAT 3-SAT 或"3-SAT ≤p SAT"。前者可用(因为 3-SAT 的实例本身就是 SAT 实例),但它只能说明 3-SAT ≤p SAT,不能证 3-SAT 是 NPC。
  • 易错: 情形漏写。SAT 的 CNF 里可能有 unit clause 和 2-clause,不处理就不完整。
  • 老师定位:「3-SAT 是后续所有归约的根」(速查册 4.1.9);SAT 的地位来自 Cook:「The first problem that was proven to be NP-complete was the Satisfiability problem」 [PPT:4上:243-249]

7. 考试英文答题模板

3-SAT NP since an assignment is a polynomial certificate. To show NP-hardness, reduce SAT to 3-SAT: replace each clause of size by clauses of size exactly 3, introducing fresh variables. For use ; for use ; for use the chain . The construction is linear-time and preserves satisfiability in both directions, so 3-SAT is NP-complete.


4.1.10 团问题(Clique)与顶点覆盖归约 【A 级 · 简答】

1. 一句话本质

团(clique) = 两两相邻的一组顶点 = 完全子图。PPT 的正题是两条:(a) 3-SAT ≤p Clique("按真值建图"),(b) Clique ≤p Vertex-Cover("取补集")。

2. 定义与性质

  • CLIQUE = { <G,K> : G is a graph with a clique of size K } [PPT:4上:382-383](注意 "at least" 语义,见 4.1.5)。
  • VERTEX-COVER = { <G,K> : G has a vertex cover of size K },即存在 ,使每条边至少有一个端点在 中 [PPT:4上:385-388]。
  • 关键恒等式(补图形式): 有大小 的团 补图 有大小 的顶点覆盖 [PPT:4上:399-400]。这里必须换成补图: 在 中两两相邻,等价于 把 的每条边都覆盖住。(若照「同一张图」理解会立刻出错: 有 3-团,但同一张图的最小顶点覆盖是 2,不是 。)
  • PPT 的证明 [PPT:4上:389-412] 完整给出:
  • NP:证书 ;验证 且每条边都有端点落在 ,(检查 条边,每条 判定成员)。
  • Clique Vertex-Cover:构造补图 (),令 ,输出 。
  • 方向:设 是 中大小 的团。任取 的边 ,则 ,故 不可能同时在团 中 ⇒ 至少一个属于 ⇒ 覆盖 的所有边,大小为 。
  • 方向:设 是 中大小 的顶点覆盖。若 而 ,则 是 的边且两端都不在 ,与覆盖矛盾 ⇒ 在 中两两相邻 ⇒ 是大小 的团。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最大团》 · https://oi-wiki.org/graph/max-clique/

OI Wiki 的定义:团(clique)是顶点的一个子集,其中任意两点都相邻,也称完全子图;若一个团不能通过加入顶点继续扩大,称极大团(maximal clique);最大团是点数最多的极大团,其大小记 。求解最大团的经典算法是 Bron–Kerbosch,用三个集合 递归: 是当前团, 是仍可加入的候选点, 是已处理过、不能再用的点; 时报告 为极大团。判定版 有大小 的团;顶点覆盖判定版 有大小 的顶点覆盖。

2.2 OI Wiki 校订|基本性质

  1. 团 补图独立集: 是 的团 是 的独立集,故 。例: 与其补图同构,。
  2. Gallai 恒等式:。理由是 覆盖所有边 内没有边,即后者是独立集;两者互补。
  3. 归约恒等式: 有大小 的团 有大小 的顶点覆盖。这是 1、2 两条的合并,也是 CLIQUE VERTEX-COVER 的全部内容。例: 有 3-团,其补图是 3 个孤立点,最小顶点覆盖为 0。
  4. 判定 NPC,功能版 NP-hard:CLIQUE 是 NPC(证书是 个顶点,验证 );「求 」不是语言,故只能称 NP-hard。
  5. 极大团可以指数多:Moon–Moser 界为 个极大团( 个孤立三角形取并即达),故枚举极大团的最坏代价是指数;Bron–Kerbosch 加 pivot 后的最坏界同为 。
  6. 特殊图类可多项式:二分图无三角形,,有边即为 2;完美图(弦图、二分图及其补)上最大团与着色都多项式可解。这说明 NPC 是「一般图」的属性,不是「团」这个概念本身的属性。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 这一页是算法页,只讲 Bron–Kerbosch 的实现与优化,不给归约、不给复杂度类结论;PPT 则把 CLIQUE 当作 NPC 链条的枢纽,重点是归约。勘误:归约必须把图换成补图 并把门槛改成 ,若照「同一张图」写, 的 3-团会推出「0 个顶点的顶点覆盖」,显然错误。答卷务必写 complement graph 与 。

3. 图示

(a)3-SAT≤pClique建图(教科书标准构造,PPT4.1.12用)Φ=(x1∨¬x2∨x3)∧(¬x1∨x2∨x3)∧(x1∨x2∨¬x3)C1C2C3C1:x1¬x2x3●●●│╲│╲│←每条子句内部三点两两相连│╲│╲│C2:¬x1x2x3←不同子句之间:仅当两个字面量"不互补"时连边●●●│╲│╲│C3:x1x2¬x3●●●规则:①同子句内任意两点相连②跨子句:连边⟺两字面量不矛盾(非x与¬x)(b)Clique⇄Vertex-Cover(同一张G)团V'(大小K)⇄覆盖V-V'(大小|V|-K)团内两点必有边⇄覆盖外两点必无边

4. 复杂度表

问题 证书 验证耗时 难度
CLIQUE 个顶点 NPC
VERTEX-COVER 大小 的 [PPT:4上:393] NPC
INDEPENDENT-SET 个顶点 NPC( 上的 Clique)
MAX-CLIQUE(求 ) — — NP-hard(优化版)

三者关系:、,所以团、独立集、顶点覆盖三兄弟同难。

5. 典型例题

Suppose CLIQUE is known NP-complete. Prove VERTEX-COVER is NP-complete.

中文逐步(严格照 PPT 结构 [PPT:4上:389-412]):

  1. ∈ NP:给定 ,证书取 。验证 ① ;② 对每条 检查 或 。总 。
  2. NP-hard:证 CLIQUE VERTEX-COVER。构造 ,。
  3. ⇒: 有 团 ⇒ 任何边至少一端在 (否则两端同在团内则…… 用 PPT 的表述:团内顶点两两相邻,故任意边不可能两端都在团的补集里)⇒ 是 的覆盖。
  4. ⇐: 有 覆盖 ⇒ 若 则 ⇒ 两两相邻 ⇒ 是 团。
  5. 结论:VERTEX-COVER ∈ NP 且 NP-hard ⇒ NPC。

6. 易错点 + 老师原话

  • 易错:顶点覆盖的 大小写反。团的大小 对应覆盖大小 ,必须换成 传进去。
  • 易错:只做"补集就是覆盖"的直觉陈述,不写双向证明。PPT 用了整整两段分 ⇒ 与 ⇐ 分别论证 [PPT:4上:401-412]——这就是评分点。
  • 易错:以为 Vertex Cover 是建"补图"。不是补图,是同一个 上取顶点集的补()。
  • 易错:3-SAT ≤p Clique 的跨子句连边规则写反(应连"不矛盾"的)。这是 4.1.12 的主例题。

7. 考试英文答题模板

VERTEX-COVER NP: for an instance take as certificate and in check and that every edge has an endpoint in . For NP-hardness reduce CLIQUE to VERTEX-COVER: map to . If has a clique of size , every edge has at least one endpoint outside , so of size is a vertex cover. Conversely, if of size covers all edges, then any two vertices outside cannot be adjacent, so is a clique of size . Hence the two problems are equivalent and VERTEX-COVER is NP-complete.


4.1.11 最短路径 vs 最长路径 【A 级 · 简答】

1. 一句话本质

最短路径的贪心/DP 之所以成立,是因为"最短路的最优子结构是局部的";而最长路径一旦要求"顶点不重复",就不再能局部决策——必须带着"已访问集合"这个全局状态走,状态数 ,于是爆掉。

2. 定义与性质

  • Single-source unweighted shortest-path:BFS, ⇒ P [PPT:4上:17-18]。
  • Single-source unweighted longest-path:PPT 并列在该表最后一行并注明 "No known algorithms are guaranteed to run in polynomial time" [PPT:4上:19-21]。
  • 形式对照 [PPT:4上:263-271]:抽象问题 SHORTEST-PATH 的解是路径序列 ;判定版 PATH 加参数 ,答案 。
  • 复杂度对照:PATH (Dijkstra/BFS/Bellman-Ford);LONGEST-PATH(要求 simple) NPC,因为 Hamiltonian path 是它的 特例。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《最短路》与《有向无环图》 · https://oi-wiki.org/graph/shortest-path/ 、 https://oi-wiki.org/graph/dag/

OI Wiki 先固定记号: 为点数、 为边数、 为源点、 为边权、 为 的实际最短路长度、 为算法维护的估计值,并明确「任何时候都有 ,算法终止时取等」。最短路指权和最小的路径,分单源与全源(每对结点)两类。DAG 是边有向且无环的图;其性质是「能拓扑排序 是有向无环图」,并在 DAG 上给出 的最长(短)路 DP:,按拓扑序更新。

2.2 OI Wiki 校订|基本性质

  1. 正权最短路不重复顶点、不重复边:若重复,则两次出现之间的那段是一个非负环,删去不增加长度。例:三角形 绕回起点,比直接停在 更长。
  2. 无负环时最短路可化为简单路径:故点数 、边数 ;这也是 Bellman–Ford 只需 轮松弛的理由。
  3. 最长路必须限定 simple:允许重复时,正环使其无界;全负权时简单路径自动最优。故「LONGEST-PATH」在复杂度语境下默认「简单路径」。
  4. 一般图最长简单路是 NPC: 时即哈密顿通路;但在 DAG 上只要 ,因为拓扑序排除了绕行。
  5. Dijkstra 必须非负:负边会破坏「出堆即定型」。例:、、, 先以 1 出堆,真实距离是 0。
  6. 算法分工:Bellman–Ford 允许负权(须无负环);Dijkstra 需非负;Floyd 求全源且允许负权。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 是算法页,给出 记号与正确性直觉,但不讨论 与 NPC;PPT 用 SHORTEST-PATH 与 LONGEST-PATH 的对照说明「只差一个 simple 约束,难度跨过 P/NPC 边界」。考试若问为什么最长路难,答案不是「没有算法」,而是「简单路径约束使最优子结构失效,状态必须带已访问集合,规模 」。术语上注意 OI 的「最短(长)路」在 DAG 语境允许重复边但无环,仍与一般图的 simple 要求一致。

3. 图示(为什么全局约束是杀手)

最短路:最优子结构✔dist(s,v)=minoveru{dist(s,u)+w(u,v)}→只依赖dist(s,u),不需要知道路径"经过了谁"→状态=(顶点)状态数O(V)⇒多项式最长简单路:最优子结构✘想把s→t的最长路拆成s→u+u→t?但更长的那条s→u可能已经用掉了v,导致u→t无法走v❌→状态=(当前顶点,已访问集合)状态数O(V·2^V)⇒指数────────────────────────────────────────────────若去掉"simple":图中无正环时最长路=-最短路上负权,P反之若有正环,最长路=∞,问题本身无意义⇒"不能重复顶点"是唯一让问题变难的东西

4. 复杂度表

问题 约束 算法 复杂度 类别
单源最短路(无权) — BFS P
单源最短路(非负权) — Dijkstra P
单源最短路(含负权) 无负环 Bellman-Ford P
最短路(全源) — Floyd P
最长简单路 顶点不重复 无已知多项式解 — NPC
最长路(DAG) 无环 DAG DP P
最长路(有正环,不要求 simple) — 无定义() — 无意义

表中 DAG 一行是关键对照组:只要有"无环"这个结构性保证,最长路又回到 P。这说明 NP 难度的来源不是"求最大",而是"简单性 + 有环"的组合。

5. 典型例题

Shortest paths are computable in polynomial time, whereas longest simple paths are NP-complete. Explain the difference.

中文逐步:

  1. 最短路满足最优子结构:任何最短路的子路径也是最短的,可以只用 一个标量做 DP/松弛;BFS/Dijkstra/Bellman-Ford 都在"顶点"这一层状态上收敛。
  2. 最长简单路不满足:一条更长的 路径可能已经占用了后续要用的顶点,"前缀最优"与"整体最优"冲突。要正确求解必须显式记住已访问集合 状态空间 。
  3. 复杂度对照:Hamilton path 是 LONGEST-PATH 在 时的特例 ⇒ LONGEST-PATH 是 NP-hard;又因证书(顶点序列)可多项式验证 ⇒ ∈ NP ⇒ NPC。
  4. 一句话根源:"不能重复顶点"是把局部问题变成全局约束的那一刀。

6. 易错点 + 老师原话

  • 易错:说"最长路更难,因为要取 max"。取 max 本身毫无难度( 的加减改号即可),难的是 simple 约束。
  • 易错:忘记 DAG 特例,把"所有最长路都 NPC"写死。
  • 易错:把 shortest simple path 也算成 NPC。最短路即使要求 simple,也不会有问题——因为负权环之外最短路自然不重复顶点;真正的差异在权重符号 + 环的组合。
  • 老师原话(同一问题的另一种问法):讨论题就是 "the longest Hamiltonian cycle" [稿:4-逐字稿:59],老师提示同学先想"怎么判定它是最长的",最后收敛到 「给定图 G 和整数 K 判断是否存在一个长度至少为 K 的简单环」 [稿:4-逐字稿:71]。

7. 考试英文答题模板

The shortest-path problem has optimal substructure with a local state: a shortest walk can be built from shortest sub-walks, so a single label per vertex suffices (BFS , Dijkstra, Bellman-Ford). The longest simple path lacks this property: a longer prefix to an intermediate vertex may have consumed vertices needed later, so optimality of a prefix does not extend to a full solution. The correct state becomes (current vertex, set of visited vertices), with states, which is why no polynomial algorithm is known. Formally, LONGEST-PATH with the requirement that the path be simple is NP-complete, since Hamiltonian path is the case ; the global "no repeated vertex" constraint is exactly what turns an easy problem into a hard one. (Note that on a DAG the longest path is again in P.)


4.1.12 NPC 证明三步法 【S 级 · 证明必考】

1. 一句话本质

① 它在 NP 里(能快验)→ ② 挑一个已知 NPC 的 Y → ③ 证 (从难指向它),并且双向证明等价。

2. 定义与性质(可套用模板)

┌─Step1:X∈NP──────────────────────────────────────────────┐│给出证书(certificate)的具体形式││写出多项式验证算法,并给出复杂度││(这一步漏了,后面全白做——老师:「它都不是np的话,││你就后头就不要走了」[稿:4-逐字稿:53])│├─Step2:选已知NPC的Y─────────────────────────────────────┤│首选3-SAT(子句规整,最易建图);图问题可挑Clique/││Vertex-Cover/Ham-Cycle;数值问题可挑Partition/TSP│├─Step3:Y≤pX──────────────────────────────────────────────┤│(a)构造f:把Y的实例映成X的实例,写出显式规则││(b)证明f是多项式时间(数清规模与时间)││(c)双向证明:││⇒Y有解⇒f(x)有解(completeness)││⇐f(x)有解⇒Y有解(soundness)│└───────────────────────────────────────────────────────────────┘

方向红线(第三次强调):Step 3 一定是 , 是已知 NPC、 是待证对象。若写成 ,你只证明了" 不比 易",而这句话在 已是 NPC 时是废话。

2.1 OI Wiki 校订|形式定义

来源:OI Wiki《计算复杂性》 · https://oi-wiki.org/misc/cc-basic/

OI Wiki 提供的是判据而非模板:NP-hard 指所有 类问题都能多项式时间规约到它;NPC 指既是 类问题又是 NP-hard。把这两句展开成证明步骤,就是三步法:① 证 ,即给出证书形式与多项式验证算法;② 选一个已知 NPC 的 (SAT、3-SAT、CLIQUE、VERTEX-COVER、HAM-CYCLE 等);③ 证 ,即构造多项式映射 并双向证明 。三步齐备才有 。

2.2 OI Wiki 校订|基本性质

  1. 三步缺一不可:漏 ① 只能得 NP-hard——停机问题就是反例,它 NP-hard 却不在 NP;漏 ③ 只能得 ——PATH 在 NP 却不难。只有 ①③ 同时成立才落进 。
  2. 方向唯一:第 ③ 步必须是 , 已知难、 待证。写成 只说明「 不比 易」,在 已是 NPC 时是废话,且无法推出 难。
  3. 双向证明不可省:只证 会放过「所有实例映到同一个 Yes 实例」的假归约;反过来只证另一向则会放过「全部映到 No」。
  4. 多项式性要数两处: 的计算时间与输出规模都必须是多项式。例:3-SAT CLIQUE 每个子句放 3 个顶点,共 个顶点、边数 ,构造 。
  5. 传递性支持链式搬运:SAT 3-SAT CLIQUE VERTEX-COVER HAM-CYCLE TSP,任一环断掉都能靠已知 NPC 续上;这就是「只需一个根」的原因。
  6. 证书与验证都要多项式:CLIQUE 的证书是 个顶点,验证 ;VERTEX-COVER 的证书是 个顶点,验证 。若证书长度指数,第 ① 步不成立。

2.3 OI Wiki 校订|与课堂口径的差异

OI Wiki 只给 NP-hard 与 NPC 的定义,没有三步法模板;PPT 的模板把「它都不是 np 的话,后头就不要走了」写成硬性顺序,并按 ①证书 ②选根 ③双向 组织答案。术语仍是「规约 / 归约」之别。答题建议:先用一句话写清 的实例格式与证书,再写 的映射规则,最后分 completeness 与 soundness 两段收尾。

3. 图示(完整演算:3-SAT ≤p CLIQUE)

实例:, 个子句。

 构造 G:
   顶点 = 每个出现过的字面量(共 3×3 = 9 个),按所属子句分三组
        C1:        C2:        C3:
        x1         ¬x1        x1
      ¬x2          x2         x2
        x3         x3        ¬x3

 连边规则:
   ① 同组内两两相连(无论字面量是否矛盾)
   ② 跨组 (u ∈ Ci, v ∈ Cj, i≠j):连边  ⟺  u 与 v 不互补
      即禁止连接 x 与 ¬x

 例:C1 的 x1 与 C2 的 ¬x1 不连;C1 的 x1 与 C2 的 x2 连。

 点击"取 K = 子句数 = 3":
   一个 3-团 ⟺ 从每组各取一个字面量,且两两不冲突
             ⟺ 存在一种真值指派,使每组所选字面量为真
             ⟺ Φ 可满足

双向证明(逐条写):

  • ⇒(可满足 ⇒ 有 团):设赋值 使 为真。则每个子句 中至少有一个字面量为真,从中选一个真字面量 。跨子句任意两点 ()不可能互补——因为 与 不可能同时为真。故这 个顶点两两相邻,构成 -团。
  • ⇐(有 团 ⇒ 可满足):设 -团存在( = 子句数)。团中不能有两个顶点同属一个子句(同组之外……准确地说:团恰好每组取一个,因为跨组连边只在不同组间成立,且 组最多各取一个才凑够 个,若某组取两个则必有另一组空出,而空出组的顶点与其他组的顶点……)——标准论证:-团中不能有同组两点(同组两点相同下标,不构成额外约束),但更关键的是:团中任何两点都不互补,因为互补的两个字面量不连边。于是可以把团中所有字面量同时置真(赋值 ),无冲突 ⇒ 每个子句至少有一个字面量为真 ⇒ 可满足。
  • 多项式性:顶点数 ( 为子句数),边数 ,建图 ⇒ 多项式。
  • 结论:CLIQUE ∈ NP(证书为 个顶点, 验证两两相邻)且 3-SAT CLIQUE ⇒ CLIQUE 是 NPC。

4. 复杂度表(各归约的构造代价)

归约 构造 代价 用途
SAT 3-SAT 子句拆分 得到规整的起点
3-SAT CLIQUE 3 组顶点 + 冲突边 逻辑 → 图
CLIQUE VERTEX-COVER 图 → 图
HAM-CYCLE TSP 有边赋 1、无边赋 2, 图 → 加权图 [PPT:4上:229-236]
3-SAT HAM-CYCLE 变元链 + 子句搭桥 逻辑 → 图(较难,一般只需会用)

TSP 那行的构造细节(PPT 原例,可能单独考) [PPT:4上:217-236]:

  1. 给定 (Hamilton 实例)。构造完全图 : 的边权 ,非边权 。
  2. 取 。
  3. ⇒:若 有 Hamilton 回路,则 中同样走法只经原边,总权 。
  4. ⇐:若 有总权 的巡回,它经过 条边,每条权 ,总权 ⇒ 每条权恰为 1 ⇒ 全走 的边 ⇒ 是 的 Hamilton 回路。
  5. TSP ∈ NP:证书是巡回顶点序列,检查"是排列 + 总权 ",多项式。

5. 典型例题(完整答题示范)

Prove that the Clique problem is NP-complete.

中文逐步(考场直接照抄行文):

  1. Statement:CLIQUE = { <G,K> : G has a clique of size K }。
  2. Membership:证书为 个顶点;验证 且对 中所有点对查边, ⇒ CLIQUE ∈ NP。
  3. Hardness:已知 3-SAT 是 NPC,证 3-SAT CLIQUE。
  4. Construction: 有 个 clause、每个恰 3 个字面量。建图 :顶点 出现在 ,按 clause 分组;同组两两连边;跨组 连边当且仅当 。取 。时间 。
  5. ⇒:满足赋值 ⇒ 每 clause 取一真字面量 ⇒ 所选 点两两不互补 ⇒ 有 -团。
  6. ⇐:-团 ⇒ 团中各点跨组且互不互补 ⇒ 同时置真得一致赋值 ⇒ 每 clause 满足。
  7. Conclusion:CLIQUE ∈ NP 且 NP-hard ⇒ NP-complete。

6. 易错点 + 老师原话

  • 易错(方向):写成 CLIQUE 3-SAT。老师第三次强调:「我们已知 a 是 npc……我们要证明 B 也是 npc,我们就得证明 a 比 B 要容易……你就要证明 a 可以在多项式时间里面归约到 B」 [稿:4-逐字稿:53]
  • 易错(漏 Step 1):「第一步,首先,B 你要证明 B 是在 np 里面的。它都不是 np 的话,你就后头就不要走了,就走不下去了。」 [稿:4-逐字稿:53]
  • 易错(单向):只证"Y 有解 ⇒ X 有解"。必考的是双向,因为要的是 可解 可解(否则 不构成归约)。
  • 易错(构造缺失):写"可以构造一张图,显然……"。必须给出显式规则:顶点是什么、边怎么连、 取多少、规模多大。
  • 易错(原点选错):挑一个还"未知是否是 NPC"的问题当 。 必须已知 NPC(SAT / 3-SAT / Clique / Vertex-Cover / Ham-Cycle / TSP)。

7. 考试英文答题模板(整段可背)

Let be the problem X = { <I> : ... }. We prove it NP-complete in three steps. (1) . A certificate is [structure]; the verifier checks [conditions], which takes time, polynomial in the instance size. (2) Choose a known NP-complete problem (e.g. 3-SAT) and construct a polynomial-time reduction . (3) Reduction. Given an instance of , build as follows: [explicit vertex/clause/edge construction, with the value of ]. The construction runs in time. () If has a solution [describe], then [the selected objects are pairwise compatible], so has a solution. () If has a solution [describe], then [no two chosen objects conflict], so [an assignment/set satisfying is recovered]. Hence is a yes-instance iff is a yes-instance. Since and with NP-complete, is NP-complete.


附:本讲考点的"一句话自检表"

编号 自检问题 标准答案
4.1.1 NPC 的完整定义? ∈NP 且所有 NP 它;NPC NP NP-hard
4.1.2 NP-hard 必须在 NP 里吗? 不必;Halting 是反例
4.1.3 谁更难? B(箭头指向难处);证 是 NPC 要证
4.1.4 归约定义里的关键符号? , 多项式时间
4.1.5 max 型怎么转? 问 ;min 型问
4.1.6 能在优化层面比难度吗? 不能,必须转到判定层面
4.1.7 停机问题属于哪类? Undecidable,NP-hard 但 ∉NP
4.1.8 欧拉 vs 哈密顿? 边一次 = P(偶度判据);点一次 = NPC
4.1.9 3-SAT 怎么来? SAT 子句拆分成恰 3 字面量,引入新变量
4.1.10 团与顶点覆盖的恒等式? 大小 的团 大小 的覆盖
4.1.11 最长路为何难? "不能重复顶点"是全局约束,状态数
4.1.12 三步法的第三步方向? , 已知 NPC,且双向证明

证据索引(教师原话集中处)

  • 难度谱 / "hardest 有限定语":[稿:4-逐字稿:17]
  • NP = easy to verify:[稿:4-逐字稿:23-26]
  • NPC 定义 + 归约方向口头测验(同学答 B):[稿:4-逐字稿:29-32]
  • NP-hard 可能在 NP 外:[稿:4-逐字稿:32]
  • co-NP 与 formal language 的作用:[稿:4-逐字稿:35]
  • decide vs accept:[稿:4-逐字稿:41]
  • NPC 证明三步的第一步(必须 ∈NP):[稿:4-逐字稿:53]
  • 最长 Hamilton 回路讨论题:[稿:4-逐字稿:59,71,92]
  • 必须在判定层面谈归约 / 引入 K 的口诀:[稿:4-逐字稿:110,113,116]
  • 纪要汇总:[纪要:4-conclusion:1](四个类与归约方向)、[纪要:4-conclusion:2](优化→判定)
  • PPT 核心页:四类定义 [PPT:4上:311-334]、NPC 定义与归约 [PPT:4上:359-372]、TSP 例 [PPT:4上:217-236]、Vertex-Cover 例 [PPT:4上:377-412]、Halting [PPT:4上:48-65]
4.2.1 – 4.2.13

详解 08 · 近似算法

配套 A0_速查册.md 的「4 下」部分。速查册给结论,本册给推导、图示与证明全过程。 证据约定:[PPT:4下:n] = 01_原始素材/PPT_文本/ADS/第四讲(下)Approximation.txt 行号;[稿:4-逐字稿:n] = 课堂录音文稿/4-逐字稿.md 行号;[纪要:4-conclusion:§] = 课堂录音文稿/4-conclusion.md 小节。 术语一律「中文(English)」首现双写,考试按 English 列作答。


0. 本讲逻辑主线

上半讲把世界切成两半:容易(P)/难(NPC、NP-hard)(4.1)但"难"不等于"不解决"——难问题有几种绕法:①实例小⇒指数算法也行,O(2^N)②只解重要的特殊情形,多项式时间求最优③多项式时间给"近似最优"←←←本讲focused的路线[PPT:4下:10-21]→近似算法凭什么"配"叫近似:必须能证ρ(n)上界(4.2.1–4.2.2)→想更逼近1:approximationscheme,PTAS/FPTAS(4.2.3–4.2.4)→主战场:BinPacking(装箱,NP-hard)On-line:NextFit≤2M−1(紧界)(4.2.5)├课堂讨论:S_i≤α<1/2时ρ=1/(1−α)(4.2.6)└α=1/2恰是1/(1−α)与常数2的交点(4.2.7)FirstFit/BestFit≤17/10(4.2.10)Off-line:FFDρ=3/2(真ratio,不是11/9)(4.2.8)└3/2最优:ρ<3/2⇒P=NP(4.2.9)→K-center:2-近似;下界:ρ<2不存在(除非P=NP)(4.2.11–4.2.12)→全课程收尾:算法设计三目标A/B/C不可兼得(4.2.13)

老师对本讲的定位:「approximation 是说我们碰到难问题以后不是说就不解决了,虽然要等一个精确解等到有生之年等不出来,我们可以凑合,有很多种方法可以凑合,近似算法是其中一个大方向。」 [稿:4-逐字稿:14]

这一讲的教学重心不在"发明算法",而在三件事:给算法贴上 ratio 标签的资格线、证明 ratio 上界的固定套路、证明 ratio 已到顶(=P=NP)的反证套路。三条套路共用一套逻辑,最后一节把它们收进全课程的总图。


4.2.1 近似比 ρ(n) 的定义 【S 级 · 填空/简答必考】

1. 一句话本质

近似比是"最坏情况下你的解比最优解差多少倍"的一个上界——它是对所有规模为 n 的输入取最大,不是对某个例子取平均,也不是"平均误差"。

2. 定义与性质

【Definition】An algorithm has an approximation ratio of if, for any input of size , the cost of the solution produced by the algorithm is within a factor of of the cost of an optimal solution:

If an algorithm achieves an approximation ratio of , we call it a -approximation algorithm. [PPT:4下:29-37]

拆成五条性质,逐条都能出判断题:

# 性质 说明
① 恒成立 因为 max 里含 的可能性;若取到 则
② 双向比值 既管"解不太差"(),也管"解不会好过最优"()。对最小化问题 ,故 ,max 退化为
③ 对 n 一致(uniform) 是 作为 的函数,不是对某个实例的比值
④ 最坏情形(worst-case) 不涉及概率分布,与"期望意义下"完全不同
⑤ 目标是把 压向 1 越接近 1 越好; 即精确算法

老师点明 ②⑤ 的用意:「近似解跟精确解差多少倍,你谁在上头,谁在下头都无所谓的,让它有一个上界就是了。然后我们理想状态下 ratio 尽可能趋近于一……总之目标是让它逼近一。」 [稿:4-逐字稿:128]

对最大化问题(如 knapsack 最大化 profit),,故 max 退化为 ;对最小化问题(如 bin packing 最小化箱数),,max 退化为 。做题时先判断问题方向,再决定分子分母谁在上——这是第一个高频扣分点。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

设最优化问题 的实例 编码长度为 ,可行解集为 ,代价函数为 ;记 为最小化问题的最优值(最大化问题取 ),算法 在 上输出 ,。若存在函数 ,使对每个满足 的实例都有

则称 为 -近似算法(-approximation algorithm),并把满足上式的最小 称为 的近似比(approximation ratio)。最小化问题有 ,双向 max 退化为 ;最大化问题有 ,退化为 。由此 恒成立,且 当且仅当 在每个规模 的实例上都返回最优解。界对实例一致(instance-independent),只随 变化,故 是 的函数而非某个实例上的商。

2.2 OI Wiki 校订|基本性质

  1. 最小化问题必有 。 理由: 的输出是可行解,而 是全体可行解代价的下确界。最小实例:容量为 1,物品 ,Next Fit 两两都装不下,得 4 箱;最优为 与 共 2 箱, 成立。
  2. 最大化问题必有 。 理由与上一条对称:最优值是可行解代价的上确界。最小实例:0-1 背包容量 10,物品 ,最优取后两件得 16;按利润密度先取 后余量装不下任何一件,。
  3. 等价于该规模上精确。 理由:两个比值互为倒数,同时不超过 1 只能同时等于 1。最小实例:单件物品利润 7,任何可行输出都是 7,比值为 1。
  4. 两级复合的比是相乘而非相加。 理由:第一级把最优值放大不超过 倍,第二级在该中间值上再放大不超过 倍,逐级传递即得上界;加法只能描述常数项,不能描述比值。最小实例:、、,则 ,而按加法 会给出 14 箱的错误上界。
  5. 单个实例只能给下界,不能否定上界。 理由: 是对同一规模全部实例取上确界,一个实例的商只是该上确界的一个候选值。最小实例:列表 上 Next Fit 得 3 箱、最优 2 箱,商 ,它只给出 ,与 不冲突。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 上确界是否写出。 CLRS 口径把 说成对任意规模 的输入都成立的不等式,等价于对上确界成立;课堂 PPT 只写 一句,没有提上确界。考试按 PPT 的不等式作答,追问时再补「取最坏实例」。
  2. 是否依赖 。 CLRS 允许 随 变化,例如 ;课堂全程只用常数比。填空问「 是什么的函数」须答输入规模 ;具体算题按常数处理。
  3. 的含义。 OI Wiki 无近似算法条目,但其《复杂度》页明确「 为输入的长度」,可用来支撑 的自变量是编码长度而不是数值大小;课堂在 4.2.4 正是用这一点把 判成指数。考试按编码长度理解这里的 。
  4. 最优值的记号。 CLRS 用 或 ,课堂从 4.2.5 起改用 表示最优箱数,三者同义;同一段证明里不可混写出两个符号。考试统一写 。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;输入规模取编码长度见 OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

3. 图示(ratio 的几何含义)

cost│✗ρ太大:解质量差│┌───C(算法解)│││─────┼─────C*(最优解)││└────────┴────────────────▶实例ρ=C/C*⇔"算法解最多是最优解的ρ倍"────────────↑对每个实例都成立,取最大────────────ρ(n)曲线示意:ρ2─┤●│╲1.5┤╲___│╲─────(好的近似算法)1─┤────────────────渐近线(不可达,除非多项式解出NPC)└────────────────▶n

4. 复杂度表

概念 形式 计算难度 判据
精确算法 通常指数 / NP-hard 给出
-近似算法 多项式 需证明对所有实例成立
启发式 heuristic 无 多项式 无证明,不算近似算法
近似方案 scheme 固定时多项式 见 4.2.3

5. 典型例题

Q. An algorithm for the minimum bin packing problem may use up to 17/10 times the optimal number of bins. For an instance whose optimal packing uses 10 bins, what is the guaranteed upper bound on the number of bins produces? If actually produced 7 bins, is the ratio bound violated?

中文逐步:

  1. 判定问题方向:最小化 ⇒ ,。
  2. ⇒ 。保证上界 17 箱。
  3. 输出 7 箱:,矛盾——因为最优是 10,任何可行解不可能只占 7 箱,故 7 不可能是可行解的输出(要么 记错,要么输出非法)。
  4. 若强行代入比值检验:,双向比值仍满足——这正说明只看单向会漏检,只有双向 max 才能表达"解不会好过最优"这一物理约束。

6. 易错点 + 老师原话

  • 易错:把 当成"平均误差"或"典型误差"。定义是 for any input,最坏情形。
  • 易错:最小化问题写成 ,方向反了。做题第一步先问"目标是最小还是最大"。
  • 易错:认为 可以小于 1。老师:"rho 能小于 1 吗?近似算法比最优解能更好吗?不可能。" [稿:4-逐字稿:311](这句同时是 4.2.7 的杀手锏)
  • 易错:把 里的 理解成数值大小。 是输入规模(元素个数/编码长度),不是数值本身——这条在 4.2.4 会升级成陷阱。

7. 考试英文答题模板

An algorithm has approximation ratio if for every input of size , , where is the cost of 's solution and that of an optimal solution. The two-sided maximum makes the definition valid for both minimization and maximization problems and forces . is then called a -approximation algorithm. The bound is a worst-case, instance-independent guarantee: it must be provable for all inputs of size , not merely observed on examples.


4.2.2 近似算法 vs 启发式 【S 级 · 判断高频易错】

1. 一句话本质

两者都可以是"贪心试一把",分界线只有一条:能不能严格证明 的上界。能证 → approximation;证不出 → heuristic。

2. 定义与性质

Approximation algorithm Heuristic
解的质量 有可证明的上界 无保证
时间复杂度 多项式 通常多项式
是否需 worst-case 分析 必须 无
能否叫 "-approximation" 能 不能
典型例子 NF / FF / FFD / K-center 贪心 模拟退火、遗传算法、局部搜索

关键在"可证明"三个字。 是可证的,所以 FFD 是近似算法;"按利润密度贪心的 0-1 背包"虽然也给出界 2,也是近似算法(因为界可证)。反过来,一个跑得很快、实测效果很好的启发式,只要给不出对所有实例都成立的 ,就不能写成 "-近似算法"。

老师原话:「heuristic 一个算法被称为启发式算法……不管效果,是我不知道效果怎么样,很难分析。那 approximation 是有近似解的质量方面的讨论的,它关注解离最优解到底差多少倍。凡是能够具体的分析出来 ratio 的,它才配叫做 approximation,否则它不配,它只能被叫做 heuristic。」 [稿:4-逐字稿:125-128]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

设 是求解最优化问题 的算法类。算法 称为近似算法(approximation algorithm),若同时满足两条:(i) 在多项式时间内停机;(ii) 存在常数 ,使对 的每个实例 都有 ,且该不等式有覆盖全部实例的证明(不依赖输入分布与实测样本)。只满足 (i) 的算法称为启发式(heuristic)。因此「近似」是一个带证书的质量承诺,「启发式」只是可行解的生成过程;两者可以共用同一段贪心代码,差别只在能否给出 。

2.2 OI Wiki 校订|基本性质

  1. 启发式没有可证上界,故不构成近似算法。 理由:定义要求存在对所有实例成立的 ,启发式连一个候选 都拿不出,存在性命题为假。最小实例:模拟退火在 上可能给出 2 箱,换一个随机种子在 上可能给出 3 箱,两次都不违反任何界,因为根本没有界。
  2. 近似算法必须多项式,否则精确枚举也满足 。 理由:若去掉时间限制,穷举可行解即得 ,定义将失去区分力。最小实例:4 件物品的 0-1 背包枚举 个子集必得最优,比值 1;但 时 不可行,故它只是精确算法而非近似算法。
  3. 比值证明覆盖全部实例,一个反例即可推翻。 理由:全称命题的否证只需一个反例。最小实例:若声称 Next Fit 是 -近似,取 得 ,一句话即推翻。
  4. 实测效果好不等于可证。 理由:基准测试是有限样本,界是全称命题,有限个成功实例推不出全称结论。最小实例:某启发式在 1000 个随机实例上平均比值 1.02,但把物品都取 时每件独占一箱,比值升到 2,平均值约束不了最坏值。
  5. 上界允许取等,输出最优不违反近似性。 理由:定义用的是单向不等号。最小实例:物品 只需 1 箱,Next Fit 也只开 1 箱,,与 相容。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 资格线的表述。 CLRS 第 35 章把「多项式时间」与「可证比值」并列为定义的两条;课堂用「配不配叫 approximation」的口语说法,判据相同。考试按两条定义作答,不要只写「能证明」而漏掉多项式。
  2. 精确算法的归类。 CLRS 形式上承认 ,但把它单列为精确算法;课堂同样说 即精确解。判断题若说「 的算法一定是近似算法」,应判错,理由是其时间复杂度未必多项式。
  3. 无 OI 条目可引。 OI Wiki 只有复杂性类与复杂度记法,没有启发式或近似算法的条目;本考点不引 OI,以免与 OI 的随机化、剪枝混淆。
  4. 多项式的时间口径。 OI 与 CLRS 都以编码长度为自变量;课堂到 4.2.4 才展开这一点。本考点答题写 即可,不必提前引入 。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(资格线)

所有多项式时间算法(针对NPC问题)┌───────────────────────────────────────────────┐│heuristic(无质量承诺)││┌───────────────────────────────────────┐│││approximation(能证ρ上界)││││NFρ=2·FF1.7·FFD1.5·KC2│││└───────────────────────────────────────┘││模拟退火/遗传/局部搜索/各种"试一把"│└───────────────────────────────────────────────┘资格线="∃可证明的ρ(n)",不是"跑得快",也不是"跑得好"

4. 复杂度表

对象 时间 质量保证 可写入论文的表述
Exact(non-poly) "exact algorithm"
PTAS/FPTAS ( 固定) "-approx"
-approx "-approximation"
Heuristic 无 "heuristic",不许写 ratio

5. 典型例题

Q. (T/F) (a) Any polynomial-time heuristic for bin packing is a 2-approximation algorithm. (b) An algorithm whose solution quality cannot be analyzed may still be called an approximation algorithm if its empirical error is small. (c) A 2-approximation algorithm may output the optimal solution on some instances.

中文逐步:

  1. (a) 假。FF 实际是 1.7 近似,但"任意启发式"没有可证的 ;存在 Any-Fit 类算法在任何常数比上都可能失效(更别说 adversarial 输入)。判据是"能否证明"而非"跑得快"。
  2. (b) 假。定义的第二条腿是可证明的界,实测误差不进定义。这正是老师强调的"配不配"。
  3. (c) 真。 是上界,不是下界; 并不排斥 。事实上 NF 在最优为 1 箱时必须恰好 1 箱。

答案 (a) F, (b) F, (c) T。这题考的就是"上界是上界,不是等式"。

6. 易错点 + 老师原话

  • 易错:把"贪心算法"一律等同"近似算法"。贪心只是手段;Scheduling 的贪心、MST 的 Prim 都是精确算法,不需要 ratio。
  • 易错:把"近似算法"理解成"近似地跑一个算法"(如浮点误差)。这是数值逼近(numerical approximation),与组合优化里的 approximation algorithm 完全无关。
  • 易错:把实测效果(benchmark 跑得好)当作 quality guarantee。
  • 判断技巧:题干出现"cannot be analyzed / no worst-case bound / empirically good" → 一律归为 heuristic。

7. 考试英文答题模板

An approximation algorithm for an optimization problem is a polynomial-time algorithm for which a provable worst-case bound on can be established for every input of size . A heuristic is a polynomial-time procedure with no analyzable quality guarantee; its performance may be good in practice but cannot be certified. Hence the dividing line is provability of the ratio, not running time and not empirical accuracy. An exact algorithm for a special case, or a greedy algorithm that happens to be optimal (e.g. Prim's MST), needs no ratio at all.


4.2.3 PTAS vs FPTAS 【S 级 · 选择必考】

1. 一句话本质

近似方案(approximation scheme)把 从常数降到 ,代价是时间随 增长。PTAS 只要求对固定 是 ;FPTAS 额外要求对 也多项式——"Fully" 的那个 Fully 就是"连精度也算多项式"。

2. 定义与性质

【Definition】An approximation scheme for an optimization problem is an approximation algorithm that takes as input not only an instance of the problem, but also a value such that for any fixed , the scheme is a -approximation algorithm. We say it is a polynomial-time approximation scheme (PTAS) if for any fixed , the scheme runs in time polynomial in the size of its input instance. [PPT:4下:38-44]

形式化对比(这是选择/填空的标准答法):

类型 时间函数 判据 允许发散方向
PTAS 或 固定 ⇒ 对 可指数、可任意快
FPTAS 对 且 对 都多项式 两边都管住

PPT 直接给出的两个样本 [PPT:4下:45-47]: 与 都属于 FPTAS 那一侧(PPT 把这两个标在 FPTAS 之上)。

一句话记忆:FPTAS = PTAS + "对 也多项式"; 记成 "Fully polynomial in both and "。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

近似方案(approximation scheme)是以实例与精度 为输入、对每个固定 输出 -近似解的算法族 。记其时间上界为 ,其中 是实例的编码长度。

  • PTAS(polynomial-time approximation scheme):对每个固定 , 是 的多项式,即存在只依赖 的 使 。
  • FPTAS(fully polynomial-time approximation scheme): 是 与 的二元多项式,即存在常数 使 。

包含关系为 ,且 是精度参数,不是实例数据的一部分。

2.2 OI Wiki 校订|基本性质

  1. FPTAS 严格强于 PTAS。 理由:二元多项式在固定 下当然是 的多项式,反向不成立,且在 下确有只有 PTAS 而无 FPTAS 的问题(如极小化最大完工时间的平行机调度)。最小实例: 在 时为 ,属 PTAS; 时为 ,幂次随 增长,不是 与 的二元多项式。
  2. 固定 时 PTAS 一定是 的多项式。 理由:定义只要求每个固定精度对应一个多项式,指数可以含 。最小实例:,取 得 ,取 得 ,都是三次。
  3. 判据是 在底数还是在指数。 理由:在底数或分母上, 对两个变量都是幂;在指数上, 增大会抬高 的幂次。最小实例: 在 时只放大 1000 倍,属 FPTAS; 在同一 下把次数抬到 1000,只属 PTAS。
  4. 两者都不是。 理由:固定 后 对 仍是指数。最小实例: 时退化为 , 约为 步。
  5. 的编码方式会改变「对长度是否多项式」的判断。 理由:FPTAS 的定义写 而不写「输入长度的多项式」,正因为二进制下 可达 。最小实例:取 、时间 ;一进制要写 位, 对长度 是多项式,二进制只写约 100 位, 对长度 是指数。

2.3 OI Wiki 校订|与课堂口径的差异

  1. PTAS 的时间表述。 CLRS 写「对任意固定 时间为 的多项式」,未限定 的编码;PPT 直接给 与 两个样本。考试按「固定 是否为 」作答。
  2. FPTAS 的表述。 CLRS 写 ,课堂用「对 分之一也是多项式」的口语说法,两者等价;答卷写 最稳。
  3. 是否算输入。 OI Wiki 无 PTAS 条目,但其《复杂度》页把 定义为输入长度,可支持「 只计实例编码长度」这一辨析;课堂未讨论编码方式,属超出 PPT 的补充,判断题不考。
  4. 记号。 PPT 与 CLRS 都用 ,部分教材写 或用 代替精度,含义相同。考试统一写 。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;输入长度定义见 OI Wiki《复杂度》 · https://oi-wiki.org/basic/complexity/

3. 图示(曲线族)

时间│PTAS家族FPTAS家族│n^{1/ε}n³/ε²│╱╱│╱(ε变小时陡到吓人)╱(ε变小时只按多项式涨)│╱╱│╱╱└──┴──────────────▶1/ε────┴──────────────▶1/εε固定⇒两者都是poly(n)ε→0⇒PTAS可能爆炸(如2^{1/ε}),FPTAS不会

4. 复杂度表(四个必背样本)

时间 固定 时 对 归类 判据一句话
✓ 多项式 ✓ FPTAS 分母线性,两变量都多项式
✓ 多项式 ✓ FPTAS PPT 原样标注
✓(次数 是常数) 指数级 ✗ PTAS only 指数在 的指数位上
✓(线性) 指数级 ✗ PTAS only 指数因子与 相乘,但含

判据的统一口径(务必背死这句):把时间写成 与 的二元函数后,只看 出现在指数上还是底数上:

  • 出现在底数/系数(、)⇒ 两维都多项式 ⇒ FPTAS;
  • 出现在指数(、)⇒ 只保证 ⇒ PTAS。

注意 与 在"固定 是 "这一点上都成立,所以都是 PTAS;差别只在"当 是输入的一部分时是否还是多项式",两者都不是。因此两行都写 "PTAS only"。

5. 典型例题

Q. Classify each running time as (i) FPTAS, (ii) PTAS but not FPTAS, or (iii) neither: (a) (b) (c) (d)

中文逐步:

  1. (a) 在分母 ⇒ 两个变量都多项式;固定 是 ⇒ FPTAS。
  2. (b) 固定 (如 )⇒ ,是 ⇒ 是 PTAS;但 在指数上, 时爆炸 ⇒ PTAS,非 FPTAS。
  3. (c) 固定 ⇒ 常数 , ⇒ PTAS; 在指数上 ⇒ PTAS,非 FPTAS。
  4. (d) 固定 得 ,对 指数 ⇒ 连 PTAS 都不是,(iii) neither。
  5. 归纳陷阱:(d) 最容易误判成 PTAS。检验方法只有一句——"固定 之后, 在指数上吗?" 在 ⇒ 淘汰。

6. 易错点 + 老师原话

  • 易错:以为 不是 PTAS(因为"有指数")。错:PTAS 只要求"固定 时 ",指数落在 上不算违约。老师:「对于 epsilon 分之一也是多项式……就叫做 FPTAS……首先努力设计一个 PTAS 出来,然后看看有没有可能把它改善成一个 FPTAS,也算是一个交代。」 [稿:4-逐字稿:131-137]
  • 易错:认为 越大越精确。 是误差容忍度:, 越小越精确、时间越长。
  • 易错:把 PTAS 的 "polynomial" 理解成"对 也多项式"。那是 FPTAS。
  • 记法:先写 ,圈出 的位置——底数/分母 = F,指数 = 只有 P。

7. 考试英文答题模板

An approximation scheme takes and returns a -approximate solution. It is a PTAS if for every fixed its running time is polynomial in ; the dependence on may be arbitrary, e.g. or are valid PTAS. It is a FPTAS if the running time is polynomial in both and , e.g. or . Equivalently, may appear in the base but not in the exponent. Hence , and is neither.


4.2.4 参数陷阱:与"数值"成正比即指数级 【S 级 · 判断易错】

1. 一句话本质

复杂度是输入编码长度的函数,不是输入数值的函数。写成 时若把 当常数,就误把伪多项式(pseudo-polynomial)当成了多项式—— 用二进制写只需 位, 本身是 级。

2. 定义与性质

0-1 背包的记忆化 DP(PPT 版本):

时间 [PPT:4下:280]。这里 。

  • 输入规模 ,记作 (bit 数)。
  • 本身可以达到 。于是 ——对 指数。
  • 术语:这类算法叫 pseudo-polynomial algorithm(伪多项式)。它"看起来多"项式,实际随数值指数。

老师原话:「跟某一个参数本身成正比的话,意味着时间复杂度是指数级的……你如果跟 本身成正比,意味着跟 是一个指数级,是 次方。就是 input size,我们说复杂度的时候是 input size 的一个函数。它的 input size 是什么?它的字节数,那编码长度。所以这里头大家一定要搞清楚一点:参数不是常数。」 [稿:4-逐字稿:512]

PPT 给的对照表 [PPT:4下:287-295]:左边 ,右边只是把利润整体除以约 。因为 极大会让 DP 表尺寸爆炸,才有 4.2.3 的"缩放利润(round all profit values up to lie in smaller range)"技巧——用放大/缩小 换来 的误差,把 压成 FPTAS。中间那层的条件 ,其中 是精度参数 [PPT:4下:298-300]。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

设实例的二进制编码长度为

其中 为物品件数, 为利润, 为重量, 为容量。若算法时间为 且 ,则因 而有 ,对编码长度是指数。一般地,若时间以某个输入数值的多项式为上界,而不是以编码长度为多项式,则称该算法为伪多项式时间(pseudo-polynomial time)算法。若 NP-hard 问题存在伪多项式算法,称其为弱 NP-hard(weakly NP-hard);若在 下不存在伪多项式算法,称其为强 NP-hard(strongly NP-hard)。

2.2 OI Wiki 校订|基本性质

  1. 对 是指数。 理由: 的二进制位数是 ,故 。最小实例: 的二进制为 ,共 20 位,而 ,两者同阶。
  2. 伪多项式存在不推出 。 理由:伪多项式只对数值多项式,对编码长度仍是指数,不构成多项式算法。最小实例:、 时约 次运算尚可运行;把 改成 只多约 20 位,运算量却升到 。
  3. 一进制编码下同一算法才是多项式。 理由:一进制下 , 对长度线性。最小实例: 一进制写 8 个 1,长度 8,运算量与长度同阶;二进制只写 1000,长度 4,运算量仍是 8。
  4. 缩放利润可把伪多项式改成 FPTAS。 理由:把 换成 后表的列数降为约 ,取 得 ,代价是 的误差。最小实例:、、 时 ,列数从 降到 。
  5. 强弱之分看有无伪多项式算法。 理由:强 NP-hard 的定义就是排除了伪多项式算法这一整类。最小实例:0-1 背包有 的伪多项式算法,属弱;装箱的判定版由 3-Partition 归约而来,无伪多项式算法,属强。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 强弱 NP-hard 的命名。 OI Wiki《计算复杂性基础》用「是否有伪多项式算法」定义弱/强 NP-hard,与 CLRS 一致;PPT 只说「跟 成正比就是指数级」,没有给出名词。考试问名词时写 weakly/strongly NP-hard 并附定义。
  2. 伪多项式的定义方式。 OI Wiki 写「用时是值域的多项式」,CLRS 举例 ;两者同指一事,但 OI 强调「伪多项式也可能同时是多项式」。答题按「对编码长度指数」表述更稳。
  3. 编码长度的记号。 CLRS 用 或 ,课堂用「字节数、编码长度」,OI 用 表示输入长度;本节写 ,与前文一致。考试统一写 。
  4. 按重量 DP 是否算伪多项式。 课堂把 与 并列都叫伪多项式,CLRS 只详述按利润的版本。考试两者都算伪多项式,但 FPTAS 的标准路径是按利润缩放。

来源:OI Wiki《计算复杂性基础》 · https://oi-wiki.org/misc/cc-basic/ (伪多项式与强/弱 NP-hard 见该页「多项式时间」一节)

3. 图示(数值 vs 编码长度)

p_max的两种写法:一元编码111...1长度∝p_max→O(n²p_max)是"多项式"二进制110110001010...长度∝logp_max→O(n²p_max)=O(n²·2^D)指数!真实世界:计算机存p_max用二进制⇒必须用D计复杂度────────────────────────────────────────────────DP表尺寸(列数)=总的profit上界≈n·p_max├─p_max中等⇒可跑└─p_max=10^7⇒表10^7列×n行⇒爆内存/爆时间⇒用缩放(shrinkprofits)→近似→FPTAS

4. 复杂度表

算法 时间 对 (bit)而言 分类
0-1 背包 DP by profit 指数 伪多项式,不是 poly
0-1 背包 DP by weight 或 指数 伪多项式
分数背包(profit density 贪心) poly 精确
缩放利润 取 ⇒ FPTAS
DFS 枚举 0-1 背包 指数(真) 非多项式

5. 典型例题

Q. A dynamic program for 0-1 knapsack runs in where is the largest profit. A student claims: "Since is bounded by a constant for the given test data, the algorithm is polynomial." Is the claim correct? What is the correct characterization?

中文逐步:

  1. 该同学用"给定数据的 是常数"来论证。复杂度定义必须对所有输入统一定义,不能对某组数据把参数当常数。
  2. 正确口径: 可达 , ⇒ 对编码长度 指数。
  3. 结论:该算法是 pseudo-polynomial,不是多项式时间算法;因此 0-1 背包的"DP 解法"并不证明 (这一点是判断题最爱)。
  4. 若因此说"0-1 背包有多项式算法" ⇒ 错,0-1 背包仍是弱 NP-hard(weakly NP-hard)。
  5. 正确补法:想真正 就要缩放 ,付 的误差代价——得到 FPTAS。

6. 易错点 + 老师原话

  • 易错:把 归为多项式,理由" 是常量"。参数不是常数。
  • 易错:把"伪多项式存在"推成 。伪多项式只说明问题是弱 NP-hard;强 NP-hard(如 TSP、装箱)连伪多项式也没有。
  • 易错:混淆"数值大小"与"输入规模"。 是元素个数, 是bit 数, 是数值。三者不同。
  • 记法:凡是复杂度中以某个输入数值本身(不带 log)为因子的,先怀疑伪多项式。

7. 考试英文答题模板

Running time must be measured in the size of the input encoding, , not in the numeric values themselves. An algorithm running in is pseudo-polynomial: since can be as large as , its true complexity is exponential in . It is therefore not a polynomial-time algorithm and does not imply ; 0-1 knapsack remains weakly NP-hard. To obtain a genuine polynomial-time guarantee one must round ("shrink") the profits, which introduces a error and yields an FPTAS in with .


4.2.5 Next Fit: 及紧例 【S 级 · 证明必考】

1. 一句话本质

Next Fit 只回看当前这一箱,装不下就永久封箱。因为它从不回头,它的"浪费"是有结构的:任意相邻两箱的容量之和必然超过 1(否则第二个箱的第一个物品本可以塞进第一箱)。把这个结构两两配对,就得到 。

2. 定义与性质

【Theorem】Let be the optimal number of bins required to pack a list of items. Then next fit never uses more than bins. There exist sequences such that next fit uses bins. [PPT:4下:82-84]

算法(在线)[PPT:4下:69-78]:

void NextFit() {
    read item1;
    while (read item2) {
        if (item2 can be packed in the same bin as item1)
            place item2 in the bin;       // 留在"当前箱"
        else
            create a new bin for item2;   // 封掉当前箱,永不回头
        item1 = item2;
    }
}

三条性质:

  • 在线(online):边读边放,不改变已做决定 [PPT:4下:162-164]。
  • 只维护一个开箱 ⇒ 空间 、时间 。
  • 常数 2 量级:,故 ,渐近为 2。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

Next Fit 的不变量:任意时刻至多一个箱处于打开状态,其余箱已封。读入物品 时,若能放入当前箱则放入,否则封当前箱并以 开新箱,已封的箱不再回看。设输出为 个箱 , 为第 箱的物品总量,则对每个 有

因为开 的首件 放不进 ,即 ,而 。把前 箱两两配对即得 ,其中 为最优箱数。

2.2 OI Wiki 校订|基本性质

  1. 相邻两箱之和严格大于 1。 理由:开箱首件塞不进前一箱,而首件不超过后一箱的总量。最小实例:物品 ,Next Fit 得两箱,。
  2. 。 理由:若 ,前 箱配成 对,每对之和大于 1,总重超过 ;但最优用 箱装完全部物品,总重不超过 ,矛盾。最小实例: 时总重不超过 1,若出现两箱则相邻和大于 1 已矛盾,故 。
  3. 这是箱数界不是比值。 理由: 是仿射式,除以 才得到 的比值形态。最小实例: 时上界 19 箱,比值是 而非 19; 增大时比值趋于 2。
  4. 时间 、空间 。 理由:每件只与当前箱比较一次,只存当前箱的剩余容量。最小实例: 件只需 1000 次比较和一个剩余量变量,与箱数无关。
  5. 界是紧的。 理由:需要构造使每对相邻箱之和刚刚超过 1 的序列。最小实例():,Next Fit 逐件开新箱得 3 箱,最优为 与 共 2 箱,恰为 ,比值 。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 结论的出处。 CLRS 第 35 章讲装箱时以 First Fit 与 FFD 为主,没有 这条定理,它来自课堂与 Weiss 教材;考试按 PPT 的定理与配对证明作答。
  2. 「界」与「ratio」的区分。 课堂强调 要化成 才是比值,CLRS 只用 记最优值、不讨论这个转换。答卷先写箱数界,再补一句除以 得比值。
  3. 最优值的记号。 PPT 用 ,课堂从本节起写 ,CLRS 用 ,三者同义;同一段证明中不可混用两个符号。考试统一写 。
  4. 在线性的定义。 CLRS 的 online 指输入逐件到达且决定不可撤回,与 PPT 的「边读边放」一致,此处无实质差异,仅记号不同。考试按 CLRS 的 online 定义作答。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(关键不等式 + 配对)

箱:B1B2B3B4...B_{2M-1}B_{2M}││││││└──>1───┘└──>1───┘└──>1───┘S(B1)+S(B2)>1S(B3)+S(B4)>1S(B2M-1)+S(B2M)>1为什么>1?B2的第一个物品x之所以开新箱,是因为它塞不进B1,即S(B1)+x>1。而x∈B2⇒S(B2)≥x⇒S(B1)+S(B2)≥S(B1)+x>1∎于是2M个箱两两配对成M对,每对>1:Σ_{i=1}^{2M}S(Bi)>M·1=M但总量S_total≤M·1=M(最优M箱,每箱容量1)⇒矛盾⇒NF不可能用≥2M箱

4. 复杂度表

算法 时间 空间 箱数上界 是否在线
Next Fit (紧) 是
First Fit 或用并查集/线段树 是
Best Fit 是
FFD(降序 + FF) ,真比 否(离线下排序)

5. 典型例题

Q. Prove that Next Fit never uses more than bins, where is the optimal number of bins, and show that the bound is tight.

Proof (by contradiction). 设 NF 输出 个箱 , 为第 箱的物品总量。假设 ,取前偶数个箱 两两配对 。

对每个 : 之所以被开出来,是因为某个物品 塞不进 ,即 ;而 ,故 ,于是

把 对相加:
另一方面,全部物品总重 。而最优解用 个容量为 1 的箱装完,必有 。合并得 ,矛盾。故 。

Tightness(紧例构造,必须会写). 取 充分小,令物品列表(共 件):

更直观的标准构造:让物品大小交错为

其中

  • Next Fit 行为:读 开 ;读 , 不超过 1——等等,这里要小心,和恰为 1 是装得下的。故把 的配对改成"严格装不下"的写法:取 、(都大于 1/2),则任意两件之和 ,装不下:
  • 进 ; 放不进 ⇒ 开 ; 放不进 ⇒ 开 ……
  • 于是共 件、每件独占一箱 ⇒ NF 用 箱。
  • 最优解:每箱容量 1,放 后还剩 ,放不下一整件 ⇒ 也只能一件一箱,。这说明该构造的 NF 并不差,不是紧例。

正确的经典紧例(达到 )用的正是" 与 交替"的另一种排列:

核算:

  1. NF 过程:第 1 件 独占 ;第 2 件 与 之和恰为 ,装得下 ⇒ 同箱 (满)。第 3 件 开 ,第 4 件 与之配满 ……前 对恰装满 个箱。接着 件 各开新箱共 箱;最后那件 再开 1 箱。

  2. 最优解:把最后的 与一件 配成一箱,其余 两两配成 的箱, 件 除去配掉的 1 件还剩 件,两两配需 箱。取 :列表 重排即可,直接核算最方便。

    考试写法(推荐):不写通项,直接说"取 组形如 的配满箱,再接 件 与 1 件 ";则 NF 得 箱,而最优把最后那件 与一件 合并、其余两两合并,得 箱。。

关键点:NF 的紧例必须让相邻两箱之和对"刚刚超过 1"—— 与 之和恰好等于 1 是"装得下"的边界,因此通过控制出现顺序让一部分配成、一部分配不成,才能卡在 。

6. 易错点 + 老师原话

  • 易错:把证明里的 写成 。若只是 ,配对求和只能得 ,推不出矛盾。必须严格 ,理由就是"塞不下 ⇒ "。
  • 易错:忘记"最优也要 箱 ⇒ "这一步。缺了它,配对给的不等式和总量搭不上桥。
  • 易错:认为 是比例界。它不是" 倍",而是"最多 个箱";除以 得 ,这才是 ratio 的形态。老师:「证明 FFD 时,书上是 加一个尾巴,加了尾巴就不能叫 ratio……ratio 指的是你得到的解跟最优解的比值,后头不能多尾巴。」 [稿:4-逐字稿:314](同理 NF 的 也要化成 才是 ratio)
  • 易错:紧例写成" 个 与 个 交错"。顺序交错方式不同结论完全不同:若严格交替 则每对恰好装一起,NF 箱,不紧;必须把""排在能配对的位置、把 堆在后面单独开箱。

7. 考试英文答题模板

Claim. Next Fit uses at most bins, where is optimal. Proof. Let be NF's bins and the total size in . Suppose . Whenever is opened, its first item did not fit into , so , and since we get . Summing over the disjoint pairs yields . Yet the total size of all items is at most , since an optimal packing uses unit-capacity bins. Contradiction; hence . Tightness. Take items of size and items of size , ordered so that each is separated from a partner that would fit, followed by the remaining items and one final . NF then needs bins while suffice. Hence the bound is tight and .


4.2.6 时 Next Fit 的界 【S 级 · 课堂讨论原题】

1. 一句话本质

如果把每个物品都压到 以下,NF 的"浪费"就有了硬下限:除最后一箱,每箱剩余的空白都小于 ,所以每箱至少装了 。于是箱数 总重 ,直接给出 。

2. 定义与性质

命题:设列表 的每件物品大小 ,其中 。若 的最优装箱数为 ,则 Next Fit 的箱数 满足

证明(课堂原版,行号可查):设 NF 共用 箱。对 :

  • NF 在开第 箱时,那个"开箱物品" 放不进第 箱 ⇒ 第 箱的剩余空间 。
  • 故第 箱已装 (严格)。
  • 求和:。
  • 又总重 (最优 箱、每箱容量 1)。
  • 于是 ,即 。

因为 为整数,。若要得到干净的 形式,注意 当 足够大时成立(或用 后按 4.2.9 的"尾巴剥离"手法处理)。考试按 PPT 与课堂口径写

[稿:4-逐字稿:167-203]:学生原话 「前面 个箱子的剩余空间全都小于 ,前 个箱子里面装的物品的大小都严格大于 ,总大小就一定大于 。」 老师追问"为什么是 不是 ",答 「因为除了最后一个箱子之外,前面每一个箱子的剩余空间都必须小于下一个物品的大小。」、「最后有可能留一个尾巴,我们不管那个尾巴了。」

这里的 用在哪? 这正是老师连着追问两次的点。 的作用不在这个不等式的代数里(代数只用了 ),而在选择更小的上界:

  • 与 无关的普适界是 (4.2.5,常数 2)。
  • 与 有关的界是 。
  • 取两者中较小者:。而
    所以 时 更紧(更好); 时它比 2 还大,没有信息量,只能退回 2。

学生的补充原话:「 到 取 到 是来比较两种上界哪个更好……next fit 还有一个与 无关的上界是 ,我们应该选择两者中更小的一个近似系数,在 和 2 当中去选。」 老师确认:「2 分就可以得到一个更好的更小的 是不是?」 [稿:4-逐字稿:275-287]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

命题:设列表 的每件物品 ,其中 。若 Next Fit 用 箱、最优用 箱,则

从而 ,写成比值即 (尾部常数在 足够大时吸收)。证明只用到一件事:开第 箱的首件 满足 ,故第 箱剩余小于 。

2.2 OI Wiki 校订|基本性质

  1. 前 箱每箱装载超过 。 理由:下一箱首件 放不进,剩余量小于 ,故不超过 。最小实例:,某箱剩 0.2,来件 0.25 放不进,该箱装载 。
  2. 求和只到 。 理由:最后一箱没有「下一件」作见证,剩余可能任意大。最小实例:,列表 全进一箱,,求和为空,不等式 仍成立。
  3. 趋零时界趋近 1。 理由: 在 0 处连续且取值 1。最小实例: 得 , 得 。
  4. 只用于与常数 2 比较。 理由:代数推导对任意 都成立, 来自不等式 。最小实例: 时界为 , 时为 ,后者弱于已知的 2。
  5. 尾巴不能省成等号。 理由: 是整数,严格不等式 只能给上取整。最小实例:、 时 ,正确结论是 ,写成 会多算一箱。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 命题的来源。 CLRS 没有这条命题,PPT 只给 与 , 是课堂讨论的补充;考试按课堂推导写,并注明 的作用是比较两条界。
  2. 的地位。 课堂把它写在命题条件里,严格说命题对 都成立, 只是与常数界的交点;判断题问「证明是否用到 」应回答「没有」。
  3. 记号。 课堂用 表示箱数,PPT 用 表示最优箱数,本节统一写 ;答卷任选其一但要前后一致。
  4. 无 OI 条目可引。 OI Wiki 无装箱近似条目,《复杂度》页只提供 记号,与本命题无关,故按教材口径。考试按教材口径作答。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示( 与界的关系)

ρ3┤╲│╲ρ=1/(1−α)2┤╲___________________________←常数界2│╲╱1.5┤╲╱α=1/3⇒ρ=1.5│╲╱1┤╲___╱└──────┬───────────────────▶α1/2↑交点:1/(1−α)=2α<1/2:1/(1−α)<2⇒用1/(1−α)(更紧,更接近1)α=1/2:1/(1−α)=2⇒两界重合(分界点)α>1/2:1/(1−α)>2⇒无信息量,只能退回2

4. 复杂度表

限制 NF 箱数上界 备注
无限制() 4.2.5
逼近 1
极限情况

这张表就是老师 4.2.5 讨论前那段直觉的量化:「你装箱的所有物品都很小……装不下的时候,剩下的就是被浪费的空间肯定也很小,所以每个箱子都可以被装得比较满,它 ratio 应该更接近 1。」 [稿:4-逐字稿:146]

5. 典型例题

Q. Let be a bin-packing instance in which every item has size at most , where . Prove that Next Fit uses at most bins. Why is required for this to improve on the general bound?

中文逐步:

  1. 设 NF 用 箱。对 ,"开第 箱的物品" 塞不进 ⇒ 剩余 ⇒ 。
  2. 前 箱总量 。
  3. 最优 箱 ⇒ 总重 ⇒ 。
  4. 整理得 ,写作 (忽略常数尾巴,见 4.2.9 的手法)。
  5. 为什么 :另有 -无关界 。只有当 即 时,前者才更紧; 时它比 2 还大,说了等于没说。
  6. 具体数值: ⇒ ; ⇒ 。

6. 易错点 + 老师原话

  • 易错:以为 是证明不等式的必要条件。不是——代数推导只用 ,对任何 都成立; 是与常数界比较后取优的临界点。老师在学生第一次讲完后连问三次 「这个式子能成立就是跟 是有什么关系?」「你 用在哪儿了?」「如果它不是 的话,如果它大于 的话,这也能成立吗?」 [稿:4-逐字稿:206-224] —— 学生答"我没有用到 ",正是这个点的失分样貌。
  • 易错:写成 却省掉"尾巴"。严格推导给的是 。考试若考"ratio"就必须把 剥离(或说明对足够大的 可忽略)。
  • 易错:把 当成"物品平均大小"。它是上界(每件都 )。
  • 易错:以为"箱数 总重/(1-\alpha)" 就能直接除 。必须显式用 这一步,它就是老师说的 「最优解用的箱子数至少得装下所有物品的总大小」 [稿:4-逐字稿:215]。

7. 考试英文答题模板

Claim. If every item has size at most , then . Proof. Let NF use bins. For each , the item that opened bin did not fit into bin , so bin 's residual capacity is smaller than that item's size, hence at most ; therefore . Summing, . Since an optimal packing uses bins of capacity 1, the total item size is at most , giving , i.e. , and the ratio bound follows. Because the -free bound is , the new bound is strictly better exactly when , i.e. .


4.2.7 是分界点 【S 级 · 选择必考 · 易错第 10 条】

1. 一句话本质

是两条上界 与常数 的交点:在它左边前者更紧,在它右边后者更紧。它不是某个证明的"前提条件",而是"取哪个界"的切换开关。

2. 定义与性质

解不等式:

  • (严格):,用更紧的 。
  • :,两界完全重合——这是"分界点"的准确含义。
  • :,此界不再有改进意义,只能退回 。

注意题目若写""(严格小于 1/2),则 严格在分界点左侧, 严格优于 2,且 时 。这就是" 为该上界与常数 2 的分界点" [纪要:4-conclusion:三、1]。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

把 4.2.6 的界与常数界并列,比较函数 与 :

在 上 严格递增且 ,故 时 , 时两界相等, 时 。因此 是「用哪条界」的唯一分界点,与证明前提无关。

2.2 OI Wiki 校订|基本性质

  1. 交点唯一。 理由: 在 上严格单调,与水平线 至多交于一点。最小实例: 时 , 时 。
  2. 排除 。 理由: 时 ,与近似比的下界冲突。最小实例: 得 ,而任何实例都满足 。
  3. 时两界相等。 理由:,两个候选给出同一数值。最小实例:物品全为 ,每箱恰放两件,Next Fit 与最优同为 箱。
  4. 时该界无信息量。 理由: 弱于已知的 。最小实例: 时 ,而 三件下 Next Fit 得 3 箱、最优 2 箱,比值 。
  5. 两条界在最优点不能同时取等。 理由:取等需 ,此时 而箱数界是 ,二者仍差 。最小实例: 的列表 比值 。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 结论的性质。 OI Wiki 与 CLRS 都不讨论这个分界,它是课堂把两条上界并列后得到的推论;考试按「解不等式再取较小者」作答。
  2. 「分界点」的说法。 课堂称 为分界点,CLRS 口径下应表述为两条上界的交点;两者含义相同,答题写「交点」不易被误判成证明前提。
  3. 记号。 PPT 选项写 ,其中 是物品尺寸上界;部分资料用 表示同一量,勿与 4.2.3 的精度 混淆。考试按 理解。
  4. 判断题口径。 问「 时哪条界更好」应回答「一样」;问「 能否写 」应回答「能写但不能改进」。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(分界点 + 错误选项的荒谬性)

α=1/2│─────────────────┼─────────────────▶α01/211/(1−α)<2│1/(1−α)>2↑用前者│↑用2──────────D选项ρ=2α的荒谬性──────────ρ1┤ρ=2α│╱0.8┤╱α=0.4⇒ρ=0.8<1✗✗✗│╱"近似解比最优解还好"—不可能0┤─────────╱──────────────▶α00.51↑α<0.5时ρ=2α<1⇒逻辑自毁

为什么 荒谬(课堂原题 D 选项)

近似比的定义要求 (4.2.1 性质①)。若 且 ,则 ,这等价于宣称"",即算法解比最优解还优——与 " 是最优"矛盾。老师一句话判死:

「D 为什么是错的?你有一个更简单的想法:当 小于 的时候, 就小于 了。 能小于 吗?近似算法比最优解能更好吗?不可能。你只要说只要 ,你就能得到一个 的结果,是不可能的。D 肯定是错的。」 [稿:4-逐字稿:311]

学生王沁怡给的具体反例(可作考试备选)[稿:4-逐字稿:287-293]:取 ,令 含 10 个大小均为 的物品。每箱最多放 2 个 ⇒ NF OPT 。若 D 成立(),则须有 ——不等式取等却方向要求严格成立,实际 虽成立但 才是不等式应有的形式,与 冲突,故 D 错。更干净的说法就是" 荒谬"。

4. 复杂度表(三个 区间的答案)

区间 推荐界 数值例 是否用
是(更紧)
两界相同,写哪个都对
(用 ) 否(此界 无意义)
无条件 —

5. 典型例题

Q. (Multiple choice, choose the WRONG statement) Let be a bin-packing instance with item sizes , packed by Next Fit. (A) The approximation ratio of Next Fit is bounded by , and is the boundary where this bound meets the constant bound 2. (B) Next Fit uses at most bins. (C) [a counting question about a concrete list] (D) The approximation ratio is .

中文逐步:

  1. (A) 对。由 4.2.6 的推导得 ,且解 得 ,正是与常数 2 的交点 ⇒ 正确。
  2. (B) 对。4.2.5 定理,与 无关 ⇒ 正确。
  3. (C) 按具体数据数箱即可 ⇒ 正确。
  4. (D) 错。,违反 ⇒ 这就是错项。
  5. 答案 (D)。课堂原题就是"选错的",D 为唯一错项 [稿:4-逐字稿:152]。

6. 易错点 + 老师原话

  • 易错:把 说成"证明的前提"。它是界的比较结果,不是推导前提(见 4.2.6 第 6 条)。
  • 易错:在 时说" 比 2 更好"。此时两者相等,不存在谁更好。
  • 易错:把 D 选项的错误归因于"数值算错"或"举例不符",而没说出根本理由 。老师在学生用 PPT 例子算完后仍在找"更简单的想法",最后给出 的判据 [稿:4-逐字稿:311]。考试优先写逻辑判据,再补反例。
  • 易错:忘记 是定义的硬约束(4.2.1 性质①)。这条一用,D 秒杀。

7. 考试英文答题模板

The bound obtained under the size restriction is , while Next Fit always satisfies (more precisely bins). Comparing them, , so is exactly the boundary point at which the two bounds coincide: for the former is strictly tighter, for it exceeds 2 and carries no information. Option (D), , is impossible: when it gives , asserting that the approximate solution beats the optimum, which contradicts the definition of an approximation ratio (). Hence (D) is the wrong statement.


4.2.8 FFD 及其证明 【S 级 · 证明必考】

1. 一句话本质

FFD = 先按大小降序排序,再跑 First Fit。降序把"捣乱的大物品"(trouble-maker)排到前面,使每个箱的"封箱原因"变得可分析;再配合 FF 的"前两箱必满"结构,把 的带尾巴界折算成真正的 。

2. 定义与性质

离线(off-line):先看全部物品,再给答案 [PPT:4下:182-186]。 排序降序是关键一步:Trouble-maker: the large items. Solution: sort into non-increasing sequence of sizes; then apply first (or best) fit — first (or best) fit decreasing. [PPT:4下:184-186]

两个层次的上界必须分清:

层次 式子 出处 是否是 "ratio"
教材(Weiss)带尾巴界 [PPT:4下:199-202] 不是(多一个常数尾巴)
课堂提到的更紧形式 PPT/讨论题提示 是" 的仿射式",仍是尾巴形式
真正的 ratio [纪要:4-conclusion:三、2]、[稿:4-逐字稿:317] 是

为什么带尾巴的 不能叫 ratio:老师专门纠正:「书上写的是 最优解加上 ,或者优化更好一点变成 。但是由此我们能不能说 ffd 这个 ratio 就是差不多 了?不能。因为当我们说 ratio 的时候……它后头多了一个尾巴。ratio 指的是 得到的解跟最优解的比值,后头不能多尾巴。加了尾巴的东西就不能叫 ratio 。真正的 ratio 是说 等于一个 ratio 乘以最优解,才是真正的 ratio。」 [稿:4-逐字稿:314]

从带尾巴界到 :核心放缩(课堂第一问原版) [稿:4-逐字稿:341]

目标是证明 。已知 (把 粗化为 1)。

  • 情形 1:。 则

    ( ✓)

  • 情形 2:。 逐一验证:

  • :任何可行解 ,而 , ✓(整数意义上 )
  • : ✓
  • : ✓ (更强地说,FFD 在小实例上往往就等于 )

两情形合并 ⇒ 对一切 成立。∎

证明思路之"按物品大小分类讨论":上面是"按 的大小"分情形;教材/讨论题的正统做法是按物品尺寸分层统计箱数占比——把物品按 、、…… 分层,逐层估计 FFD 用了多少箱、最优至少要多少箱,加权求和得 ,再按上述放缩升到 。考试若要求"分情况讨论",写按 的大小区间那种(更短,且是课堂亲授版本)最稳;若要求"从 推",就写上面的放缩。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

FFD(First Fit Decreasing)分两步:先把物品按尺寸非增排序 ,再依次把每件放入第一个剩余容量足够的箱,都不行则开新箱。它需要先看到全部物品,属离线(off-line)算法。已知带尾界

而真正的比值界是 。前者的常数项无法并入系数,故不是形如 的比值。

2.2 OI Wiki 校订|基本性质

  1. 不是比值。 理由:比值要求 ,常数尾巴会随 变小而主导。最小实例: 时该式给 ,而实际 ,带尾式既不等于 1 也不形如 。
  2. 时放缩到 。 理由:,故 。最小实例: 时上界为 。
  3. 需逐一验证。 理由:箱数为整数,可行解不超过最优值的某个小倍数。最小实例: 时 ; 时 。
  4. 离线是达到该界的必要条件。 理由:降序要求先看到全部物品,在线算法无法预知后面是否有更小的件来填空。最小实例:在线按 用 First Fit 得 3 箱,离线排序为 后得 2 箱。
  5. FFD 不是精确算法。 理由:比值界只给上界,允许存在 的实例。最小实例:,FFD 排序后得 、、 共 3 箱;最优为 与 共 2 箱。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 哪条界是最终答案。 CLRS 第 35 章不给 FFD 的 证明,PPT 给的是 , 由课堂放缩得到。考试问「FFD 的比值」答 ,问「PPT 的界」答 带尾。
  2. 带尾界与比值的区分。 课堂专门纠正「加尾巴的不能叫 ratio」,CLRS 的定义同样要求比例形式;此处无实质差异,只是 PPT 原文的写法容易误导。考试按「无实质差异」作答。
  3. 记号。 PPT 用 表示最优箱数,本节写 ; 与 含义相同。考试统一写 。
  4. 无 OI 条目可引。 OI Wiki 无装箱近似条目,其《贪心》页只讲贪心选择性质,不能作为比值界的依据。考试不引 OI。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(FFD 结构 + 放缩)

原列表:0.80.20.70.50.40.30.1↓降序排序0.80.70.50.40.30.20.1↓FirstFitB1:0.80.2B2:0.70.3B3:0.50.40.1(3箱)──为什么不排序会差──0.4,?,?,...若大物品晚到,前面小物品把箱子"占碎",后面的大物品必须开新箱⇒"trouble-maker=largeitems"──降序后──大物品先坐定,后续小物品才有填空机会⇒箱的"封箱原因"可追踪──放缩链条──FFD≤11/9·M*+6/9≤11/9·M*+1(粗化常数)=(11/9+1/M*)·M*≤(11/9+1/4)·M*(M*≥4)=53/36·M*<3/2·M*✓小M*(1,2,3)逐个代入✓

4. 复杂度表

算法 排序 装箱 总时间 箱数界
Next Fit —
First Fit — (平衡树/并查集)
Best Fit —
FFD ,真比
BFD 同量级

5. 典型例题

Q. FFD is known to satisfy . Using only this inequality, prove that the true approximation ratio of FFD is at most .

Proof. 粗化 ,得 。

Case 1: . Then , so

Case 2: . Check directly: : ; : ; : . (In each case the inequality holds since the number of bins is an integer.)

Hence for all instances.

注:这里" 时 "用到了 至少会给出可行解、且 意味着所有物品总量 ,最坏也只是把某些物品拆不开——严格说应写 (FFD 若能塞进一箱就是最优)。考试写 即可。

6. 易错点 + 老师原话

  • 易错:把 直接当 ratio 答。老师点名纠正过 [稿:4-逐字稿:314]:带尾巴的常数项就不是 ratio。
  • 易错:认为 FFD 的界就是 。真 ratio 是 ()。""是界的一种写法,不是最终的 ratio。
  • 易错:忘记 FFD 是离线算法。考试若给"读一个放一个"的限制,FFD 不合法。
  • 易错:把 与 搞混。 是另一种带尾巴形式(且系数 与 不同的推导口径);要转成 ratio 仍需同样的"剥离尾巴"手法。考试以 作为最终答案。
  • 易错:证明里把""的放缩写成""。这一步要求 即 ,必须显式写理由。

7. 考试英文答题模板

FFD sorts items into non-increasing order and then applies First Fit. A classical result states . Note that this is not an approximation ratio because of the additive constant; a ratio must be of the form . To obtain one, write . If then . If , direct substitution gives . Hence FFD is a -approximation algorithm for bin packing. Moreover is best possible unless (see next section).


4.2.9 的最优性:反证 + 【S 级 · 证明必考】

1. 一句话本质

若存在 的多项式近似算法 ,就用它冒充判定器去解 NPC 的 "2-bin packing"(能否用 2 箱装完):因 的输出是整数且严格小于 ,它只能 ,于是 的答案恰好等于判定答案 ⇒ 多项式解出 NPC ⇒ 。矛盾 ⇒ 。

2. 定义与性质

定理(要证):Unless , no polynomial-time algorithm for bin packing has approximation ratio .

关键引理:判定问题

是 NP-complete(它是 bin packing 在 时的判定版本;等价于 Partition 问题:能否把物品分成两堆、每堆总量 )。

证明(反证):

  1. 反设:存在多项式时间算法 与常数 ,使对任意列表 ,。
  2. 可行性下限:任何可行算法( 必须输出可行装箱)都有 (算法解的箱数不可能少于最优)。
  3. 构造归约:给定任意 ,用 跑一次(多项式时间),得整数 。分两种情形: - 情形 Yes: 能用 2 箱装下,即 。则
    又 是整数且 ,故 。答 "Yes"。 - 情形 No: 不能用 2 箱装下,即 。则
    答 "No"。
  4. 判定正确性:情形 Yes ⇒ ;情形 No ⇒ 。二者以 " ?" 为分界,完全可区分,因此步骤 3 的判据在多项式时间内精确解出 2-BIN-PACKING。
  5. 矛盾:2-BIN-PACKING 是 NP-complete,若它有多项式解法则 。与实际(假定 )矛盾。
  6. 结论:不存在 的多项式近似算法 ⇒ 是理论最优上界。

逻辑链必须完整的三处细节(考试扣分点):

  • 为什么"整数"是关键:若 可为实数, 并不妨碍 对应 (实际 必须是整数箱数)。整数性把 "" 变成 "",这一步是不可替代的。
  • 为什么必须 输出可行解:否则 可能小于 ,情形 No 下 就断不了。可行性与整数性合用才给出" 是 的紧夹逼"。
  • 为什么 要求严格 :若 ,情形 Yes 只能得 ,则 时无法区分 与 ,归约失效。严格不等式是必须的。

老师的逻辑链:「存在一个算法,它能够用严格小于 的 ratio 去解决问题,那意味着什么?意味着如果给了一个具体的问题,我就用这个算法去跑一下,在多项式时间里头,我得到能不能装进两个箱子的问题的这个答案……他回答 no 就意味着最优解一定是 no,他回答 yes 就意味着最优解一定是 yes。那我们就有一个多项式级别的算法,能够回答最优解是 yes 还是 no 的问题,我就用多项式时间解决了一个 NPC,那 P 就等于 NP 了。」 [稿:4-逐字稿:485-491]

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

定理:除非 ,装箱问题不存在多项式时间、比值 的近似算法。证明取 NP-完全的判定问题

它与 Partition 等价。反设存在这样的 ,对任意列表跑 得整数 :若 可装两箱,则 ,,由整数性 ;若不可,则 ,由可行性 。于是判据「」精确判定 2-BIN-PACKING,推出 。

2.2 OI Wiki 校订|基本性质

  1. 整数性把「小于 3」变成「不超过 2」。 理由:箱数取正整数, 与 等价。最小实例:、 得 ,故 ;若允许实数, 就无法与 区分。
  2. 可行性 是 No 情形的唯一依据。 理由:算法必须输出合法装箱,箱数不可能少于最优。最小实例:总量为 2.2 的列表必有 ,任何可行输出 ,不会被误判成 Yes。
  3. 必须严格 。 理由: 时 Yes 情形只给 ,与 No 情形的 重叠。最小实例:、 得 , 时无法判定。
  4. 2-BIN-PACKING 是 NP-完全。 理由:它等价于 Partition,而 Partition 由 Subset-Sum 归约得到。最小实例: 总量为 2,可分为 与 ,答案为 Yes。
  5. 不能推广到任意 。 理由: 箱判定的夹逼变为 与 ,要分开需 ,阈值随 增大而变小。最小实例: 时需 ,而 FFD 的 已超过它,该模板给不出更强的界。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 结论的写法。 CLRS 第 35 章以「若 则无 PTAS」的形式给出装箱的不可近似性,与本节 下界一致;PPT 只证 。考试按 PPT 的 2-BIN-PACKING 反证写。
  2. 推导方向。 课堂强调「先有算法输出 ,再反推 的区间」,CLRS 的证明同样从输出出发;学生常写反方向,属表述错误而非口径差异。考试按课堂的方向写。
  3. 可引的 OI 内容。 OI Wiki《计算复杂性基础》给出 NPC 与多项式时间的定义,可支持「2-BIN-PACKING 是 NPC」这一步;该页不涉及装箱近似,不能引作下界来源。考试只引其 NPC 定义。
  4. 记号。 PPT 用 ,本节写 或 ,两者同义。考试统一写 。

来源:OI Wiki《计算复杂性基础》 · https://oi-wiki.org/misc/cc-basic/ (NPC 与多项式时间定义);教材 CLRS 第 35 章给出同一下界。

3. 图示(归约示意 + 整数夹逼)

任意列表L│▼多项式时间┌─────────┐│算法A│ρ<3/2└────┬────┘│输出整数a=A(L)▼┌──────────────────────────────────┐│a≤2?││├─是⇒答Yes⇒OPT(L)≤2││└─否⇒答No⇒OPT(L)≥3│└──────────────────────────────────┘│▼多项式时间解出2-BIN-PACKING(NPC)⇒P=NP✗矛盾──整数夹逼(a的取值区间)──情形Yes:OPT≤2⇒a≤ρOPT≤2ρ<3⇒a∈{1,2}⇒a≤2情形No:OPT≥3⇒a≥OPT≥3⇒a≥3↑可行解⇒a≥OPT两个区间[1,2]与[3,∞)不相交⇒判据"a≤2"精确

4. 复杂度表

环节 时间 说明
归约(构造 的实例) 直接输入
跑 反设
判据 "" 比整数
合计 若 存在则 2-BIN-PACKING ∈ P

5. 典型例题

Q. Show that, unless , bin packing admits no polynomial-time approximation algorithm with ratio .

Proof (by contradiction). Suppose such and exist. Consider the NP-complete decision problem: given items of total size , can they be packed into two unit bins? Run on the instance, obtaining an integer .

  • If the items can be packed into 2 bins, then and ; as is a positive integer, .
  • If they cannot, then ; since must return a feasible packing, .

Thus "" decides the NP-complete problem exactly, in polynomial time, so —a contradiction. Therefore no approximation exists unless , and is the best possible ratio (attained by FFD).

6. 易错点 + 老师原话

  • 易错:逻辑方向反了。学生在课堂上就犯了这个错:先假设 再推 ,老师纠正:「不是,你先回答了说 等于二,你倒推那个 也会二—— 是我们不知道的东西。我们能知道的是算法 给出的答案是 2。 的答案我们是知道的, 可以在多项式时间里头直接算出来,由此我们来推那个 大概是一个什么情况。逻辑反了!」 [稿:4-逐字稿:359-374]。考场上必须写 " 的输出 已知 ⇒ 反推 的区间"。
  • 易错:漏掉" 是整数"。没有整数性,""推不出""。
  • 易错:漏掉"(可行性)"。没有它,情形 No 下 无依据。
  • 易错:把 和 混用。必须是严格小于,否则 时区分度消失。
  • 易错:把"2 箱"换成" 箱"后仍照抄此证明。 箱判定仍是 NPC,但夹逼要改为 vs ,条件变成 ——这就是为什么不能把这个技巧推广到任意 。

7. 考试英文答题模板

Theorem. Unless , no polynomial-time algorithm approximates bin packing within . Proof. Assume is such an algorithm with ratio . Consider the NP-complete problem 2-BIN-PACKING. On any instance , run to obtain an integer . • If is packable into 2 bins, , so , hence by integrality. • If not, , and since outputs a feasible packing, . Therefore the test "?" decides 2-BIN-PACKING exactly in polynomial time, implying , a contradiction. So is unavoidable, and since FFD achieves , this ratio is optimal. (Note: integrality of is essential — it turns "" into "".)


4.2.10 First Fit 与 Best Fit: 【A 级 · 记忆】

1. 一句话本质

First Fit 放进第一个装得下的箱(可能回看所有已开箱),Best Fit 放进最紧的箱(剩余最小的)。二者的界同为 ,比 Next Fit 的 2 好,但比 FFD 的 差。

2. 定义与性质

void FirstFit() {
    while (read item) {
        scan for the first bin that is large enough for item;
        if (found) place item in that bin;
        else      create a new bin for item;
    }
}

[PPT:4下:116-124],PPT 旁注 "Can be implemented in "(平衡树/BIT 维护剩余容量)。

【Theorem】Let be the optimal number of bins. Then first fit never uses more than bins. There exist sequences such that first fit uses bins. [PPT:4下:128-131]

Best Fit:Place a new item in the tightest spot among all bins. and bin no. . [PPT:4下:132-134]

性质对比:

  • FF/BF 都是在线算法,且比 NF 强,因为它们回看所有箱而不只当前箱。
  • 略小于 2;紧例达到 ,即同样带尾巴形式。
  • FF 与 BF 的界相同,但 BF 在紧例上可能略有差异;考试只考数值。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

First Fit:维护已开箱的序列,每件放入第一个剩余容量足够的箱,没有则开新箱。Best Fit:放入剩余容量最小的可行箱(最紧),没有则开新箱。两者都不改动已放物品,属在线(on-line)算法,时间为 (平衡树维护剩余容量,或用并查集跳过满箱),并满足

且存在使 First Fit 用到 箱的序列,故常数 不可再降。

2.2 OI Wiki 校订|基本性质

  1. 两算法界相同但选箱规则不同。 理由:分析用的是同一组装载下界,差别只在选哪个可行箱。最小实例:,First Fit 把 放进第一箱得 ,最后 无处可放,共 3 箱;Best Fit 把 放进更紧的第二箱凑满,最后 与第一箱凑满,共 2 箱。
  2. 在线性使下界可以逼近上界。 理由:在线算法必须在不知道后续物品时定案,存在对抗序列迫使任何在线算法多用箱。最小实例:、、 各 6 件,最优每箱各取一件共 6 箱,而三种在线算法都需 10 箱,比值约 。
  3. 时间为 。 理由:每件需要找可行箱,平衡树上的查找与更新各为对数。最小实例: 时约 次基本操作,而不是 次。
  4. 离线降序能再降一档。 理由:大件先定位,减少「小件占碎箱子」的浪费。最小实例:同一列表 在线 First Fit 得 3 箱,离线排序后 FFD 得 2 箱。
  5. 介于 与 2 之间。 理由:,故 First Fit 优于 Next Fit、劣于 FFD。最小实例: 时 First Fit 至多 17 箱,FFD 至多 15 箱,Next Fit 至多 19 箱。

2.3 OI Wiki 校订|与课堂口径的差异

  1. Best Fit 的界。 CLRS 以 First Fit 与 FFD 为主,Best Fit 的界放在习题;PPT 直接给 。考试按 PPT 的数值答。
  2. 在线下界。 课堂把 与 并列,CLRS 只给上界;判断题问「在线算法能否达到 」须依据 回答不能。
  3. 记号。 PPT 用 表示物品数,CLRS 用 ,本节统一写 。考试统一写 。
  4. 无 OI 条目可引。 OI Wiki 无装箱近似条目,其《贪心》页不含比值分析,故本考点按教材口径。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(三种 fit 的行为差异)

  物品: 0.5, 0.7, 0.8, 0.2, 0.4, 0.3, 0.1

  Next Fit: 只看当前箱
    [0.5] [0.7] [0.8 0.2] [0.4] [0.3 0.1]   → 5 箱

  First Fit: 第一个装得下
    B1: 0.5 0.2 0.3   B2: 0.7   B3: 0.8 0.1   (0.4?) → 4 箱
        ↑ 0.2 回到 B1,0.3 也回 B1,0.4 无箱可放

  Best Fit: 最紧的箱
    B1: 0.5 0.4 0.1   B2: 0.7 0.3   B3: 0.8 0.2  → 3 箱

(具体数值以 PPT "Discussion 14" 为准,[PPT:4下:143-148] 要求自行推演三法的结果。)PPT 另给了 的构造,最优 6 箱,而三种在线算法都要 10 箱 [PPT:4下:151-159] —— 这个 的例子直观说明在线算法的 量级是真实的。

4. 复杂度表

算法 时间 箱数上界 紧例值 在线/离线
Next Fit 在线
First Fit 在线
Best Fit 同类 在线
Worst Fit / Almost Worst Fit 常数比 — 在线
任意在线算法 — — 在线
FFD 离线

特别记一条定理:There are inputs that force any on-line bin-packing algorithm to use at least the optimal number of bins. [PPT:4下:177-179]

这条给了在线算法的下界 。所以:

  • 在线:,FF/BF 已接近最优在线。
  • 离线:可以做到 (FFD),且 是理论极限(4.2.9)。

老师原话:「只要是在 online 的……告诉我 ratio 好不了,最好的也就是 左右了,就 。」 [稿:4-逐字稿:140]

5. 典型例题

Q. Compare Next Fit, First Fit and FFD for bin packing in terms of (i) whether they are online, (ii) running time, and (iii) proven approximation ratio. Give an instance where First Fit uses strictly fewer bins than Next Fit.

中文逐步:

  1. (i) NF、FF 在线;FFD 离线(需全局排序)。
  2. (ii) NF ;FF ;FFD (排序 + FF )。
  3. (iii) NF ;FF ;FFD 。
  4. 反例构造:。NF: 进 , 放不下( 其实装得下——需改)取 :NF 用 3 箱(||,因 且 ),FF 也用 3 箱。改用 PPT 第 8 页构造最稳: 取 、、 各 6 个 ⇒ 最优 6,三种在线算法均 10 [PPT:4下:151-159]。
  5. 结论:在线改离线 + 降序,才从 跨到 。

6. 易错点 + 老师原话

  • 易错:把 记成 或 。 是 FF/BF 的标签。
  • 易错:以为 FF 是离线算法。FF 是在线的(顺序读、顺序放),只有 FFD 需要先排序(离线)。
  • 易错:以为 优于 。,FF 比 FFD 差。
  • 易错:忘记在线下界 。问"能否设计一个在线算法达到 "——不能, 是屏障。
  • 记法:在线三兄弟 (NF, FF, BF),下界 ;离线到 封顶。

7. 考试英文答题模板

First Fit places each item into the first bin with enough residual capacity, opening a new bin only when none fits; it runs in with a balanced tree or union-find structure, and is online. It satisfies , and there are instances attaining . Best Fit (tightest spot) achieves the same bound with the same time. In contrast, no online algorithm can beat , so First/Best Fit are nearly optimal among online algorithms; only by going offline (sort decreasing → FFD) can one reach the ratio , which is itself optimal unless .


4.2.11 K-center 问题及 2-近似 【A 级 · 简答】

1. 一句话本质

给 个站点,选 个作中心,使所有站点到最近中心的最大距离(covering radius)最小。这是 NPC 的;但存在 的贪心给出 ,且 是理论下界(除非 ,无 )。

2. 定义与性质

输入:站点集 ,要求选中心集 ,。

距离公理(必须满足,缺一不可)[PPT:4下:328-331]:

公理 式子
identity
symmetry
triangle inequality

目标函数:

[PPT:4下:332-337]

候选中心数:(若限定中心必须取自站点;PPT 标注 "Number of candidate centers = " [PPT:4下:339] 是指若允许任意点,则无限多——这正是难的原因)。

算法 1(Greedy-2r,判定式) [PPT:4下:381-390]:

Centers Greedy-2r( Sites S[], int n, int K, double r ) {
    Sites S'[] = S[];        // 剩余站点
    Centers C[] = ∅;
    while ( S'[] != ∅ ) {
        Select any s from S' and add it to C;
        Delete all s' from S' with dist(s', s) ≤ 2r;
    }
    if ( |C| ≤ K ) return C;
    else ERROR(No set of K centers with covering radius at most r);
}

【Theorem】Suppose the algorithm selects more than centers. Then for any set of size at most , the covering radius is . [PPT:4下:395-397]

对 的处理:真实 未知 ⇒ 二分搜索 [PPT:4下:404-415]:在 上二分,每次问 "Greedy-2r 能否用 个 -半径球覆盖所有站点"。找到临界 后,解的半径 ⇒ -近似。

算法 2(Greedy-Kcenter,直接式,更聪明) [PPT:4下:423-431]:

Centers Greedy-Kcenter( Sites S[], int n, int K ) {
    Centers C[] = ∅;
    Select any s from S and add it to C;
    while ( |C| < K ) {
        Select s from S with maximum dist(s, C);
        Add s to C;
    }
    return C;
}

【Theorem】The algorithm returns a set of centers such that . [PPT:4下:434-437]

算法 2 的证明(简答标准版):反设 。则存在站点 使 。考虑 加入时的时刻:当时 是最终 的子集,故 ,即所有已选中心两两距离 (否则由三角不等式 会被覆盖)。于是 中任意两点距离 ,共 个点,两两距离 > 2r(C*)。

现在把最优的 个中心 摊开:因 而 ,由鸽巢,存在 覆盖 中的两个点 。于是

矛盾。(第一步用 覆盖定义,第二步用三角不等式。)∎

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

输入为站点集 ()、度量 与整数 。度量需满足三条公理:恒等 、对称 、三角不等式 。要求选 、,最小化覆盖半径

最远点贪心(farthest-point greedy):任取一站点入 ,重复 次,每次把距当前 最远的站点加入,时间 。

2.2 OI Wiki 校订|基本性质

  1. 三角不等式是证明的唯一工具。 理由:证明只用 把「同球两点」与「两两远离」对接。最小实例:直线上 ,。
  2. 点两两距离大于 。 理由:每个被选中心入选时距此前中心的距离不小于 到这些中心的距离,而 到最终 的距离大于 。最小实例: 时阈值为 ,直线上 与 的距离为 。
  3. 鸽巢给出矛盾。 理由: 个半径 的球覆盖全部站点, 个点中必有两个落在同一球内,其距离不超过 ,与「大于 」冲突。最小实例:、站点 ,最优半径 ;贪心取 与 ,半径仍为 。
  4. 与 是两回事。 理由: 是中心预算, 是站点数; 固定时算法仍随 线性。最小实例:、 时只需 3 轮、每轮扫 100 点,约 300 次距离计算。
  5. 2 是上界而不是等号。 理由:证明只给 ,实际可能严格更小。最小实例:直线上 、,贪心取 与 得 ;最优取 与 也得 ,比值为 1。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 两种算法版本。 CLRS 以最远点贪心为标准写法;PPT 另给 Greedy-2r 的判定版本(覆盖后二分半径)。考试两版都可写,最远点版本更短。
  2. 候选中心的范围。 课堂强调候选中心只能是站点(共 个),CLRS 允许中心取任意点;若允许任意点,最优半径可能更小,答题须按题目限定。
  3. 记号。 PPT 用 与 ,本节相同;部分资料写 与 ,含义一致。考试统一写 。
  4. 可引的 OI 内容。 OI Wiki 无 K-center 条目,其图论页只提供最短路距离作为度量实例,可作构造参考,不能引作 2-近似的出处。考试不引 OI。

来源:教材 CLRS 第 35 章(Approximation Algorithms)口径;OI Wiki 无对应条目。

3. 图示(2r 覆盖 + 鸽巢)

──Greedy-2r的覆盖结构──c*ss'●──────────○────────────────────○│←r(C*)→││←─────────2r────────────────→│若r=r(C*),则任何被c*覆盖的站点都在c*的r球内,故以s为中心、半径2r的球能吞掉c*的整个r球。──鸽巢矛盾图──最优K个球:○○○...○(K个)╲╲╲我们选出K+1点:●●●...●●(两两距离>2r)必须有两个落在同一个最优球里⇒其距离≤2r(C*),与>2r(C*)矛盾✗

4. 复杂度表

算法 时间 近似比 备注
逐个贪心(每次最大幅度降半径) 任意差(arbitrarily bad) [PPT:4下:343-357],反面教材
Greedy-2r + 二分 需 次覆盖检测
Greedy-Kcenter(最远点) (朴素)/ 简单、无需二分
精确解 指数 NPC
任何 — 不存在除非 4.2.12

5. 典型例题

Q. Describe the K-center problem and give a polynomial-time 2-approximation algorithm. Prove its ratio.

中文逐步:

  1. 问题:给定站点集 ()、度量距离 (满足三公理),选 个中心 使 最小。
  2. 算法:任取一个站点作首个中心;重复 次,每次选距离当前中心集最远的站点加入。
  3. 证明:设最终得 ,断言 。反设存在 使 。则 中 点两两距离 ( 与各中心的距离取自其加入时刻,且 最终距离)。
  4. 最优 个 -球覆盖 ,由鸽巢必有一个球同时含 中两点 ,则 ,与"两两 "矛盾。
  5. 故 ,即 2-近似,时间 ,多项式。

6. 易错点 + 老师原话

  • 易错:以为"每次最大幅度降低半径"的贪心就是这 2-近似。错误——PPT 明确标注 "arbitrarily bad!" [PPT:4下:355]。必须是"选最远点"或"用 覆盖再二分"这两种。
  • 易错:忘记三角不等式公理。整个 2-近似证明只用三角不等式;若距离不满足它(如 型、图最短路的非度量变体),结论全崩。
  • 易错:把 与 混。 是中心个数(输入参数), 是站点数。
  • 易错:认为 2-近似可以直接改进成 。不行——4.2.12 说 不可能(除非 )。
  • 记法:K-center 的 与 bin packing 的 是同一套反证手法的两个实例:都靠"整数性/离散性 + 反推 NPC 判定"。

7. 考试英文答题模板

K-center. Given a set of sites and a metric (identity, symmetry, triangle inequality), choose centers minimizing the covering radius . Algorithm (farthest-point greedy). Start with any site; repeatedly add the site farthest from the current center set, until . Time . Claim. where is optimal. Proof. Suppose some has . Then the points in are pairwise apart. But the optimal balls of radius cover , so by the pigeonhole principle one of them contains two of these points ; then , a contradiction. Hence . Moreover no is possible unless (reduction from Dominating Set, see 4.2.12).


4.2.12 近似下界定理 【A 级 · 简答】

1. 一句话本质

"能近似到什么程度"本身有理论天花板,而证明手法是统一的:把近似算法当判定器,去解一个 NPC 判定问题。若某个 能让判定成功,则 。这就是本节和 4.2.9 的共同模板。

2. 定义与性质

【Theorem】Unless , there is no -approximation for the center-selection problem for any . [PPT:4下:443-444]

证明骨架(PPT 原版):

By contradiction. If we can obtain a -approximation in polynomial time, then we can solve DOMINATING-SET (which is NP-complete) in polynomial time. Dominating set problem has a solution of size iff there exists centers with . Then a -approximation must give the optimal solution since all the distances involved are integers. [PPT:4下:446-454]

逐句展开:

  1. DOMINATING-SET(支配集):给定图 与整数 ,问是否存在 ,,使每个 要么在 中、要么与 中某点相邻。是 NPC。
  2. 等价构造:以图的最短路距离( = 最短路边数)为度量,取站点 = 顶点。则

    有大小 的支配集 存在 个中心 使 。

道理:半径 1 的球恰好覆盖"自己 + 邻居",正是支配的定义;反之支配集里的点作中心,半径必 。

  1. 关键:距离是整数。所以 只能取整数值; 与 之间没有中间地带。
  2. 反证:设存在多项式 -近似 。跑 得中心集 ,半径 。 - 若 有支配集 ⇒ ⇒ ⇒ 因 为整数, ⇒ 答 Yes。 - 若 无支配集 ⇒ ⇒ ⇒ 答 No。
  3. 结论:判据"?"在多项式时间内精确解出 DOMINATING-SET ⇒ ,矛盾。故 。

与 4.2.9 的同构性(这是本节最大的价值):

维度 4.2.9 bin packing 4.2.12 K-center
NPC 判定问题 2-BIN-PACKING DOMINATING-SET
判定阈值 vs vs
离散性从何而来 箱数是整数 距离是整数
临界
反证结论 无 无

一句总结:"近似的极限"= 你能不能用 把 NPC 的两个答案区间分开。区间越窄(要求 越小),越需要离散性来兜底。

一般 TSP 的对应结论(速查册提到):一般(非度量)TSP 无常数比近似(除非 ),因为"是否存在长度 的 Hamilton 回路"是 NPC,且可把边权放大到让近似算法无法区分;而度量 TSP 有 -近似(Christofides)。这条是对比记忆项。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

定理:除非 ,K-center 不存在比值 的多项式近似算法。证明用 DOMINATING-SET 归约:给定图 与整数 ,构造站点集 、距离取最短路边数(整数度量)、中心预算 ,则

若存在 -近似算法 ,跑得整数半径 :有支配集时 ,故 ;无支配集时 ,故 。判据「」精确判定 NP-完全问题,推出 。

2.2 OI Wiki 校订|基本性质

  1. 距离的整数性制造 1 与 2 之间的空隙。 理由:最短路边数是整数, 只能取整数, 与 之间没有中间值。最小实例:路径 、,取中心 后半径 。
  2. 半径 1 的球恰好是「自己加邻居」。 理由:最短路边数不超过 1 等价于同点或相邻,正是支配的定义。最小实例: 中以 为中心、半径 1 的球是 。
  3. 与 可以判定。 理由:整数性使 倍的上界落回 1。最小实例:、 时 ,由整数性得 。
  4. 与 下界同构。 理由:两者都是「NPC 判定 + 离散输出 + 严格小于阈值」,阈值分别为 与 。最小实例:装箱的临界值是 ,K-center 是 ,形式上都可写成 ( 为判定问题中的小常数)。
  5. 上下界吻合。 理由:4.2.11 的贪心达到 2,本节排除小于 2 的比值,故 2 最优。最小实例:直线上 、,最优取 得 ,若贪心首点取端点 则 ,说明 2 不可改进。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 证明的完整度。 CLRS 用 Dominating Set 归约证明同一下界,PPT 只给骨架句,未展开「距离是整数」;考试须补整数性一句,否则归约不成立。
  2. 整数性的地位。 课堂把整数距离称为关键,CLRS 同样依赖它;若距离取实数,-近似可返回 ,判据失效。考试按整数距离作答。
  3. 可引的 OI 内容。 OI Wiki《计算复杂性基础》给出 NPC 与多项式归约的定义,可支持「DOMINATING-SET 是 NPC」这一步;该页不含 K-center,不能引作下界来源。考试只引其 NPC 定义。
  4. 记号。 PPT 用 与 ,本节相同;CLRS 写 ,三者同义。考试统一写 。

来源:OI Wiki《计算复杂性基础》 · https://oi-wiki.org/misc/cc-basic/ (NPC 与归约定义);教材 CLRS 第 35 章给出同一下界。

3. 图示(统一模板)

NPC判定问题Q(Yes/No)│多项式构造▼优化问题的实例+离散结构│跑ρ-近似算法A▼算法输出a(离散/整数)│┌────────────┴────────────┐Q=Yes⇒OPT小⇒a<阈值Q=No⇒OPT大⇒a≥阈值└────────────┬────────────┘▼a与阈值比较⇒精确回答Q⇒P=NP──阈值表──binpacking:OPT≤2⇒a<3|OPT≥3⇒a≥3⇒ρ<3/2不可能K-center:r*≤1⇒R<2|r*≥2⇒R≥2⇒ρ<2不可能TSP(general):不存在这样的"窄区间"⇒无常数比

4. 复杂度表(各类问题的可近似性谱)

问题 有无 PTAS 最好常数比 下界(除非 P=NP)
Bin packing 无(NPC,无 PTAS 因 下界) (FFD) 不行
K-center 无 不行
一般 TSP 无 无常数比 任何常数都不行
度量 TSP 无 PTAS (Christofides) —
0-1 背包 有 FPTAS 弱 NP-hard,可任意逼近
分数背包 精确(贪心) —

5. 典型例题

Q. Prove that, unless , no -approximation algorithm exists for the K-center problem.

中文逐步:

  1. 取 NPC 的 DOMINATING-SET:图 、参数 ,问是否存在 个顶点支配全图。
  2. 把 变成 K-center 实例:站点 = 顶点,距离 = 最短路边数(整数度量),中心预算 = 。
  3. 双向等价: 有 的支配集 存在 中心 使 。
  4. 反设存在多项式 -近似 。跑 得 , 是整数。
  5. Yes 情形: ⇒ ⇒ 。 No 情形: ⇒ 。

  6. 故"?"精确判定 DOMINATING-SET,多项式完成 ⇒ ,矛盾。

  7. 补充一句"为什么 -center 的 2 是紧的":Greedy-Kcenter 达到 2(4.2.11),下界也是 2,上下吻合。

6. 易错点 + 老师原话

  • 易错:证明里漏掉"距离是整数"。没有它, 不能推出 ,整个归约塌掉。老师专门强调:「因为它是个整数的距离,它严格小于 2,它就只能等于 1,那我就回答了 dominating set 的问题。」 [稿:4-逐字稿:494]
  • 易错:把 DOMINATING-SET 与 VERTEX-COVER 混淆。支配集覆盖"自己+邻居",点覆盖覆盖"每条边的端点"——两者不同(虽然都 NPC)。
  • 易错:以为"K-center 有 2-近似"就意味着"2 可以改进"。2 是下界,不能改。
  • 易错:把 4.2.12 的模板套到装箱上时忘记改阈值( vs 的差别来自""结构)。
  • 老师对整套手法的定性:「技巧都是一样的……你就给他找一个 NPC,跟你这个问题,你发现如果你这个近似算法能够回答 yes 就意味着 npc 也回答了 yes,你回答 no 他也回答 no,那么你就解决了……这是通用的技巧。」 [稿:4-逐字稿:494]

7. 考试英文答题模板

Theorem. Unless , K-center admits no -approximation for any . Proof. Let and be an instance of the NP-complete DOMINATING-SET problem. Build a K-center instance with sites , centers, and = shortest-path distance in (an integer metric). Then has a dominating set of size iff there are centers with covering radius . Suppose a polynomial -approximation exists; run it to get integer radius . If a dominating set exists, , so and hence . If not, , so . Thus "is ?" decides DOMINATING-SET in polynomial time, implying —a contradiction. Therefore , matching the 2-approximation of the farthest-point greedy algorithm. The same reduction template (NPC decision problem + integrality of the algorithmic output) yields the lower bound for bin packing and the non-approximability of general TSP.


4.2.13 算法设计三目标(课程总结题) 【S 级 · 全课程收尾】

1. 一句话本质

任何算法设计都在三角之间取舍:A 最优性(Optimality,解的质量)、B 高效性(Efficiency,计算代价)、C 全实例(All instances,覆盖面)。三者不可同时满足;放弃其中一个,就得到一类算法。本课程四次课,本质上就是在给这张图填内容。

2. 定义与性质

Three aspects to be considered: A: Optimality — quality of a solution B: Efficiency — cost of computations C: All instances

Researchers are working on A+C: Exact algorithms for all instances A+B: Exact and fast algorithms for special cases B+C: Approximation algorithms

Even if P=NP, still we cannot guarantee A+B+C. [PPT:4下:460-471]

四个组合(含被放弃的那个):

目标组合 追求的 放弃的 代价 本课程对应
A+B+C 全部 — 不可达(若 ;PPT 并称即使 也不保证) 理想国
A+C 所有实例都最优 B(效率) 指数时间,NP-hard 回溯/分支限界;NPC 精确解
A+B 快且最优 C(覆盖面) 只覆盖特殊实例 特殊图上的多项式算法;伪多项式 DP
B+C 快、覆盖全实例 A(最优性) 解有 误差 近似算法(本讲)

老师原话:「全实例最优解:追求对所有输入实例都能得到最优解,此类精确算法可能是 NP-hard 的。特殊实例最优解:放弃对全实例的覆盖,仅针对部分特殊实例能在多项式时间内求得最优解。全实例快速解:放弃最优解,追求对所有实例都能快速运行,并能在最坏情况下提供质量保证,即近似算法。」 [纪要:4-conclusion:四、1]

为什么 A+B+C 不可达:若三者兼得,则所有 NPC 问题都能被"对所有实例最优 + 多项式"解决 ⇒ ,与 矛盾。所以只要承认 ,就必须至少放弃一个。PPT 又补了一句更狠的 "Even if P=NP, still we cannot guarantee A+B+C",这句应作修辞性强调理解:不可达不能只归因于「 与 是否相等」这一种来源——对判定层已超出 的问题(如 EXPTIME-hard 问题)、以及连可行解都无法在多项式时间内验证的问题,另有独立的障碍。反过来,对 内的优化问题,若真有 ,精确多项式算法就直接给出 A+B+C,所以不能把这句读成「存在 问题在 时仍无精确多项式解」。

2.1 OI Wiki 校订|形式定义

说明:OI Wiki 未收录本主题的独立条目,以下按教材(CLRS)口径整理。

三目标记为 A、B、C:

  • A 最优性(optimality):对问题 的每个实例 ,输出满足 ;
  • B 高效性(efficiency):运行时间是编码长度 的多项式;
  • C 全实例(all instances): 对 的每个实例都有定义,并输出可行解。 三者同时成立即「多项式时间精确算法」。PPT 断言 不可达,且补充「即使 也不能保证」;放弃其中一个目标,就得到 A+C、A+B、B+C 三类研究方向。

2.2 OI Wiki 校订|基本性质

  1. 成立推出 。 理由:对任一 NPC 优化问题,三者兼得即得多项式精确算法,其判定版本随之落入 。最小实例:若装箱满足 ,则 2-BIN-PACKING 可多项式判定,与 4.2.9 的下界冲突。
  2. 三目标不可达有两个层次的原因。 理由:其一, 对某个 优化问题成立,其判定版本即落入 ,故 时 -hard 问题一律不可达;其二,PPT 的「即使 」只能作修辞性强调——对判定层超出 的问题,或连可行性都无法多项式验证的问题,障碍与 是否等于 无关。最小实例:一般 TSP 的无常数比来自保空隙归约,前提是 ;若 ,一般 TSP 反而有多项式精确解,故这条不能用来支持「 时仍不可达」。
  3. 三个两两组合各对应一类算法。 理由:放弃一个目标即得一个方向。最小实例:A+C 是回溯( 时约 个结点),A+B 是排序与 MST,B+C 是 FFD(比值 )。
  4. 近似算法属于 B+C。 理由:它保住多项式与全实例,放弃 。最小实例:Next Fit 对任意列表只需 时间,并保证 。
  5. PTAS 仍属 B+C。 理由: 时 ,且固定 下仍是多项式。最小实例: 时 , 仍是 的多项式。

2.3 OI Wiki 校订|与课堂口径的差异

  1. 「即使 」的措辞。 这是 PPT 的结论性断言;CLRS 的同一定理写作「若 ,则不存在 小于某阈值的多项式近似算法」,前提明确。答卷引用 PPT 原句时应补出严格形式(以 为前提),并说明该句是修辞性强调,而非「存在 问题在 时仍不可精确求解」的定理;也不要由一般 TSP 断言其不属于 。
  2. 三分法的地位。 A、B、C 是课堂的总结框架,CLRS 无此术语;答题须先给英文全称(optimality、efficiency、all instances),再使用字母缩写。
  3. 可引的 OI 内容。 OI Wiki《计算复杂性基础》只提供 、、NPC 的定义,可用于支撑「 推出 」这一步;「即使 」一句超出该页,按 PPT 引用。
  4. C 的含义。 PPT 的 C 指 all instances(覆盖面),不是 complexity;易与 B 混淆,答题时须写明。

来源:OI Wiki《计算复杂性基础》 · https://oi-wiki.org/misc/cc-basic/ (P/NP/NPC 定义);三目标框架见 PPT 与课堂纪要。

3. 图示(全课程收尾总图)

A:Optimality△╱╲╱╲╱╲←A+B+C:空集(不可达)╱╲╱╲A+C─────────╱───────────╲─────────A+B精确·全实例╱╲精确·快(指数/NP-hard)╱╲(仅特殊实例)╱╲╱B+C:近似算法╲╱(本讲:装箱/背包/╲╱K-center)╲▽─────────────────────────▽B:EfficiencyC:Allinstances──课程四次课在这张图上的位置──第1–2次:B+C与A+B的基础工具(排序、树、散列、图算法)——这些问题多数在P内,A免费获得第3次:A+C的利器(分治、DP、贪心、回溯)——会撞上NP-hard的墙(4.1)第4次上:墙的画法(P/NP/NPC/NP-hard)(4.1)第4次下:撞墙后往哪走:B+C=近似算法(4.2)←本讲

全课程"四大经典算法思想"的定位(老师收尾时点出的):

「所有计算机专业学生必须掌握枚举(含回溯剪枝)与分治(含动态规划、贪心)两大派系下的四大经典算法思路。」 [纪要:4-conclusion:四、2]

派系 算法 属于哪一格 备注
枚举 回溯 + 剪枝 A+C 全实例最优,代价指数;剪枝只降常数
枚举 分支限界 A+C 同上,用界剪枝
分治 分治(Divide & Conquer) A+B 快且最优,但对问题结构有要求(如排序、FFT)
分治 动态规划 A+B(伪多项式时近似 A+C→B+C) 有最优子结构才行; 是伪多项式
分治 贪心 A+B 或 B+C 拟阵上精确(A+B),非拟阵上只能近似(B+C)
— 近似算法 B+C 本讲全部内容

4. 复杂度表(三目标的量化对照)

目标 时间 解质量 覆盖面 典型代表 理论障碍
A+C 指数 / 全部 回溯、分枝限界、 DP NPC(4.1)
A+B 多项式 部分 排序、MST、最短路、特殊图 问题结构限制
B+C 多项式 全部 NF/FF/FFD、K-center、背包贪心 近似下界(4.2.12)
A+B+C — 全部 不存在 / 不可近似性

5. 典型例题(课程总结题,标准答题模板)

Q. An algorithm designer faces a hard optimization problem. Explain the three goals A (optimality), B (efficiency), C (all instances), state why they cannot all be achieved simultaneously, and place the four classical algorithm-design paradigms and approximation algorithms on this map.

中文逐步:

  1. 三目标定义:A = 解的质量( 越接近 1 越好);B = 计算代价(多项式 vs 指数);C = 覆盖面(所有实例 vs 特殊实例)。
  2. 不可兼得:若 A+B+C 同时成立,则对任意 NPC 优化问题都有"多项式时间、全实例、最优"的算法 ⇒ 。更进一步,即便 ,某些问题(K-center 的 、一般 TSP 的无常数比)仍由归约给出不可近似性,所以 A+B+C 无条件不可达。
  3. 三个两两组合: - A+C(放弃 B):全实例最优,接受指数时间 ⇒ 回溯/分枝限界/枚举 DP。在 NPC 问题上必然超多项式。 - A+B(放弃 C):快且最优,只对特殊实例 ⇒ 排序、MST、最短路、特殊图上的多项式算法、有最优子结构的 DP。 - B+C(放弃 A):全实例都快,解有可证上界 ⇒ 近似算法:NF()、FF/BF()、FFD()、K-center()、0-1 背包贪心() 与 FPTAS()。
  4. 课程定位:前三次课主要在 A+B 区(P 内问题),第四次上揭示 A+C 的墙(NPC),第四次下给出 B+C 的出路(近似)。
  5. 收束句:近似算法不是"妥协的艺术",而是在 C(全实例)与 B(效率)锁死的前提下,把 A 变成可量化、可证明、可优化的目标—— 就是 A 的度量。

6. 易错点 + 老师原话

  • 易错:把"三目标不可兼得"说成"因为 "。PPT 原话是 "Even if P=NP, still we cannot guarantee A+B+C" [PPT:4下:471],说明障碍不止于 vs 。正确表述:"存在 也绕不过的不可近似性"。
  • 易错:把"特殊实例最优"理解成"随机实例最优"或"平均情况最优"。它是结构受限的实例类(如树、平面图、有界度数图),不是概率意义。
  • 易错:认为近似算法"放弃效率"。不是——近似算法恰恰是为了保住 B(多项式),才放弃 A。
  • 易错:认为 PTAS/FPTAS 属于 A 那一格。它们是 B+C 格内 可任意逼近 1 的极端情况,仍然放弃"精确"( 时 )。
  • 老师对全讲的收束:「我们今天的内容,跨过了边界线,再往 research 的前沿推一点。我们要知道什么是难问题,什么是简单问题。遇到难问题的话我们怎么办?其实是有很多办法的,我们今天就讲了比较可控的叫近似算法。我想了一种算法,我能知道质量;没法知道质量是大多数的情况,能知道质量的是少数的情况,大家要知道这一点。」 [稿:4-逐字稿:530] —— 这句正好把 4.2.2 的"能证明 vs 不能证明"抬到了全课程高度:"能知道质量"是稀有能力,这正是近似算法的全部价值。

7. 考试英文答题模板

Algorithm design involves three competing goals: (A) Optimality — the quality of the returned solution; (B) Efficiency — the computational cost; (C) All instances — the coverage of inputs. They cannot be achieved simultaneously: an algorithm satisfying A+B+C would solve every NP-hard optimization problem exactly in polynomial time on all inputs, implying ; moreover, even if , unconditional inapproximability results (e.g. no for K-center, no constant ratio for general TSP) still rule out A+B+C. Abandoning one goal yields the three research directions: • A+C (drop efficiency): exact algorithms for all instances — backtracking, branch-and-bound, exponential DP; inherently super-polynomial on NP-hard problems. • A+B (drop coverage): exact and fast algorithms for special instances — sorting, MST, shortest paths, polynomial algorithms on restricted graph classes, DP with optimal substructure. • B+C (drop optimality): approximation algorithms — polynomial time on all instances with a provable worst-case ratio , e.g. Next Fit (), First Fit (), FFD (), K-center (), and FPTAS schemes with . Approximation algorithms are therefore not a compromise but the systematic way of turning optimality into a quantifiable and provable objective once efficiency and full coverage are fixed.


附:本讲公式与结论速记表

项目 结论 出处
近似比定义 [PPT:4下:29-37]
近似 vs 启发式 能否证明 上界 [稿:4-逐字稿:125-128]
PTAS 判据 固定 ⇒ [PPT:4下:42-44]
FPTAS 判据 [PPT:4下:45-47]
、 FPTAS [PPT:4下:45]
、 PTAS only 判据
伪多项式 对 bit 数指数 [PPT:4下:280]、[稿:4-逐字稿:512]
Next Fit (紧) [PPT:4下:82-84]
NF 紧例手法 控序 + 尾堆 4.2.5
[稿:4-逐字稿:167-203]
分界点 [纪要:4-conclusion:三、1]
错误选项 ⇒ 时 荒谬 [稿:4-逐字稿:311]
First Fit / Best Fit ;在线下界 [PPT:4下:128-134,177-179]
FFD 带尾巴界 (不是 ratio) [PPT:4下:199-202]
FFD 真 ratio ; 时 [稿:4-逐字稿:341]
最优性 ⇒ 解 2-BIN-PACKING ⇒ [稿:4-逐字稿:485-491]
K-center 2-近似(最远点贪心); 不可能 [PPT:4下:434,443]
三目标 A 最优 / B 效率 / C 全实例;A+B+C 不可达 [PPT:4下:460-471]