一、什么是最优二叉查找树

最优二叉查找树:

给定n个互异的关键字组成的序列K=<k1,k2,...,kn>,且关键字有序(k1<k2<...<kn),我们想从这些关键字中构造一棵二叉查找树。对每个关键字ki,一次搜索搜索到的概率为pi。可能有一些搜索的值不在K内,因此还有n+1个“虚拟键”d0,d1,...,dn,他们代表不在K内的值。具体:d0代表所有小于k1的值,dn代表所有大于kn的值。而对于i = 1,2,...,n-1,虚拟键di代表所有位于ki和ki+1之间的值。对于每个虚拟键,一次搜索对应于di的概率为qi。要使得查找一个节点的期望代价(代价可以定义为:比如从根节点到目标节点的路径上节点数目)最小,就需要建立一棵最优二叉查找树。

图一显示了给定上面的概率分布pi、qi,生成的两个二叉查找树的例子。图二就是在这种情况下一棵最优二叉查找树。

概率分布:

i

0

1

2

3

4

5


pi

0.15

0.10

0.05

0.10

0.20

qi

0.05

0.10

0.05

0.05

0.05

0.10

已知每个关键字以及虚拟键被搜索到的概率,可以计算出一个给定二叉查找树内一次搜索的期望代价。假设一次搜索的实际代价为检查的节点的个数,即所发现的节点的深度加1.计算一次搜索的期望代价等式为:

建立一棵二叉查找树,如果是的上式最小,那么这棵二叉查找树就是最优二叉查找树

而且有下式成立:

二、最优二叉查找树的最优子结构

最优子结构:

如果一棵最优二叉查找树T有一棵包含关键字ki,..,kj的子树T',那么这可子树T'对于关键字Ki,...,kj和虚拟键di-1,...dj的子问题也必定是最优的。可以应用剪贴法证明。

根据最优子结构,寻找最优解:

给定关键字ki,...,kj,假设kr(i<=r<=j)是包含这些键的一棵最优子树的根。其左子树包含关键字ki,...,kr-1和虚拟键di-1,...,dr-1,右子树包含关键字kr+1,...,kj和虚拟键dr,...dj。我们检查所有的候选根kr,就保证可以找到一棵最优二叉查找树。

递归解:

定义e[i,j]为包含关键字ki,...,kj的最优二叉查找树的期望代价,最终要计算的是e[1,n]。

当j = i - 1时,此时子树中只有虚拟键,期望搜索代价为e[i,i - 1] = qi-1.

当j >= i时,需要从ki,...,kj中选择一个根kr,然后分别构造其左子树和右子树。下面需要计算以kr为根的树的期望搜索代价。然后选择导致最小期望搜索代价的kr做根。

现在需要考虑的是,当一棵树成为一个节点的子树时,期望搜索代价怎么变化?子树中每个节点深度都增加1.期望搜索代价增加量为子树中所有概率的总和。

对一棵关键字ki,...,kj的子树,定义其概率总和为:

因此,以kr为根的子树的期望搜索代价为:

因此e[i,j]可以进一步写为:

这样推导出最终的递归公式为:

三、代码实现(C++):

//最优二叉查找树#include <iostream>using namespace std;const int MaxVal = 9999;const int n = 5;
//搜索到根节点和虚拟键的概率
double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2};
double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1};int root[n + 1][n + 1];//记录根节点
double w[n + 2][n + 2];//子树概率总和
double e[n + 2][n + 2];//子树期望代价void optimalBST(double *p,double *q,int n)
{//初始化只包括虚拟键的子树for (int i = 1;i <= n + 1;++i){w[i][i - 1] = q[i - 1];e[i][i - 1] = q[i - 1];}//由下到上,由左到右逐步计算for (int len = 1;len <= n;++len){for (int i = 1;i <= n - len + 1;++i){int j = i + len - 1;e[i][j] = MaxVal;w[i][j] = w[i][j - 1] + p[j] + q[j];//求取最小代价的子树的根for (int k = i;k <= j;++k){double temp = e[i][k - 1] + e[k + 1][j] + w[i][j];if (temp < e[i][j]){e[i][j] = temp;root[i][j] = k;}}}}
}//输出最优二叉查找树所有子树的根
void printRoot()
{cout << "各子树的根:" << endl;for (int i = 1;i <= n;++i){for (int j = 1;j <= n;++j){cout << root[i][j] << " ";}cout << endl;}cout << endl;
}//打印最优二叉查找树的结构
//打印出[i,j]子树,它是根r的左子树和右子树
void printOptimalBST(int i,int j,int r)
{int rootChild = root[i][j];//子树根节点if (rootChild == root[1][n]){//输出整棵树的根cout << "k" << rootChild << "是根" << endl;printOptimalBST(i,rootChild - 1,rootChild);printOptimalBST(rootChild + 1,j,rootChild);return;}if (j < i - 1){return;}else if (j == i - 1)//遇到虚拟键{if (j < r){cout << "d" << j << "是" << "k" << r << "的左孩子" << endl;}elsecout << "d" << j << "是" << "k" << r << "的右孩子" << endl;return;}else//遇到内部结点{if (rootChild < r){cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl;}elsecout << "k" << rootChild << "是" << "k" << r << "的右孩子" << endl;}printOptimalBST(i,rootChild - 1,rootChild);printOptimalBST(rootChild + 1,j,rootChild);
}int main()
{optimalBST(p,q,n);printRoot();cout << "最优二叉树结构:" << endl;printOptimalBST(1,n,-1);
}

