算法导论 — 15.5 最优二叉搜索树

###笔记

二叉搜索树满足如下性质:假设 x x 是二叉搜索树中的一个结点。如果 l l x x 的左子树的一个结点,那么 l . k e y x . k e y l.key ≤ x.key 。如果 r r x x 的右子树的一个结点,那么 r . k e y x . k e y r.key ≥ x.key
  
  也就是说,二叉搜索树中的任意一个结点,它的左子树中的所有结点都不大于它,它的右子树中的所有结点都不小于它。下图给出了一个二叉搜索树的例子。
  这里写图片描述
  最优二叉搜索树(Optimal Binary Search Tree)问题描述如下。给定一个 n n 个不同关键字的已排序的序列 K [ 1.. n ] = < k 1 , k 2 , , k n > K[1..n]= <k_1, k_2, …, k_n> (因此 k 1 < k 2 < < k n k_1 < k_2 < … < k_n ),我们希望用这些关键字构造一个二叉搜索树。对每个关键字 k i k_i ,都有一个概率 p i p_i 表示其搜索概率。搜索过程中有可能遇到不在 K [ 1.. n ] K[1..n] 中的元素,因此我们还有 n + 1 n+1 个元素的“伪关键字”序列 D [ 0.. n ] = < d 0 , d 1 , d 2 , , d n > D[0..n] = <d_0, d_1, d_2, …, d_n> ,表示搜索过程中可能遇到的所有不在 K [ 1.. n ] K[1..n] 中的元素。 d 0 d_0 表示所有小于 k 1 k_1 的元素; d n d_n 表示所有大于 k n k_n 的元素;对 i = 1 , 2 , , n 1 i = 1, 2, …, n-1 d i d_i 表示所有在 k i k_i k i + 1 k_{i+1} 之间的元素。对每个伪关键字 d i d_i ,也有一个概率 q i q_i 表示对应的搜索概率。在二叉搜索树中,伪关键字 d i d_i 必然出现在叶结点上,关键字 k i k_i 必然出现在非叶结点上。每次搜索要么成功(找到某个关键字 k i k_i ),要么失败(找到某个伪关键字 d i d_i )。关键字和伪关键字的概率满足:
              这里写图片描述
  假定一次搜索的代价等于访问的结点数,也就是此次搜索找到的结点在二叉搜索树中的深度再加 1 1 。给定一棵二叉搜索树 T T ,我们可以确定进行一次搜索的期望代价。
        这里写图片描述
  其中 d e p t h T depth_T 表示一个结点在二叉搜索树 T T 中的深度。
  
  对于一组给定的关键字和伪关键字,以及它们对应的概率,我们希望构造一棵期望搜索代价最小的二叉搜索树,这称之为最优二叉搜索树。现在我们用动态规划方法来求解最优二叉搜索树问题。

