算法导论 — 15.5 最优二叉搜索树
###笔记
二叉搜索树满足如下性质:假设xxx是二叉搜索树中的一个结点。如果lll是xxx的左子树的一个结点,那么l.key≤x.keyl.key ≤ x.keyl.key≤x.key。如果rrr是xxx的右子树的一个结点,那么r.key≥x.keyr.key ≥ x.keyr.key≥x.key。
也就是说,二叉搜索树中的任意一个结点,它的左子树中的所有结点都不大于它,它的右子树中的所有结点都不小于它。下图给出了一个二叉搜索树的例子。
最优二叉搜索树(Optimal Binary Search Tree)问题描述如下。给定一个nnn个不同关键字的已排序的序列K[1..n]=<k1,k2,…,kn>K[1..n]= <k_1, k_2, …, k_n>K[1..n]=<k1,k2,…,kn>(因此k1<k2<…<knk_1 < k_2 < … < k_nk1<k2<…<kn),我们希望用这些关键字构造一个二叉搜索树。对每个关键字kik_iki,都有一个概率pip_ipi表示其搜索概率。搜索过程中有可能遇到不在K[1..n]K[1..n]K[1..n]中的元素,因此我们还有n+1n+1n+1个元素的“伪关键字”序列D[0..n]=<d0,d1,d2,…,dn>D[0..n] = <d_0, d_1, d_2, …, d_n>D[0..n]=<d0,d1,d2,…,dn>,表示搜索过程中可能遇到的所有不在K[1..n]K[1..n]K[1..n]中的元素。d0d_0d0表示所有小于k1k_1k1的元素;dnd_ndn表示所有大于knk_nkn的元素;对i=1,2,…,n−1i = 1, 2, …, n-1i=1,2,…,n−1,did_idi表示所有在kik_iki到ki+1k_{i+1}ki+1之间的元素。对每个伪关键字did_idi,也有一个概率qiq_iqi表示对应的搜索概率。在二叉搜索树中,伪关键字did_idi必然出现在叶结点上,关键字kik_iki必然出现在非叶结点上。每次搜索要么成功(找到某个关键字kik_iki),要么失败(找到某个伪关键字did_idi)。关键字和伪关键字的概率满足:
假定一次搜索的代价等于访问的结点数,也就是此次搜索找到的结点在二叉搜索树中的深度再加111。给定一棵二叉搜索树TTT,我们可以确定进行一次搜索的期望代价。
其中depthTdepth_TdepthT表示一个结点在二叉搜索树TTT中的深度。
对于一组给定的关键字和伪关键字,以及它们对应的概率,我们希望构造一棵期望搜索代价最小的二叉搜索树,这称之为最优二叉搜索树。现在我们用动态规划方法来求解最优二叉搜索树问题。
首先我们描述最优二叉搜索树问题的最优子结构:假设由关键字子序列K[i..j]=<ki,…,kj>K[i..j] = <k_i, …, k_j>K[i..j]=<ki,…,kj>和伪关键字子序列D[i−1..j]=<di−1,…,dj>D[i-1..j] = <d_{i-1}, …, d_j>D[i−1..j]=<di−1,…,dj>构成的一棵最优二叉搜索树以kr(i≤r≤j)k_r(i ≤ r ≤ j)kr(i≤r≤j)为根结点。那么它的左子树由子序列K[i..r−1]K[i..r-1]K[i..r−1]和D[i−1..r−1]D[i-1..r-1]D[i−1..r−1]构成,这颗左子树显然也是一棵最优二叉搜索树。同样,它的右子树由子序列K[r+1..j]K[r+1..j]K[r+1..j]和D[r..j]D[r..j]D[r..j]构成,这颗右子树显然也是一棵最优二叉搜索树。
这里有一个值得注意的细节—空子树。如果包含子序列K[i..j]K[i..j]K[i..j]的最优二叉搜索树以kik_iki为根结点。根据最优子结构性质,它的左子树包含子序列K[i..i−1]K[i..i-1]K[i..i−1],这个子序列不包含任何关键字。但请注意,左子树仍然包含一个伪关键字di−1d_{i-1}di−1。同理,如果选择kjk_jkj为根结点,那么右子树也不包含任何关键字,而只包含一个伪关键字djd_jdj。
用e[i,j]e[i, j]e[i,j]表示包含关键字子序列K[i..j]=<ki,…,kj>K[i..j] = <k_i, …, k_j>K[i..j]=<ki,…,kj>的最优二叉搜索树的期望搜索代价。我们最终希望计算出e[1,n]e[1, n]e[1,n]。
对于j=i−1j = i-1j=i−1的情况,由于子树只包含伪关键字di−1d_{i-1}di−1,所以期望搜索代价为e[i,i−1]=qi−1e[i, i-1] = q_{i-1}e[i,i−1]=qi−1。
当j≥ij ≥ ij≥i时,我们要遍历以ki,ki+1,…,kjk_i, k_{i+1}, …, k_jki,ki+1,…,kj作为根结点的情况,然后从中选择期望搜索代价最小的情况作为子问题的最优解。假设选择kr(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]。
当一棵子树链接到一个根结点上时,子树中所有结点的深度都增加了111,那么这棵子树的期望搜索代价的增加值为它的所有结点的概率之和。对于一棵包含子序列K[i..j]K[i..j]K[i..j]的子树,所有结点的概率之和为
接上文,若kr(i≤r≤j)k_r(i ≤ r ≤ j)kr(i≤r≤j)作为包含关键字子序列K[i..j]K[i..j]K[i..j]的最优二叉搜索树的根结点,可以得到如下公式
e[i,j]=pr+(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]+pr+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]
我们要遍历以ki,ki+1,…,kjk_i, k_{i+1}, …, k_jki,ki+1,…,kj作为根结点的情况,并选择期望搜索代价最小的情况作为子问题的最优解。于是我们可以得到下面的递归式。
e[i,j]e[i, j]e[i,j]给出了最优二叉搜索树子问题的期望搜索代价。我们还需要记录最优二叉搜索树子问题的根结点,用root[i,j]root[i, j]root[i,j]来记录。
根据上文给出的递归式,我们可以采用自下而上的动态规划方法来求解最优二叉搜索树问题。下面给出了伪代码。
我们可以看到,该问题的求解过程与15.2节矩阵链乘法问题是很相似的。该算法的时间复杂度也与矩阵链乘法问题一样,都为Θ(n3)Θ(n^3)Θ(n3)。
###习题
15.5-1 设计伪代码CONSTRUCT-OPTIMAL-BST(root)\text{CONSTRUCT-OPTIMAL-BST(root)}CONSTRUCT-OPTIMAL-BST(root),输入为表rootrootroot,输出是最优二叉搜索树的结构。例如,对图15-10中的rootrootroot表,应输出
k2k_2k2为根
k1k_1k1为k2k_2k2的左孩子
d0d_0d0为k1k_1k1的左孩子
d1d_1d1为k1k_1k1的右孩子
k5k_5k5为k2k_2k2的右孩子
k4k_4k4为k5k_5k5的左孩子
k3k_3k3为k4k_4k4的左孩子
d2d_2d2为k3k_3k3的左孩子
d3d_3d3为k3k_3k3的右孩子
d4d_4d4为k4k_4k4的右孩子
d5d_5d5为k5k_5k5的右孩子
与图15-9(b)中的最优二叉搜索树对应。
解
15.5-2 若777个关键字的概率如下所示,求其最优二叉搜索树的结构和代价。
iii | 000 | 111 | 222 | 333 | 444 | 555 | 666 | 777 |
---|---|---|---|---|---|---|---|---|
pip_ipi | 0.040.040.04 | 0.060.060.06 | 0.080.080.08 | 0.020.020.02 | 0.100.100.10 | 0.120.120.12 | 0.140.140.14 | |
qiq_iqi | 0.060.060.06 | 0.060.060.06 | 0.060.060.06 | 0.060.060.06 | 0.050.050.05 | 0.050.050.05 | 0.050.050.05 | 0.050.050.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≤n1 ≤ i < j ≤ n1≤i<j≤n,存在最优二叉搜索树,其根满足root[i,j−1]≤root[i,j]≤root[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,使得运行时间减少为Θ(n2)Θ(n^2)Θ(n2)。
解
在代码一中,在处理每个子问题[i,j][i, j][i,j]时,从iii到jjj进行迭代(代码一第9行)。根据题目所给的结论,在处理每个子问题[i,j][i, j][i,j]时,可以改为从root[i,j−1]root[i, j-1]root[i,j−1]到root[i+1,j]root[i+1, j]root[i+1,j]进行迭代。下面给出了伪代码。
现在分析OPTIMAL-BST-KNUTH\text{OPTIMAL-BST-KNUTH}OPTIMAL-BST-KNUTH的运行时间。
先考虑子问题规模l>1l > 1l>1的情况。规模为lll的子问题一共有n−l+1n-l+1n−l+1个(参见代码三第6行)。每个规模为lll的子问题包含(root[i+1,j]−root[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−1j = i+l-1j=i+l−1),故求解所有规模为lll的子问题所需的迭代次数为
故求解所有规模为l(l>1)l (l > 1)l(l>1)的问题的迭代次数为Θ(n)Θ(n)Θ(n)。而每次迭代时间为Θ(1)Θ(1)Θ(1),故求解所有规模为lll的子问题的时间为Θ(n)Θ(n)Θ(n)。
而规模为l=1l = 1l=1的子问题一共有nnn个,每个子问题需要Θ(1)Θ(1)Θ(1)。故求解所有规模为l=1l = 1l=1的子问题需要Θ(n)Θ(n)Θ(n)时间。
子问题的规模lll的取值有nnn种情况,故OPTIMAL-BST-KNUTH\text{OPTIMAL-BST-KNUTH}OPTIMAL-BST-KNUTH的运行时间为Θ(n2)Θ(n^2)Θ(n2)。
本节相关的code链接。
https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter15/Section_15.5
算法导论 — 15.5 最优二叉搜索树相关推荐
- 《算法导论》15.5 最优二叉搜索树(含C++代码)
一.问题背景和描述 给定一个n个不同关键字的已排序的序列K=<k1,k2, - kn>(因此k1<k2<-<kn),我们希望用这 些关键字构造一棵二叉搜索树.对每个关键字 ...
- 算法设计与分析--最优二叉搜索树(Python)
最优二叉搜索树: 给定一个n个不同关键字的已排序的序列K=<k1,k2,-,kn>(因此k1<k2<-<kn)我们希望用这些关键字构造一棵二叉树.对每个关键字ki,都有一 ...
- 第十五章 动态规划(最优二叉搜索树)
第15章动态规划(最优二叉搜索树) 15.5 最优二叉搜索树 15.5 练习 15.5-1 15.5-2 15.5-3 15.5-4 说在前面的话: 为什么单独拿出来发? 1.由于排版篇幅问题,放一起 ...
- 最优二叉搜索树(Optimal BST)-算法导论
问题描述: 维基百科定义: https://en.wikipedia.org/wiki/Optimal_binary_search_tree In the static optimality prob ...
- 动态规划最优二叉搜索树C语言,【算法导论】动态规划之“最优二叉搜索树”...
详解动态规划之"最优二叉搜索树" 之前两篇分别讲了动态规划的"钢管切割"和"矩阵链乘法",感觉到了这一篇,也可以算是收官之作了.其实根据前两 ...
- 算法:最优二叉搜索树
算法设计第五次作业part2 1.纸面题:对最优二叉树和矩阵连乘两种算法验证四边形法则,如果符合四边形法则则举几个正例,如果不符合则举几个反例 四边形法则 i<i'j<j'w(i,j)+w ...
- 【算法设计与分析】动态规划:最优二叉搜索树
最优二叉搜索树问题的问题提出是,设S={x1, x2, -, xn}是一个由n个关键字组成的线性有序集,(a0, b1, a1, -, bn, an) 为集合S的存取概率分布,表示有序集S的二叉搜索树 ...
- 动态规划最优二叉搜索树C语言,算法 – 动态规划:最优二叉搜索树
好吧,我希望有人可以向我解释一下.我正在攻读决赛,我无法解决问题. 问题是动态编程;构造最优二叉搜索树(OBST).我理解一般的动态编程和特别是这个问题的概念,但我不明白这个问题的递归形式. 我得到的 ...
- 算法实验 最优二叉搜索树
最优二叉搜索树 最优二叉搜索树 问题描述 问题分析 代码 问题描述 二叉搜索树我们都知道,左子树结点的值都小于根结点,右子树结点的值都大于根节点.如果某个结点没有左子树或右子树,那么在对应的位置上加一 ...
最新文章
- 【转】Visual Studio团队资源管理器 Git 源码管理工具简单入门
- php请编写一个函数来将一个_为什么开发人员讨厌PHP
- r语言plot函数x轴y轴名字_Matplotlib入门-1-plt.plot( )绘制折线图
- seaborn.distplot()
- 音视频技术开发周刊 | 134
- 代码 抠图_3 行 Python 代码 5 秒抠图的 AI 神器,根本无需 PS,附教程
- php 获取当前action,ThinkPHP3.2.2获取当前Action名称
- css3-10 css3中的边框样式有哪几种
- c语言数组元素前移后移,如何将一个数组的元素循环左移?
- 最新解决vscode中文乱码问题
- MySQL5.7.32 64位解压缩版 windows操作系统安装教程图解
- matlab中如何去掉多行注释_MATLAB中多行注释的三种方法
- win7蓝牙怎么连接_win7添加打印机提示windows无法连接怎么办?正确解决方法分享...
- 花花公子 243线SLOT
- 思维 POJ - 2361 Tic Tac Toe
- python爬虫-selenium爬取链家网房源信息
- 笑cry!在镜子面前,沉稳暖心的金毛同学也会瞬间化身逗比!
- python 少儿不宜图片识别(基于肤色数量)
- 利用秀米的SVG布局的穿透功能实现横屏长图互动原理+教程
- 克里斯·麦克切斯尼《高效能人士的执行4原则》读书笔记