相关笔记

概览

本节研究最优二叉搜索树(Optimal Binary Search Tree, Optimal BST)问题。给定一组已排序的关键字及其搜索概率,构造一棵使期望搜索代价最小的二叉搜索树。这是一个经典的区间DP问题,与14.2 矩阵链乘法类似,状态定义为连续区间 ,但递归公式的推导涉及概率加权的深度计算。本节将完整展示最优子结构证明、OPTIMAL-BST伪代码、 复杂度分析,以及Knuth优化将复杂度降至 的技术。


知识结构总览

flowchart TD
    A["最优二叉搜索树 OBST"] --> B["问题形式化"]
    A --> C["步骤1:最优子结构"]
    A --> D["步骤2:递归公式"]
    A --> E["步骤3:OPTIMAL-BST"]
    A --> F["步骤4:构造最优解"]
    A --> G["Knuth优化"]

    B --> B1["关键字概率 p_i"]
    B --> B2["哑关键字概率 q_j"]
    B --> B3["期望搜索代价"]

    C --> C1["剪切-粘贴证明"]
    C --> C2["左右子树独立性"]

    D --> D1["e[i,j] 定义"]
    D --> D2["w[i,j] 辅助量"]
    D --> D3["递归公式"]

    E --> E1["按区间长度递增填表"]
    E --> E2["时间 Θ(n³)"]

    G --> G1["root单调性"]
    G --> G2["四边形不等式"]
    G --> G3["时间降至 Θ(n²)"]

核心思想

核心思路

最优BST问题的核心思路是枚举根节点:对于包含关键字 的子树,尝试每个关键字 )作为根,然后递归地构造左右子树的最优BST。关键洞察是深度增加效应:当以 为根时,左右子树中所有节点的深度都增加了1,因此左右子树的期望代价各增加其概率之和。利用辅助量 (概率之和),可以将递归公式简化为 。按区间长度递增填表,时间复杂度

问题形式化

给定 不同的关键字,按升序排列:。每个关键字 被搜索到的概率为 )。

此外,还有 哑关键字(dummy keys) ,其中 表示小于 的搜索, 表示介于 之间的搜索(), 表示大于 的搜索。每个哑关键字 被搜索到的概率为 )。

概率约束

哑关键字代表搜索失败的情况。在实际应用中,并非所有搜索都能在BST中找到对应的关键字。哑关键字 对应BST中的”空子树”位置,搜索到达 意味着搜索失败。将失败搜索也纳入期望代价的计算,使模型更加完整和实用。

在BST中搜索一个关键字的代价等于访问的节点数(即从根到该节点路径上的节点数,也就是该节点的深度加1)。一棵BST的期望搜索代价为:

目标:构造一棵包含 的BST,使上述期望搜索代价最小。

最优子结构证明

最优BST具有以下最优子结构性质:如果一棵最优BST 以关键字 为根(),则:

  • 左子树 是包含关键字 和哑关键字 的一棵最优BST。
  • 右子树 是包含关键字 和哑关键字 的一棵最优BST。

证明(剪切-粘贴法): 【假设左子树 T_L 不最优,则存在更优的 T_L’】 假设 是包含关键字 的最优BST,以 为根。假设 的左子树 不是包含 的最优BST,则存在另一棵BST 包含 ,其期望搜索代价更低。

【替换 T_L 为 T_L’ 得到更优树 T’,与 T 最优矛盾】 中的 替换为 ,得到新树 。由于左子树和右子树的搜索是独立的(搜索左子树不会涉及右子树的节点,反之亦然), 的期望搜索代价将严格低于 ,这与 是最优BST矛盾。因此, 必须是最优的。同理, 也必须是最优的。

最优子结构定理

最优BST的最优子结构:最优BST的左右子树本身也是对应关键字范围的最优BST。BST的左右子树是独立的——搜索左子树中的关键字时,路径只经过根节点和左子树中的节点,不会涉及右子树。因此替换左子树不会影响右子树的搜索代价。这一独立性是剪切-粘贴论证成立的基础。

递归公式 e[i,j]

定义 为包含关键字 的最优BST的期望搜索代价。

边界情形:当 时,子树为空(只包含哑关键字 ),此时

辅助量 :定义为包含关键字 的子树中所有搜索概率之和:

可以递推计算:

递归公式

公式解读

  • 基准情形):空子树,只有哑关键字 ,代价为
  • 递归情形):尝试每个关键字 )作为根:
    • :左子树的最优期望代价。
    • :右子树的最优期望代价。
    • :由于以 为根后,左右子树中所有节点的深度都增加了1。