我们将表e、w以及root旋转45°,便于查看上述程序的计算过程。上述代码核心在于函数optimalBST,其计算顺序是从下到上、从左到右。首先是依据概率数组pi、qi初始化:给最下面的一行赋值。然后是三个for循环:从下到上计算表中每一行的值,可以充分利用前面计算出来的结果。如果每当计算e[i][j]的时候都从头开始计算w[i][j],那么需要O(j-i)步加法,但是将这些值保存在表w[1...n+1][0...n]中,就避免这些复杂的计算。

输出结果:

【算法学习】最优二叉查找树(动态规划)相关推荐

  1. 最优二叉查找树—动态规划C++

    最优二叉查找树 一.问题描述 二.动态规划算法解题思路 三.解题思路图形化 四.思考:为什么输入概率相同(无序),输出结果不一样 流程图 实例 示例代码 一.问题描述 1.问题描述: 基于统计先验知识 ...

  2. 算法学习 - 01背包问题(动态规划C++)

    动态规划 01背包 问题描述 求解思路 代码实现 放入哪些物品 代码 动态规划 我在上一篇博客里已经讲了一点动态规划了,传送门:算法学习 - 动态规划(DP问题)(C++) 这里说一下,遇到动态规划应 ...

  3. 【算法学习笔记】43.动态规划 逆向思维 SJTU OJ 1012 增长率问题

    1012. 增长率问题 Description 有一个数列,它是由自然数组成的,并且严格单调上升.最小的数不小于S,最大的不超过T.现在知道这个数列有一个性质:后一个数相对于前一个数的增长率总是百分比 ...

  4. 【算法学习笔记】75. 动态规划 棋盘型 期望计算 1390 畅畅的牙签盒(改)

    一开始用了模拟的方法,DFS来搜索,但是因为当n很大的时候有很多的重复计算,因为会踏过重复的点进行重复的操作,而且不能不走这些重复的路径,因为没有存储结果,最后只过了三个点. 考虑到重复的路径,所以想 ...

  5. DP之最优二叉查找树

    前面说过动态规划最典型的就是解决最优化问题的(具有最优子结构的最优化问题),最优二叉查找树就是一个典型的最优化问题. 问题描述: 给定一个n元素的中序序列,它可以有卡特兰数个不同形状的二叉排序树.(卡 ...

  6. 算法导论之动态规划(最长公共子序列和最优二叉查找树)

    动态规划师通过组合子问题的解而解决整个问题,将问题划分成子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解.和分治算法思想一致,不同的是分治算法适合独立的子问题,而对于非独立的子问题,即各 ...

  7. php算法学习,php算法学习之动态规划

    动态规划程序设计是对解最优化问题的一种途径.一种方法,最终问题的最优解可以通过前面子问题的最优解推导出来. 对于动态规划这个算法,自己学习的还不是很透彻,简单的总结自己学习的感受是: 动态规划思想中融 ...

  8. 完全背包问题贪心算法c语言,数据结构与算法学习之路:背包问题的贪心算法和动态规划算法...

    一.背包问题描述: 有N种物品和一个重量为M的背包,第i种物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大. 二.解决方法: 1.贪心算 ...

  9. 数据结构与算法学习⑥(动态规划 题解 背包和打家劫舍问题)

    数据结构与算法学习⑥(动态规划 动态规划 1.初识动态规划 1.1.从贪心说起 1.1.1.贪心的特点 1.1.2.贪心的局限性 1.1.3.贪心失效后怎么办 1.1.4.从最优化问题到递归 1.2. ...

最新文章

  1. java 1000个线程_java,一个程序建立1000个线程,每一个线程加1到一个变量sum。
  2. 基于mvc三层架构和ajax技术实现最简单的文件上传
  3. java webstart 自动升级_windows – 使用java web start实现自动更新桌面应用程序时的AWTPermission异常...
  4. 常用的正则表达式(方便自己看)
  5. apply方法和call方法的详解2
  6. mac下安装brew下载非常慢解决方法
  7. 冲压模板自动标注LISP_干货满满!超实用冲压模具资料,加薪必看!
  8. VC++实现数据包嗅探
  9. libspark.swfassist的体会
  10. python统计词频_Python中文分词及词频统计
  11. html网页显示时中文乱码
  12. websphere多应用域名绑定
  13. 吃透Redis系列(五):RDB和AOF持久化详细介绍
  14. db2建立表空间 linux,DB2实验教程:创建数据库/表空间
  15. 知云文献翻译打不开_有了这几个软件,轻松读懂英文文献
  16. Hi3531 平台开发,Hi3532 平台开发
  17. C++ Singleton的实现方法
  18. 2014年7月份第3周51Aspx源码发布详情
  19. linux网络命令--ping
  20. 全球与中国汽车点火线圈市场深度研究分析报告

热门文章

  1. OpenCV辅助对象(help objects)(2)_Range
  2. numpy.ceil() 和 numpy.floor()--向上取整和向下取整
  3. koa上传文件处理403
  4. nginx:启动、重启、关闭、版本查看、安装目录获取
  5. 单机android游戏排行榜,安卓单机手机游戏推荐_十大必玩单机手机游戏
  6. 超好看的动态流量卡官网源码多功能集成式源码
  7. 志汇叮咚超级外卖小程序源码6.4.3
  8. wordpress主题 阿里百秀XIU v7.7版本 兼容 PHP 8.0
  9. java读取同包文件_Java实现从jar包中读取指定文件的方法
  10. java windows编程,以编程方式控制Windows媒体播放器,最好是从Java