最优二叉查找树(动态规划)
来源:互联网 发布:软件开发最新技术 编辑:程序博客网 时间:2024/05/16 14:18
一、什么是最优二叉查找树
最优二叉查找树:
给定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
建立一棵二叉查找树,如果是的上式最小,那么这棵二叉查找树就是最优二叉查找树。
而且有下式成立:
二、最优二叉查找树的最优子结构
最优子结构:
如果一棵最优二叉查找树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]中,就避免这些复杂的计算。
- 最优二叉查找树(动态规划)
- 最优二叉查找树(动态规划)
- 动态规划--最优二叉查找树
- 动态规划之-最优二叉查找树
- 动态规划 -- 最优二叉查找树
- 动态规划4-最优二叉查找树
- 动态规划4-最优二叉查找树
- 动态规划-最优二叉查找树
- 动态规划--4.最优二叉查找树
- 【算法学习】最优二叉查找树(动态规划)
- 数据结构之(动态规划)之最优二叉查找树
- 【算法学习】最优二叉查找树(动态规划)
- 【算法学习】最优二叉查找树(动态规划)
- 【算法学习】最优二叉查找树(动态规划)
- 【算法学习】最优二叉查找树(动态规划)
- 动态规划——最优二叉查找树
- 动态规划——最优二叉查找树
- 算法导论 ch15 动态规划 最优二叉查找树
- acm_最大折线分割平面数目
- 竞态条件与sigsuspend函数
- best coder (百度之星) IP聚合
- 二维数组的动态申请--c语言
- 如何在cPanel中创建新MySQL数据库、用户以及设置权限
- 最优二叉查找树(动态规划)
- spring4+springmvc4+hibernate4集成框架流程:
- 链表——删除链表中倒数第n个结点(时间复杂度为O (n))
- C++第五次上机作业
- Spark中文手册4:Spark之基本概念(2)
- 学习心得1
- 在低版本android系统上实现Material design应用
- php global用法
- android webview长按识别图片 ,利用zxing识别图片是否是二维码