为什么加 而不是 当以 为根时,左子树 和右子树 中所有节点的深度都增加了1(相对于它们在各自子树中的深度)。因此,左子树的期望代价从 变为 ,右子树的期望代价从 变为 。加上根节点自身的代价 ,总代价为:

最后一步利用了

OPTIMAL-BST —— 伪代码

以下伪代码采用自底向上的表格法,按子树大小(即区间长度)递增的顺序计算。

算法执行流程

  1. 初始化边界:e[i][i-1] = w[i][i-1] = q[i-1](空子树只有哑关键字)
  2. 外层循环:对子树长度 l 从 1 到 n
  3. 中层循环:对起始位置 i 从 1 到 n-l+1,计算终止位置 j = i+l-1
  4. 初始化 e[i][j] = 无穷大,计算 w[i][j] = w[i][j-1] + p[j] + q[j]
  5. 内层循环:对每个根 r 从 i 到 j
  6. 计算代价 t = e[i][r-1] + e[r+1][j] + w[i][j]
  7. 若 t < e[i][j],更新 e[i][j] = t,记录最优根 root[i][j] = r
  8. 返回 e 和 root
flowchart TD
    A["e[i][i-1] = w[i][i-1] = q[i-1]"] --> B["l = 1"]
    B --> C{"l <= n?"}
    C -- "是" --> D["i = 1"]
    D --> E{"i <= n-l+1?"}
    E -- "是" --> F["j = i+l-1<br/>e[i][j] = inf<br/>w[i][j] = w[i][j-1]+p[j]+q[j]<br/>r = i"]
    F --> G{"r <= j?"}
    G -- "是" --> H["t = e[i][r-1]+e[r+1][j]+w[i][j]"]
    H --> I{"t < e[i][j]?"}
    I -- "是" --> J["e[i][j] = t<br/>root[i][j] = r"]
    I -- "否" --> K["r = r + 1"]
    J --> K
    K --> G
    G -- "否" --> L["i = i + 1"]
    L --> E
    E -- "否" --> M["l = l + 1"]
    M --> C
    C -- "否" --> N["返回 e 和 root"]
OPTIMAL-BST(p, q, n):
1  let e[1..n+1, 0..n], w[1..n+1, 0..n], root[1..n, 1..n] be new tables
2  for i = 1 to n + 1
3      e[i, i-1] = q_{i-1}
4      w[i, i-1] = q_{i-1}
5  for l = 1 to n
6      for i = 1 to n - l + 1
7          j = i + l - 1
8          e[i, j] = ∞
9          w[i, j] = w[i, j-1] + p_j + q_j
10         for r = i to j
11             t = e[i, r-1] + e[r+1, j] + w[i, j]
12             if t < e[i, j]
13                 e[i, j] = t
14                 root[i, j] = r
15 return e and root

逐行解释

  • 第1行:创建三个表格。 表存储期望搜索代价, 表存储概率之和, 表存储最优根。
  • 第2-4行:初始化边界条件。,对应空子树。
  • 第5行:外层循环按子树大小 (区间长度)从1到 递增。这保证了计算 时,所有更小的子问题 已经被计算完毕。
  • 第6-7行:内层循环枚举区间的起点 ,计算终点
  • 第8行:将 初始化为无穷大。
  • 第9行:利用递推公式计算
  • 第10-14行:枚举根 ,计算以 为根时的期望代价 ,取最小值并记录最优根。
  • 第15行:返回 表和 表。

按区间长度递增填表是区间DP的标准策略。计算 (区间长度为 )时,需要 (区间长度都小于 ),它们在之前的迭代中已经被计算完毕。这与14.2 矩阵链乘法中按链长递增填表的策略完全一致。

CONSTRUCT-OPTIMAL-BST —— 伪代码

利用 表可以递归地构造最优BST。从 开始,递归构造左右子树。

CONSTRUCT-OPTIMAL-BST(root, i, j, last_direction, last):
1  if i > j
2      print "d"_{j} " is " last_direction " child of " "k"_{last}
3      return
4  r = root[i, j]
5  if last_direction == "none"
6      print "k"_{r} " is the root"
7  else
8      print "k"_{r} " is " last_direction " child of " "k"_{last}
9  CONSTRUCT-OPTIMAL-BST(root, i, r-1, "left", r)
10 CONSTRUCT-OPTIMAL-BST(root, r+1, j, "right", r)

逐行解释

  • 第1-3行:基准情形——当 时,子树为空,输出哑关键字
  • 第4行:查询 获取当前区间的最优根
  • 第5-8行:根据是否为根节点,输出相应的父子关系。
  • 第9-10行:递归构造左子树和右子树。

告诉我们区间 的最优根是谁。知道根之后,左子树对应 ,右子树对应 ,递归处理即可。这与14.2 矩阵链乘法中利用 表回溯最优括号化方案的思路完全一致。

