###笔记

二叉搜索树满足如下性质:假设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]=&lt;k1,k2,…,kn&gt;K[1..n]= &lt;k_1, k_2, …, k_n&gt;K[1..n]=<k1​,k2​,…,kn​>(因此k1&lt;k2&lt;…&lt;knk_1 &lt; k_2 &lt; … &lt; k_nk1​<k2​<…<kn​),我们希望用这些关键字构造一个二叉搜索树。对每个关键字kik_iki​,都有一个概率pip_ipi​表示其搜索概率。搜索过程中有可能遇到不在K[1..n]K[1..n]K[1..n]中的元素,因此我们还有n+1n+1n+1个元素的“伪关键字”序列D[0..n]=&lt;d0,d1,d2,…,dn&gt;D[0..n] = &lt;d_0, d_1, d_2, …, d_n&gt;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]=&lt;ki,…,kj&gt;K[i..j] = &lt;k_i, …, k_j&gt;K[i..j]=<ki​,…,kj​>和伪关键字子序列D[i−1..j]=&lt;di−1,…,dj&gt;D[i-1..j] = &lt;d_{i-1}, …, d_j&gt;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]=&lt;ki,…,kj&gt;K[i..j] = &lt;k_i, …, k_j&gt;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&lt;j≤n1 ≤ i &lt; 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&gt;1l &gt; 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&gt;1)l (l &gt; 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 最优二叉搜索树相关推荐

  1. 《算法导论》15.5 最优二叉搜索树(含C++代码)

    一.问题背景和描述 给定一个n个不同关键字的已排序的序列K=<k1,k2, - kn>(因此k1<k2<-<kn),我们希望用这 些关键字构造一棵二叉搜索树.对每个关键字 ...

  2. 算法设计与分析--最优二叉搜索树(Python)

    最优二叉搜索树: 给定一个n个不同关键字的已排序的序列K=<k1,k2,-,kn>(因此k1<k2<-<kn)我们希望用这些关键字构造一棵二叉树.对每个关键字ki,都有一 ...

  3. 第十五章 动态规划(最优二叉搜索树)

    第15章动态规划(最优二叉搜索树) 15.5 最优二叉搜索树 15.5 练习 15.5-1 15.5-2 15.5-3 15.5-4 说在前面的话: 为什么单独拿出来发? 1.由于排版篇幅问题,放一起 ...

  4. 最优二叉搜索树(Optimal BST)-算法导论

    问题描述: 维基百科定义: https://en.wikipedia.org/wiki/Optimal_binary_search_tree In the static optimality prob ...

  5. 动态规划最优二叉搜索树C语言,【算法导论】动态规划之“最优二叉搜索树”...

    详解动态规划之"最优二叉搜索树" 之前两篇分别讲了动态规划的"钢管切割"和"矩阵链乘法",感觉到了这一篇,也可以算是收官之作了.其实根据前两 ...

  6. 算法:最优二叉搜索树

    算法设计第五次作业part2 1.纸面题:对最优二叉树和矩阵连乘两种算法验证四边形法则,如果符合四边形法则则举几个正例,如果不符合则举几个反例 四边形法则 i<i'j<j'w(i,j)+w ...

  7. 【算法设计与分析】动态规划:最优二叉搜索树

    最优二叉搜索树问题的问题提出是,设S={x1, x2, -, xn}是一个由n个关键字组成的线性有序集,(a0, b1, a1, -, bn, an) 为集合S的存取概率分布,表示有序集S的二叉搜索树 ...

  8. 动态规划最优二叉搜索树C语言,算法 – 动态规划:最优二叉搜索树

    好吧,我希望有人可以向我解释一下.我正在攻读决赛,我无法解决问题. 问题是动态编程;构造最优二叉搜索树(OBST).我理解一般的动态编程和特别是这个问题的概念,但我不明白这个问题的递归形式. 我得到的 ...

  9. 算法实验 最优二叉搜索树

    最优二叉搜索树 最优二叉搜索树 问题描述 问题分析 代码 问题描述 二叉搜索树我们都知道,左子树结点的值都小于根结点,右子树结点的值都大于根节点.如果某个结点没有左子树或右子树,那么在对应的位置上加一 ...

最新文章

  1. 【转】Visual Studio团队资源管理器 Git 源码管理工具简单入门
  2. php请编写一个函数来将一个_为什么开发人员讨厌PHP
  3. r语言plot函数x轴y轴名字_Matplotlib入门-1-plt.plot( )绘制折线图
  4. seaborn.distplot()
  5. 音视频技术开发周刊 | 134
  6. 代码 抠图_3 行 Python 代码 5 秒抠图的 AI 神器,根本无需 PS,附教程
  7. php 获取当前action,ThinkPHP3.2.2获取当前Action名称
  8. css3-10 css3中的边框样式有哪几种
  9. c语言数组元素前移后移,如何将一个数组的元素循环左移?
  10. 最新解决vscode中文乱码问题
  11. MySQL5.7.32 64位解压缩版 windows操作系统安装教程图解
  12. matlab中如何去掉多行注释_MATLAB中多行注释的三种方法
  13. win7蓝牙怎么连接_win7添加打印机提示windows无法连接怎么办?正确解决方法分享...
  14. 花花公子 243线SLOT
  15. 思维 POJ - 2361 Tic Tac Toe
  16. python爬虫-selenium爬取链家网房源信息
  17. 笑cry!在镜子面前,沉稳暖心的金毛同学也会瞬间化身逗比!
  18. python 少儿不宜图片识别(基于肤色数量)
  19. 利用秀米的SVG布局的穿透功能实现横屏长图互动原理+教程
  20. 克里斯·麦克切斯尼《高效能人士的执行4原则》读书笔记

热门文章

  1. Java中List转换为Array与Array转换为List
  2. SSH服务器拒绝了密码。请再试一次。解决方法汇总。浪费我一下午的时间,只好承认自己好菜。
  3. Git使用 从入门到入土 收藏吃灰系列(四) Git工作原理
  4. 三星win平板刷linux,三星新专利:平板秒变Win10笔记本
  5. 京东零售数仓:从离线、实时到流批一体的演进之路
  6. 一文解析JVM的内存结构,身为程序员还不弄懂JVM怎么行
  7. 草料二维码如何配置模板
  8. 2021.07.22
  9. 如何用C语言实现心形线
  10. 敷面膜的时候肌肤出现刺痛感,我过敏了吗?