首先我们描述最优二叉搜索树问题的最优子结构:假设由关键字子序列 K [ i . . j ] = < k i , , k j > K[i..j] = <k_i, …, k_j> 和伪关键字子序列 D [ i 1.. j ] = < d i 1 , , d j > D[i-1..j] = <d_{i-1}, …, d_j> 构成的一棵最优二叉搜索树以 k r ( i r j ) k_r(i ≤ r ≤ j) 为根结点。那么它的左子树由子序列 K [ i . . r 1 ] K[i..r-1] D [ i 1.. r 1 ] D[i-1..r-1] 构成,这颗左子树显然也是一棵最优二叉搜索树。同样,它的右子树由子序列 K [ r + 1.. j ] K[r+1..j] D [ r . . j ] D[r..j] 构成,这颗右子树显然也是一棵最优二叉搜索树。
  
  这里有一个值得注意的细节—空子树。如果包含子序列 K [ i . . j ] K[i..j] 的最优二叉搜索树以 k i k_i 为根结点。根据最优子结构性质,它的左子树包含子序列 K [ i . . i 1 ] K[i..i-1] ,这个子序列不包含任何关键字。但请注意,左子树仍然包含一个伪关键字 d i 1 d_{i-1} 。同理,如果选择 k j k_j 为根结点,那么右子树也不包含任何关键字,而只包含一个伪关键字 d j d_j
  
  用 e [ i , j ] e[i, j] 表示包含关键字子序列 K [ i . . j ] = < k i , , k j > K[i..j] = <k_i, …, k_j> 的最优二叉搜索树的期望搜索代价。我们最终希望计算出 e [ 1 , n ] e[1, n]
  
  对于 j = i 1 j = i-1 的情况,由于子树只包含伪关键字 d i 1 d_{i-1} ,所以期望搜索代价为 e [ i , i 1 ] = q i 1 e[i, i-1] = q_{i-1}
  
  当 j i j ≥ i 时,我们要遍历以 k i , k i + 1 , , k j k_i, k_{i+1}, …, k_j 作为根结点的情况,然后从中选择期望搜索代价最小的情况作为子问题的最优解。假设选择 k r ( i r j ) k_r(i ≤ r ≤ j) 作为根结点,那么子序列 K [ i . . r 1 ] K[i..r-1] 构成的最优二叉搜索树作为左子树,左子树的期望搜索代价为 e [ i , r 1 ] e[i, r-1] ;子序列 K [ r + 1.. j ] K[r+1..j] 构成的最优二叉搜索树作为右子树,右子树的期望搜索代价为 e [ r + 1 , j ] e[r+1, j]
  
  当一棵子树链接到一个根结点上时,子树中所有结点的深度都增加了 1 1 ,那么这棵子树的期望搜索代价的增加值为它的所有结点的概率之和。对于一棵包含子序列 K [ i . . j ] K[i..j] 的子树,所有结点的概率之和为
            这里写图片描述
  接上文,若 k r ( i r j ) k_r(i ≤ r ≤ j) 作为包含关键字子序列 K [ i . . j ] K[i..j] 的最优二叉搜索树的根结点,可以得到如下公式
         e [ i , j ] = p r + ( e [ i , r 1 ] + w [ i , r 1 ] ) + ( e [ r + 1 , j ] + w [ r + 1 , j ] ) e[i,j]=p_r+(e[i,r-1]+w[i,r-1])+(e[r+1,j]+w[r+1,j])

由于 w [ i , j ] = w [ i , r 1 ] + p r + w [ r + 1 , j ] w[i, j] = w[i, r-1] + p_r + w[r+1, j] ,所以上式可重写为
         e [ i , j ] = e [ i , r 1 ] + e [ r + 1 , j ] + w [ i , j ] e[i,j]=e[i,r-1]+e[r+1,j]+w[i,j]

我们要遍历以 k i , k i + 1 , , k j k_i, k_{i+1}, …, k_j 作为根结点的情况,并选择期望搜索代价最小的情况作为子问题的最优解。于是我们可以得到下面的递归式。
        这里写图片描述
   e [ i , j ] e[i, j] 给出了最优二叉搜索树子问题的期望搜索代价。我们还需要记录最优二叉搜索树子问题的根结点,用 r o o t [ i , j ] root[i, j] 来记录。
  
  根据上文给出的递归式,我们可以采用自下而上的动态规划方法来求解最优二叉搜索树问题。下面给出了伪代码。
  这里写图片描述
  我们可以看到,该问题的求解过程与15.2节矩阵链乘法问题是很相似的。该算法的时间复杂度也与矩阵链乘法问题一样,都为 Θ ( n 3 ) Θ(n^3)

###习题

15.5-1 设计伪代码 CONSTRUCT-OPTIMAL-BST(root) \text{CONSTRUCT-OPTIMAL-BST(root)} ,输入为表 r o o t root ,输出是最优二叉搜索树的结构。例如,对图15-10中的 r o o t root 表,应输出
   k 2 k_2 为根
   k 1 k_1 k 2 k_2 的左孩子
   d 0 d_0 k 1 k_1 的左孩子
   d 1 d_1 k 1 k_1 的右孩子
   k 5 k_5 k 2 k_2 的右孩子
   k 4 k_4 k 5 k_5 的左孩子
   k 3 k_3 k 4 k_4 的左孩子
   d 2 d_2 k 3 k_3 的左孩子
   d 3 d_3 k 3 k_3 的右孩子
   d 4 d_4 k 4 k_4 的右孩子
   d 5 d_5 k 5 k_5 的右孩子
  与图15-9(b)中的最优二叉搜索树对应。
  
  这里写图片描述
