《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树

笔记

  二叉搜索树满足如下性质:假设x是二叉搜索树中的一个结点。如果lx的左子树的一个结点,那么l.keyx.key。如果rx的右子树的一个结点,那么r.keyx.key
  
  也就是说,二叉搜索树中的任意一个结点,它的左子树中的所有结点都不大于它,它的右子树中的所有结点都不小于它。下图给出了一个二叉搜索树的例子。
  《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  最优二叉搜索树(Optimal Binary Search Tree)问题描述如下。给定一个n个不同关键字的已排序的序列K[1..n]=<k1,k2,,kn>(因此k1<k2<<kn),我们希望用这些关键字构造一个二叉搜索树。对每个关键字ki,都有一个概率pi表示其搜索概率。搜索过程中有可能遇到不在K[1..n]中的元素,因此我们还有n+1个元素的“伪关键字”序列D[0..n]=<d0,d1,d2,,dn>,表示搜索过程中可能遇到的所有不在K[1..n]中的元素。d0表示所有小于k1的元素;dn表示所有大于kn的元素;对i=1,2,,n1di表示所有在kiki+1之间的元素。对每个伪关键字di,也有一个概率qi表示对应的搜索概率。在二叉搜索树中,伪关键字di必然出现在叶结点上,关键字ki必然出现在非叶结点上。每次搜索要么成功(找到某个关键字ki),要么失败(找到某个伪关键字di)。关键字和伪关键字的概率满足:
              《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  假定一次搜索的代价等于访问的结点数,也就是此次搜索找到的结点在二叉搜索树中的深度再加1。给定一棵二叉搜索树T,我们可以确定进行一次搜索的期望代价。
        《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  其中depthT表示一个结点在二叉搜索树T中的深度。
  
  对于一组给定的关键字和伪关键字,以及它们对应的概率,我们希望构造一棵期望搜索代价最小的二叉搜索树,这称之为最优二叉搜索树。现在我们用动态规划方法来求解最优二叉搜索树问题。

  首先我们描述最优二叉搜索树问题的最优子结构:假设由关键字子序列K[i..j]=<ki,,kj>和伪关键字子序列D[i1..j]=<di1,,dj>构成的一棵最优二叉搜索树以kr(irj)为根结点。那么它的左子树由子序列K[i..r1]D[i1..r1]构成,这颗左子树显然也是一棵最优二叉搜索树。同样,它的右子树由子序列K[r+1..j]D[r..j]构成,这颗右子树显然也是一棵最优二叉搜索树。
  
  这里有一个值得注意的细节—空子树。如果包含子序列K[i..j]的最优二叉搜索树以ki为根结点。根据最优子结构性质,它的左子树包含子序列K[i..i1],这个子序列不包含任何关键字。但请注意,左子树仍然包含一个伪关键字di1。同理,如果选择kj为根结点,那么右子树也不包含任何关键字,而只包含一个伪关键字dj
  
  用e[i,j]表示包含关键字子序列K[i..j]=<ki,,kj>的最优二叉搜索树的期望搜索代价。我们最终希望计算出e[1,n]
  
  对于j=i1的情况,由于子树只包含伪关键字di1,所以期望搜索代价为e[i,i1]=qi1
  
  当ji时,我们要遍历以ki,ki+1,,kj作为根结点的情况,然后从中选择期望搜索代价最小的情况作为子问题的最优解。假设选择kr(irj)作为根结点,那么子序列K[i..r1]构成的最优二叉搜索树作为左子树,左子树的期望搜索代价为e[i,r1];子序列K[r+1..j]构成的最优二叉搜索树作为右子树,右子树的期望搜索代价为e[r+1,j]
  
  当一棵子树链接到一个根结点上时,子树中所有结点的深度都增加了1,那么这棵子树的期望搜索代价的增加值为它的所有结点的概率之和。对于一棵包含子序列K[i..j]的子树,所有结点的概率之和为
            《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  接上文,若kr(irj)作为包含关键字子序列K[i..j]的最优二叉搜索树的根结点,可以得到如下公式
        e[i,j]=pr+(e[i,r1]+w[i,r1])+(e[r+1,j]+w[r+1,j])

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

  我们要遍历以ki,ki+1,,kj作为根结点的情况,并选择期望搜索代价最小的情况作为子问题的最优解。于是我们可以得到下面的递归式。
        《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  e[i,j]给出了最优二叉搜索树子问题的期望搜索代价。我们还需要记录最优二叉搜索树子问题的根结点,用root[i,j]来记录。
  
  根据上文给出的递归式,我们可以采用自下而上的动态规划方法来求解最优二叉搜索树问题。下面给出了伪代码。
  《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
  我们可以看到,该问题的求解过程与15.2节矩阵链乘法问题是很相似的。该算法的时间复杂度也与矩阵链乘法问题一样,都为Θ(n3)

习题

15.5-1 设计伪代码CONSTRUCT-OPTIMAL-BST(root),输入为表root,输出是最优二叉搜索树的结构。例如,对图15-10中的root表,应输出
  k2为根
  k1k2的左孩子
  d0k1的左孩子
  d1k1的右孩子
  k5k2的右孩子
  k4k5的左孩子
  k3k4的左孩子
  d2k3的左孩子
  d3k3的右孩子
  d4k4的右孩子
  d5k5的右孩子
  与图15-9(b)中的最优二叉搜索树对应。
  
  《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
15.5-27个关键字的概率如下所示,求其最优二叉搜索树的结构和代价。

i 0 1 2 3 4 5 6 7
pi 0.04 0.06 0.08 0.02 0.10 0.12 0.14
qi 0.06 0.06 0.06 0.06 0.05 0.05 0.05 0.05

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

  本节相关的code链接。
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter15/Section_15.5
  《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树