笔记
二叉搜索树满足如下性质:假设 x x x是二叉搜索树中的一个结点。如果 l l l是 x x x的左子树的一个结点,那么 l . k e y ≤ x . k e y l.key ≤ x.key l.key≤x.key。如果 r r r是 x x x的右子树的一个结点,那么 r . k e y ≥ x . k e y r.key ≥ x.key r.key≥x.key。
也就是说,二叉搜索树中的任意一个结点,它的左子树中的所有结点都不大于它,它的右子树中的所有结点都不小于它。下图给出了一个二叉搜索树的例子。

最优二叉搜索树(Optimal Binary Search Tree)问题描述如下。给定一个 n n n个不同关键字的已排序的序列 K [ 1.. n ] = < k 1 , k 2 , … , k n > K[1..n]=

假定一次搜索的代价等于访问的结点数,也就是此次搜索找到的结点在二叉搜索树中的深度再加 1 1 1。给定一棵二叉搜索树 T T T,我们可以确定进行一次搜索的期望代价。

其中 d e p t h T depth_T depthT表示一个结点在二叉搜索树 T T T中的深度。
对于一组给定的关键字和伪关键字,以及它们对应的概率,我们希望构造一棵期望搜索代价最小的二叉搜索树,这称之为最优二叉搜索树。现在我们用动态规划方法来求解最优二叉搜索树问题。
首先我们描述最优二叉搜索树问题的最优子结构:假设由关键字子序列 K [ i . . j ] = < k i , … , k j > K[i..j] =
这里有一个值得注意的细节—空子树。如果包含子序列 K [ i . . j ] K[i..j] K[i..j]的最优二叉搜索树以 k i k_i ki为根结点。根据最优子结构性质,它的左子树包含子序列 K [ i . . i − 1 ] K[i..i-1] K[i..i−1],这个子序列不包含任何关键字。但请注意,左子树仍然包含一个伪关键字 d i − 1 d_{i-1} di−1。同理,如果选择 k j k_j kj为根结点,那么右子树也不包含任何关键字,而只包含一个伪关键字 d j d_j dj。
用 e [ i , j ] e[i, j] e[i,j]表示包含关键字子序列 K [ i . . j ] = < k i , … , k j > K[i..j] =
对于 j = i − 1 j = i-1 j=i−1的情况,由于子树只包含伪关键字 d i − 1 d_{i-1} di−1,所以期望搜索代价为 e [ i , i − 1 ] = q i − 1 e[i, i-1] = q_{i-1} e[i,i−1]=qi−1。
当 j ≥ i j ≥ i j≥i时,我们要遍历以 k i , k i + 1 , … , k j k_i, k_{i+1}, …, k_j ki,ki+1,…,kj作为根结点的情况,然后从中选择期望搜索代价最小的情况作为子问题的最优解。假设选择 k r ( i ≤ r ≤ j ) k_r(i ≤ r ≤ j) kr(i≤r≤j)作为根结点,那么子序列 K [ i . . r − 1 ] K[i..r-1] K[i..r−1]构成的最优二叉搜索树作为左子树,左子树的期望搜索代价为 e [ i , r − 1 ] e[i, r-1] e[i,r−1];子序列 K [ r + 1.. j ] K[r+1..j] K[r+1..j]构成的最优二叉搜索树作为右子树,右子树的期望搜索代价为 e [ r + 1 , j ] e[r+1, j] e[r+1,j]。
当一棵子树链接到一个根结点上时,子树中所有结点的深度都增加了 1 1 1,那么这棵子树的期望搜索代价的增加值为它的所有结点的概率之和。对于一棵包含子序列 K [ i . . j ] K[i..j] K[i..j]的子树,所有结点的概率之和为

接上文,若 k r ( i ≤ r ≤ j ) k_r(i ≤ r ≤ j) kr(i≤r≤j)作为包含关键字子序列 K [ i . . 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]) e[i,j]=pr+(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] w[i,j]=w[i,r−1]+pr+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] 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 ki,ki+1,…,kj作为根结点的情况,并选择期望搜索代价最小的情况作为子问题的最优解。于是我们可以得到下面的递归式。

e [ i , j ] e[i, j] e[i,j]给出了最优二叉搜索树子问题的期望搜索代价。我们还需要记录最优二叉搜索树子问题的根结点,用 r o o t [ i , j ] root[i, j] root[i,j]来记录。
根据上文给出的递归式,我们可以采用自下而上的动态规划方法来求解最优二叉搜索树问题。下面给出了伪代码。