循环不变式与正确性证明

循环不变式

对于OPTIMAL-BST算法的外层循环(第5行),考虑循环不变式:在第 次迭代开始时,所有区间长度小于 已被正确计算。

【初始化(l=1 时所有 e[i,i-1]=q_{i-1} 正确对应空子树)】 在第一次迭代()开始前,第2-4行已将所有 正确初始化(对应空子树)。区间长度为0的所有子问题已正确计算。

【维护(e[i,r-1] 和 e[r+1,j] 的区间长度均 < l)】 假设在第 次迭代开始时,所有区间长度小于 的子问题已正确。内层循环(第6行)对每个区间 (长度为 )计算 依赖于 ,这两个子问题的区间长度都严格小于 ,由归纳假设已被正确计算。因此 被正确计算为所有候选根中的最小值。

【终止(l=n+1 时 e[1,n] 即为原问题最优期望搜索代价)】 时循环结束,所有 )均已正确计算,包括 (原问题的最优期望搜索代价)。

时间复杂度分析

时间复杂度

OPTIMAL-BST

  • 外层循环 执行 次。
  • 中层循环 平均执行 次。
  • 内层循环 平均执行 次。
  • 总计

空间复杂度:三个表格各 ,总计

Knuth优化后:利用最优根的单调性,将内层循环的搜索范围从 缩小到 ,时间复杂度降至

Knuth优化

Knuth 于 1971 年证明了一个重要的单调性结论:

这意味着,当区间 的右端点 增大时,最优根 不会向左移动;当左端点 增大时,最优根不会向右移动。

利用这一单调性,可以将内层循环(枚举根 )的搜索范围从 缩小到 。这一优化将时间复杂度从 降至

优化后的伪代码

算法执行流程

  1. 初始化边界:e[i][i-1] = w[i][i-1] = q[i-1],root[i][i] = i
  2. 外层循环:对子树长度 l 从 2 到 n
  3. 中层循环:对起始位置 i 从 1 到 n-l+1,计算终止位置 j = i+l-1
  4. 初始化 e[i][j] = 无穷大,计算 w[i][j] = w[i][j-1] + p[j] + q[j]
  5. 内层循环(Knuth优化):根 r 的搜索范围缩小为 root[i][j-1] 到 root[i+1][j]
  6. 计算代价 t = e[i][r-1] + e[r+1][j] + w[i][j]
  7. 若 t < e[i][j],更新 e[i][j] = t,记录 root[i][j] = r
  8. 返回 e 和 root
flowchart TD
    A["e[i][i-1] = w[i][i-1] = q[i-1]<br/>root[i][i] = i"] --> B["l = 2"]
    B --> C{"l <= n?"}
    C -- "是" --> D["i = 1"]
    D --> E{"i <= n-l+1?"}
    E -- "是" --> F["j = i+l-1<br/>e[i][j] = inf<br/>w[i][j] = w[i][j-1]+p[j]+q[j]<br/>r = root[i][j-1]"]
    F --> G{"r <= root[i+1][j]?"}
    G -- "是" --> H["t = e[i][r-1]+e[r+1][j]+w[i][j]"]
    H --> I{"t < e[i][j]?"}
    I -- "是" --> J["e[i][j] = t<br/>root[i][j] = r"]
    I -- "否" --> K["r = r + 1"]
    J --> K
    K --> G
    G -- "否" --> L["i = i + 1"]
    L --> E
    E -- "否" --> M["l = l + 1"]
    M --> C
    C -- "否" --> N["返回 e 和 root"]
KNUTH-OPTIMAL-BST(p, q, n):
1  let e[1..n+1, 0..n], w[1..n+1, 0..n], root[1..n, 1..n] be new tables
2  for i = 1 to n + 1
3      e[i, i-1] = q_{i-1}
4      w[i, i-1] = q_{i-1}
5  for i = 1 to n
6      root[i, i] = i
7  for l = 2 to n
8      for i = 1 to n - l + 1
9          j = i + l - 1
10         e[i, j] = ∞
11         w[i, j] = w[i, j-1] + p_j + q_j
12         for r = root[i, j-1] to root[i+1, j]
13             t = e[i, r-1] + e[r+1, j] + w[i, j]
14             if t < e[i, j]
15                 e[i, j] = t
16                 root[i, j] = r
17 return e and root

Knuth优化的适用条件:需要代价函数满足四边形不等式(Quadrangle Inequality)单调性条件。最优BST问题满足这些条件,因此可以应用Knuth优化。并非所有区间DP问题都能应用此优化,需要具体分析。


补充理解与拓展

Knuth优化的学术溯源

