《算法导论》读书笔记与习题解答 — 15.5 最优二叉搜索树
笔记
二叉搜索树满足如下性质:假设是二叉搜索树中的一个结点。如果是的左子树的一个结点,那么。如果是的右子树的一个结点,那么。
也就是说,二叉搜索树中的任意一个结点,它的左子树中的所有结点都不大于它,它的右子树中的所有结点都不小于它。下图给出了一个二叉搜索树的例子。
最优二叉搜索树(Optimal Binary Search Tree)问题描述如下。给定一个个不同关键字的已排序的序列(因此),我们希望用这些关键字构造一个二叉搜索树。对每个关键字,都有一个概率表示其搜索概率。搜索过程中有可能遇到不在中的元素,因此我们还有个元素的“伪关键字”序列,表示搜索过程中可能遇到的所有不在中的元素。表示所有小于的元素;表示所有大于的元素;对,表示所有在到之间的元素。对每个伪关键字,也有一个概率表示对应的搜索概率。在二叉搜索树中,伪关键字必然出现在叶结点上,关键字必然出现在非叶结点上。每次搜索要么成功(找到某个关键字),要么失败(找到某个伪关键字)。关键字和伪关键字的概率满足:
假定一次搜索的代价等于访问的结点数,也就是此次搜索找到的结点在二叉搜索树中的深度再加。给定一棵二叉搜索树,我们可以确定进行一次搜索的期望代价。
其中表示一个结点在二叉搜索树中的深度。
对于一组给定的关键字和伪关键字,以及它们对应的概率,我们希望构造一棵期望搜索代价最小的二叉搜索树,这称之为最优二叉搜索树。现在我们用动态规划方法来求解最优二叉搜索树问题。
首先我们描述最优二叉搜索树问题的最优子结构:假设由关键字子序列和伪关键字子序列构成的一棵最优二叉搜索树以为根结点。那么它的左子树由子序列和构成,这颗左子树显然也是一棵最优二叉搜索树。同样,它的右子树由子序列和构成,这颗右子树显然也是一棵最优二叉搜索树。
这里有一个值得注意的细节—空子树。如果包含子序列的最优二叉搜索树以为根结点。根据最优子结构性质,它的左子树包含子序列,这个子序列不包含任何关键字。但请注意,左子树仍然包含一个伪关键字。同理,如果选择为根结点,那么右子树也不包含任何关键字,而只包含一个伪关键字。
用表示包含关键字子序列的最优二叉搜索树的期望搜索代价。我们最终希望计算出。
对于的情况,由于子树只包含伪关键字,所以期望搜索代价为。
当时,我们要遍历以作为根结点的情况,然后从中选择期望搜索代价最小的情况作为子问题的最优解。假设选择作为根结点,那么子序列构成的最优二叉搜索树作为左子树,左子树的期望搜索代价为;子序列构成的最优二叉搜索树作为右子树,右子树的期望搜索代价为。
当一棵子树链接到一个根结点上时,子树中所有结点的深度都增加了,那么这棵子树的期望搜索代价的增加值为它的所有结点的概率之和。对于一棵包含子序列的子树,所有结点的概率之和为
接上文,若作为包含关键字子序列的最优二叉搜索树的根结点,可以得到如下公式
由于,所以上式可重写为
我们要遍历以作为根结点的情况,并选择期望搜索代价最小的情况作为子问题的最优解。于是我们可以得到下面的递归式。
给出了最优二叉搜索树子问题的期望搜索代价。我们还需要记录最优二叉搜索树子问题的根结点,用来记录。
根据上文给出的递归式,我们可以采用自下而上的动态规划方法来求解最优二叉搜索树问题。下面给出了伪代码。
我们可以看到,该问题的求解过程与15.2节矩阵链乘法问题是很相似的。该算法的时间复杂度也与矩阵链乘法问题一样,都为。
习题
15.5-1 设计伪代码,输入为表,输出是最优二叉搜索树的结构。例如,对图15-10中的表,应输出
为根
为的左孩子
为的左孩子
为的右孩子
为的右孩子
为的左孩子
为的左孩子
为的左孩子
为的右孩子
为的右孩子
为的右孩子
与图15-9(b)中的最优二叉搜索树对应。
解
15.5-2 若个关键字的概率如下所示,求其最优二叉搜索树的结构和代价。
解
最优二叉搜索树如下所示。期望搜索代价为3.12。
15.5-3 假设不维护表,而是在第9行利用公式(15.12)直接计算,然后在第11行使用此值。如此改动会对渐近时间复杂性有何影响?
解
在每次迭代中,直接利用公式(15.12)计算需要的时间为。然而,的计算并不是在最后一层迭代中,并且计算的渐近时间与最后一层迭代的渐近时间是相同的。所以此改动并不改变的渐近时间。
15.5-4 Knuth[212]已经证明,对所有,存在最优二叉搜索树,其根满足。利用这一特性修改算法,使得运行时间减少为。
解
在代码一中,在处理每个子问题时,从到进行迭代(代码一第9行)。根据题目所给的结论,在处理每个子问题时,可以改为从到进行迭代。下面给出了伪代码。
现在分析的运行时间。
先考虑子问题规模的情况。规模为的子问题一共有个(参见代码三第6行)。每个规模为的子问题包含次迭代(其中),故求解所有规模为的子问题所需的迭代次数为
故求解所有规模为的问题的迭代次数为。而每次迭代时间为,故求解所有规模为的子问题的时间为。
而规模为的子问题一共有个,每个子问题需要。故求解所有规模为的子问题需要时间。
子问题的规模的取值有种情况,故的运行时间为。
本节相关的code链接。
https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter15/Section_15.5