我们可以看到,该问题的求解过程与15.2节矩阵链乘法问题是很相似的。该算法的时间复杂度也与矩阵链乘法问题一样,都为 Θ ( n 3 ) Θ(n^3) Θ(n3)。
习题
15.5-1 设计伪代码 CONSTRUCT-OPTIMAL-BST(root) \text{CONSTRUCT-OPTIMAL-BST(root)} CONSTRUCT-OPTIMAL-BST(root),输入为表 r o o t root root,输出是最优二叉搜索树的结构。例如,对图15-10中的 r o o t root root表,应输出
k 2 k_2 k2为根
k 1 k_1 k1为 k 2 k_2 k2的左孩子
d 0 d_0 d0为 k 1 k_1 k1的左孩子
d 1 d_1 d1为 k 1 k_1 k1的右孩子
k 5 k_5 k5为 k 2 k_2 k2的右孩子
k 4 k_4 k4为 k 5 k_5 k5的左孩子
k 3 k_3 k3为 k 4 k_4 k4的左孩子
d 2 d_2 d2为 k 3 k_3 k3的左孩子
d 3 d_3 d3为 k 3 k_3 k3的右孩子
d 4 d_4 d4为 k 4 k_4 k4的右孩子
d 5 d_5 d5为 k 5 k_5 k5的右孩子
与图15-9(b)中的最优二叉搜索树对应。
解

15.5-2 若 7 7 7个关键字的概率如下所示,求其最优二叉搜索树的结构和代价。
| i i i | 0 0 0 | 1 1 1 | 2 2 2 | 3 3 3 | 4 4 4 | 5 5 5 | 6 6 6 | 7 7 7 |
|---|---|---|---|---|---|---|---|---|
| p i p_i pi | 0.04 0.04 0.04 | 0.06 0.06 0.06 | 0.08 0.08 0.08 | 0.02 0.02 0.02 | 0.10 0.10 0.10 | 0.12 0.12 0.12 | 0.14 0.14 0.14 | |
| q i q_i qi | 0.06 0.06 0.06 | 0.06 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 0.05 | 0.05 0.05 0.05 |
解
最优二叉搜索树如下所示。期望搜索代价为3.12。




15.5-3 假设 OPTIMAL-BST \text{OPTIMAL-BST} OPTIMAL-BST不维护表 w [ i , j ] w[i, j] w[i,j],而是在第9行利用公式(15.12)直接计算 w [ i , j ] w[i, j] w[i,j],然后在第11行使用此值。如此改动会对渐近时间复杂性有何影响?
解
在每次迭代中,直接利用公式(15.12)计算 w [ i , j ] w[i, j] w[i,j]需要的时间为 O ( n ) O(n) O(n)。然而, w [ i , j ] w[i, j] w[i,j]的计算并不是在最后一层迭代中,并且计算 w [ i , j ] w[i, j] w[i,j]的渐近时间与最后一层迭代的渐近时间是相同的。所以此改动并不改变 OPTIMAL-BST \text{OPTIMAL-BST} OPTIMAL-BST的渐近时间。
15.5-4 Knuth[212]已经证明,对所有 1 ≤ i < j ≤ n 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] root[i,j−1]≤root[i,j]≤root[i+1,j]。利用这一特性修改算法 OPTIMAL-BST \text{OPTIMAL-BST} OPTIMAL-BST,使得运行时间减少为 Θ ( n 2 ) Θ(n^2) Θ(n2)。
解
在代码一中,在处理每个子问题 [ i , j ] [i, j] [i,j]时,从 i i i到 j j j进行迭代(代码一第9行)。根据题目所给的结论,在处理每个子问题 [ i , j ] [i, j] [i,j]时,可以改为从 r o o t [ i , j − 1 ] root[i, j-1] root[i,j−1]到 r o o t [ i + 1 , j ] root[i+1, j] root[i+1,j]进行迭代。下面给出了伪代码。

现在分析 OPTIMAL-BST-KNUTH \text{OPTIMAL-BST-KNUTH} OPTIMAL-BST-KNUTH的运行时间。
先考虑子问题规模 l > 1 l > 1 l>1的情况。规模为 l l l的子问题一共有 n − l + 1 n-l+1 n−l+1个(参见代码三第6行)。每个规模为 l 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) (root[i+1,j]−root[i,j−1]+1)次迭代(其中 j = i + l − 1 j = i+l-1 j=i+l−1),故求解所有规模为 l l l的子问题所需的迭代次数为

故求解所有规模为 l ( l > 1 ) l (l > 1) l(l>1)的问题的迭代次数为 Θ ( n ) Θ(n) Θ(n)。而每次迭代时间为 Θ ( 1 ) Θ(1) Θ(1),故求解所有规模为 l l l的子问题的时间为 Θ ( n ) Θ(n) Θ(n)。
而规模为 l = 1 l = 1 l=1的子问题一共有 n n n个,每个子问题需要 Θ ( 1 ) Θ(1) Θ(1)。故求解所有规模为 l = 1 l = 1 l=1的子问题需要 Θ ( n ) Θ(n) Θ(n)时间。
子问题的规模 l l l的取值有 n n n种情况,故 OPTIMAL-BST-KNUTH \text{OPTIMAL-BST-KNUTH} OPTIMAL-BST-KNUTH的运行时间为 Θ ( n 2 ) Θ(n^2) Θ(n2)。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222375.html原文链接:https://javaforall.net
