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

来源:互联网 发布:bad sql grammar 编辑:程序博客网 时间:2024/05/23 19:14

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

最优二叉查找树:

给定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++):

[cpp] view plaincopyprint?
  1. //最优二叉查找树  
  2.   
  3. #include <iostream>  
  4.   
  5. using namespace std;  
  6.   
  7. const int MaxVal = 9999;  
  8.   
  9. const int n = 5;  
  10. //搜索到根节点和虚拟键的概率  
  11. double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2};  
  12. double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1};  
  13.   
  14. int root[n + 1][n + 1];//记录根节点  
  15. double w[n + 2][n + 2];//子树概率总和  
  16. double e[n + 2][n + 2];//子树期望代价  
  17.   
  18. void optimalBST(double *p,double *q,int n)  
  19. {  
  20.     //初始化只包括虚拟键的子树  
  21.     for (int i = 1;i <= n + 1;++i)  
  22.     {  
  23.         w[i][i - 1] = q[i - 1];  
  24.         e[i][i - 1] = q[i - 1];  
  25.     }  
  26.   
  27.     //由下到上,由左到右逐步计算  
  28.     for (int len = 1;len <= n;++len)  
  29.     {  
  30.         for (int i = 1;i <= n - len + 1;++i)  
  31.         {  
  32.             int j = i + len - 1;  
  33.             e[i][j] = MaxVal;  
  34.             w[i][j] = w[i][j - 1] + p[j] + q[j];  
  35.             //求取最小代价的子树的根  
  36.             for (int k = i;k <= j;++k)  
  37.             {  
  38.                 double temp = e[i][k - 1] + e[k + 1][j] + w[i][j];  
  39.                 if (temp < e[i][j])  
  40.                 {  
  41.                     e[i][j] = temp;  
  42.                     root[i][j] = k;  
  43.                 }  
  44.             }  
  45.         }  
  46.     }  
  47. }  
  48.   
  49. //输出最优二叉查找树所有子树的根  
  50. void printRoot()  
  51. {  
  52.     cout << "各子树的根:" << endl;  
  53.     for (int i = 1;i <= n;++i)  
  54.     {  
  55.         for (int j = 1;j <= n;++j)  
  56.         {  
  57.             cout << root[i][j] << " ";  
  58.         }  
  59.         cout << endl;  
  60.     }  
  61.     cout << endl;  
  62. }  
  63.   
  64. //打印最优二叉查找树的结构  
  65. //打印出[i,j]子树,它是根r的左子树和右子树  
  66. void printOptimalBST(int i,int j,int r)  
  67. {  
  68.     int rootChild = root[i][j];//子树根节点  
  69.     if (rootChild == root[1][n])  
  70.     {  
  71.         //输出整棵树的根  
  72.         cout << "k" << rootChild << "是根" << endl;  
  73.         printOptimalBST(i,rootChild - 1,rootChild);  
  74.         printOptimalBST(rootChild + 1,j,rootChild);  
  75.         return;  
  76.     }  
  77.   
  78.     if (j < i - 1)  
  79.     {  
  80.         return;  
  81.     }  
  82.     else if (j == i - 1)//遇到虚拟键  
  83.     {  
  84.         if (j < r)  
  85.         {  
  86.             cout << "d" << j << "是" << "k" << r << "的左孩子" << endl;  
  87.         }  
  88.         else  
  89.             cout << "d" << j << "是" << "k" << r << "的右孩子" << endl;  
  90.         return;  
  91.     }  
  92.     else//遇到内部结点  
  93.     {  
  94.         if (rootChild < r)  
  95.         {  
  96.             cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl;  
  97.         }  
  98.         else  
  99.             cout << "k" << rootChild << "是" << "k" << r << "的右孩子" << endl;  
  100.     }  
  101.   
  102.     printOptimalBST(i,rootChild - 1,rootChild);  
  103.     printOptimalBST(rootChild + 1,j,rootChild);  
  104. }  
  105.   
  106. int main()  
  107. {  
  108.     optimalBST(p,q,n);  
  109.     printRoot();  
  110.     cout << "最优二叉树结构:" << endl;  
  111.     printOptimalBST(1,n,-1);  
  112. }  


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



输出结果:

转载自:http://blog.csdn.net/xiajun07061225/article/details/8088784
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 有人造谣我我该怎么办 宝宝晚上不睡觉哭闹怎么办 婴儿晚上不睡觉哭闹怎么办 2月宝宝排便困难怎么办 3岁宝宝老是哭闹怎么办 2岁了囟门闭合晚怎么办 宝宝卤门闭合慢怎么办 手经常碰水脱皮怎么办 迅雷文件已移除怎么办 手机不读sd卡怎么办 g买卖卖错账号怎么办 森林被野人拖走怎么办 我的世界没有羊怎么办 黑魂3杀死铁匠后怎么办 幻境7下8走错了怎么办 换了手机号微信怎么办 微信游戏没了怎么办 找sf网站被劫持怎么办 护发精油抹多了怎么办 用了护发素洗头怎么办 电脑c盘空间不足怎么办 把水蛭吃肚子里怎么办 不小心喝到蚂蟥怎么办 水蛭喝进肚子里怎么办 蚂蝗钻入皮肤里怎么办 孩子屁眼红疼怎么办啊 宝宝屁眼红疼怎么办4岁 屁股眼上火很疼怎么办 屁股上火了很痛怎么办 脚被虫子咬肿了怎么办 人在低谷的时候怎么办 支付宝忘了密码怎么办 忘了支付宝账号怎么办 支付宝账号丢了怎么办 生完孩子奶水不足怎么办 生完宝宝没奶水怎么办 生完三天没奶水怎么办 生完孩子奶头小怎么办 生完孩子没有奶水怎么办 宝宝刚出生没奶怎么办 婴儿含着乳头睡怎么办