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

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

最优二叉查找树:

给定n个互异的关键字组成的序列K=,且关键字有序(k1

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

ef8b583804b0093f8532aa9373135ecd.png

概率分布:

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.计算一次搜索的期望代价等式为:

a5159a14fecc9ec1b09fcb1d2bc0b734.png

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

而且有下式成立:

b99b693e13be4bdacececd205480fa03.png

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

最优子结构:

如果一棵最优二叉查找树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的子树,定义其概率总和为:

e69f84cc61f6fbe460a5c9323324facd.png

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

0db90655a778101d60d418916cb98ed5.png

ffaa24a6ccfd9e85bab07e1ce4021872.png

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

76a75d31c5a00708187e027cddb6eee7.png

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

009e35bef1d6f4d6470a4f83a013e197.png

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

//最优二叉查找树

#include

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;

}

else

cout << "d" << j << "是" << "k" << r << "的右孩子" << endl;

return;

}

else//遇到内部结点

{

if (rootChild < r)

{

cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl;

}

else

cout << "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]中,就避免这些复杂的计算。

2c3335e4c6b0527caf89bda05f093281.png

输出结果:

a2ad36343abfc3e9490423f76983545f.png

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值