Knuth 于 1971 年在 Acta Informatica 上发表了最优BST的 算法。通过证明 的单调性,将朴素 算法优化为 。这一优化技术后来被称为”Knuth优化”,被广泛应用于满足四边形不等式的区间DP问题。1

四边形不等式的一般理论

Yao 于 1980 年在 STOC 上将Knuth优化推广为更一般的”四边形不等式优化”框架。Yao 给出了判断区间DP问题是否满足决策单调性的充分条件:如果代价函数 满足四边形不等式 (其中 )和单调性 (其中 ),则最优决策点满足单调性。2


易混淆点与辨析

最优BST vs 红黑树

❌ 错误理解:最优BST和红黑树都是”最优的”二叉搜索树,它们解决同一个问题。 ✅ 正确理解:它们解决的是完全不同的优化目标最优BST最小化的是期望搜索代价(假设已知每个关键字的搜索频率),是一种离线算法(需要预先知道所有频率才能构造)。红黑树保证的是最坏搜索代价为 ,是一种在线数据结构(支持动态插入和删除)。最优BST可能的最坏搜索代价为 (退化为链),但它对已知频率分布的搜索场景是最优的。在实际应用中,如果搜索频率已知且不变化,最优BST更优;如果需要动态更新或需要最坏情况保证,红黑树更合适。

最优BST vs AVL树

❌ 错误理解:最优BST比AVL树更好,因为它是”最优的”。 ✅ 正确理解:两者的设计目标不同。最优BST优化的是期望搜索代价(基于已知的搜索概率分布),但不保证最坏情况。AVL树通过严格的平衡条件保证最坏搜索代价为 ,但不考虑搜索频率。AVL树支持高效的动态插入和删除( 摊还),而最优BST的构造需要 时间且不支持高效更新。如果搜索频率分布高度不均匀(如某些关键字被频繁搜索),最优BST可能显著优于AVL树。


习题精选

题号题目描述难度考察重点
14.5-1对给定的关键字和概率,构造最优BST★★☆OPTIMAL-BST的执行过程
14.5-2给定一棵BST的关键字和深度,计算其期望搜索代价★☆☆期望搜索代价的计算
14.5-3如果所有 ,最优BST的形状是什么★★☆概率为零时的退化情况
14.5-4证明 Knuth 优化中的单调性不等式★★★Knuth优化的理论基础

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 12DP II: Text Justification, Blackjackhttps://www.youtube.com/watch?v=ENyox7kNKeYMIT经典DP课程
Abdul BariOptimal Binary Search Treehttps://www.youtube.com/watch?v=h9nTzOY2RwA直观讲解最优BST的构造过程
Tushar RoyOptimal BST DPhttps://www.youtube.com/watch?v=Ag2j6FOhS1E完整最优BST算法推导与代码实现
Back To Back SWEOptimal Binary Search Treehttps://www.youtube.com/watch?v=MAF1dpy5b8c清晰的最优BST算法讲解
董晓算法最优二叉搜索树https://www.bilibili.com/video/BV1xb411e7xx中文最优BST讲解,配合动画演示

教材原文

CLRS 第4版 14.5节原文

假设我们正在设计一个将文本从英语翻译成法语的程序。对于文本中每个英语单词的出现,我们需要查找其法语等价词。我们可以用二叉搜索树来执行这些查找。

我们希望构造一棵使期望搜索代价最小的二叉搜索树。给定 个不同的关键字 (已排序),每个关键字 被搜索到的概率为 。此外,搜索可能不成功,即搜索的关键字不在树中。我们用 个哑关键字 来表示搜索失败的情况,每个哑关键字 被搜索到的概率为

一棵包含关键字 的二叉搜索树的期望搜索代价为

最优BST问题具有最优子结构性质。如果最优BST 为根,则其左右子树分别是对应关键字范围的最优BST。递归公式为 ,其中 是所有相关概率之和。算法按区间长度递增填表,时间复杂度为


参见Wiki

章节导航:

关联知识:

第14章-动态规划 最优二叉搜索树

Footnotes

  1. Knuth, D. E. (1971). “Optimum binary search trees.” Acta Informatica, 1(1), 14-25. Knuth 在本文中首次给出了最优BST的 算法,通过证明 的单调性,将朴素 算法优化为 。这一优化技术后来被称为”Knuth优化”,被广泛应用于满足四边形不等式的区间DP问题。

  2. Yao, F. F. (1980). “Efficient dynamic programming using quadrangle inequalities.” Proceedings of the 12th Annual ACM Symposium on Theory of Computing (STOC), 429-435. Yao 在本文中将Knuth优化推广为更一般的”四边形不等式优化”框架,给出了判断区间DP问题是否满足决策单调性的充分条件,为Knuth优化的广泛应用奠定了理论基础。