15.5-2 7 7 个关键字的概率如下所示,求其最优二叉搜索树的结构和代价。

i i 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7
p i p_i 0.04 0.04 0.06 0.06 0.08 0.08 0.02 0.02 0.10 0.10 0.12 0.12 0.14 0.14
q i q_i 0.06 0.06 0.06 0.06 0.06 0.06 0.06 0.06 0.05 0.05 0.05 0.05 0.05 0.05 0.05 0.05


  最优二叉搜索树如下所示。期望搜索代价为3.12。
  这里写图片描述
  这里写图片描述
  这里写图片描述
  这里写图片描述
15.5-3 假设 OPTIMAL-BST \text{OPTIMAL-BST} 不维护表 w [ i , j ] w[i, j] ,而是在第9行利用公式(15.12)直接计算 w [ i , j ] w[i, j] ,然后在第11行使用此值。如此改动会对渐近时间复杂性有何影响?
  
  在每次迭代中,直接利用公式(15.12)计算 w [ i , j ] w[i, j] 需要的时间为 O ( n ) O(n) 。然而, w [ i , j ] w[i, j] 的计算并不是在最后一层迭代中,并且计算 w [ i , j ] w[i, j] 的渐近时间与最后一层迭代的渐近时间是相同的。所以此改动并不改变 OPTIMAL-BST \text{OPTIMAL-BST} 的渐近时间。
  
15.5-4 Knuth[212]已经证明,对所有 1 i < j n 1 ≤ i < j ≤ n ,存在最优二叉搜索树,其根满足 r o o t [ i , j 1 ] r o o t [ i , j ] r o o t [ i + 1 , j ] root[i, j-1] ≤ root[i, j] ≤ root[i+1, j] 。利用这一特性修改算法 OPTIMAL-BST \text{OPTIMAL-BST} ,使得运行时间减少为 Θ ( n 2 ) Θ(n^2)
  
  在代码一中,在处理每个子问题 [ i , j ] [i, j] 时,从 i i j j 进行迭代(代码一第9行)。根据题目所给的结论,在处理每个子问题 [ i , j ] [i, j] 时,可以改为从 r o o t [ i , j 1 ] root[i, j-1] r o o t [ i + 1 , j ] root[i+1, j] 进行迭代。下面给出了伪代码。
  这里写图片描述
  现在分析 OPTIMAL-BST-KNUTH \text{OPTIMAL-BST-KNUTH} 的运行时间。
  先考虑子问题规模 l > 1 l > 1 的情况。规模为 l l 的子问题一共有 n l + 1 n-l+1 个(参见代码三第6行)。每个规模为 l l 的子问题包含 ( r o o t [ i + 1 , j ] r o o t [ i , j 1 ] + 1 ) (root[i+1, j] - root[i, j-1] + 1) 次迭代(其中 j = i + l 1 j = i+l-1 ),故求解所有规模为 l l 的子问题所需的迭代次数为
        这里写图片描述
  故求解所有规模为 l ( l > 1 ) l (l > 1) 的问题的迭代次数为 Θ ( n ) Θ(n) 。而每次迭代时间为 Θ ( 1 ) Θ(1) ,故求解所有规模为 l l 的子问题的时间为 Θ ( n ) Θ(n)
  而规模为 l = 1 l = 1 的子问题一共有 n n 个,每个子问题需要 Θ ( 1 ) Θ(1) 。故求解所有规模为 l = 1 l = 1 的子问题需要 Θ ( n ) Θ(n) 时间。
  子问题的规模 l l 的取值有 n n 种情况,故 OPTIMAL-BST-KNUTH \text{OPTIMAL-BST-KNUTH} 的运行时间为 Θ ( n 2 ) Θ(n^2)

本节相关的code链接。
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter15/Section_15.5
  这里写图片描述

猜你喜欢

转载自blog.csdn.net/yangtzhou/article/details/81713996