OBST(Optimal Binary Tree最优二叉搜索树)
二叉搜索树
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
一、什么是最优二叉查找树
最优二叉查找树:
给定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]可以进一步写为:

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

#include <iostream>
#include <cstdio>
#define INF 0xFFFFF
using namespace std;
double p[], q[];
int root[][];//记录最优子树的根节点位置
double w[][];//w[i][j]:最优子树概率总和
double e[][];//e[i][j]: (最优)子树期望代价 void optimalBST(int n)
{
for(int i = ; i<=n+; i++)
{
w[i][i-] = q[i-];
e[i][i-] = q[i-];
} for(int len = ; len<=n; len++)
{
for(int i = ; i<=n-len+; i++)
{
int j = i+len-;
e[i][j] = INF;
w[i][j] = w[i][j-] + p[j] + q[j];
for(int r = i; r<=j; r++)
{
double t = e[i][r-] + e[r+][j] + w[i][j];
if(t<e[i][j])
{
e[i][j]=t;
root[i][j] = r;
}
}
}
}
} int main()
{
int n;
while(scanf("%d", &n)!=EOF)
{
getchar();
for(int i = ; i<=n; i++)
scanf("%lf", &p[i]);
getchar();
for(int i =; i<=n; i++)
scanf("%lf", &q[i]);
getchar();
optimalBST(n);
printf("%.3lf\n", e[][n]);
}
}
参考代码:
//最优二叉查找树 #include <iostream> using namespace std; const int MaxVal = ; const int n = ;
//搜索到根节点和虚拟键的概率
double p[n + ] = {-,0.15,0.1,0.05,0.1,0.2};
double q[n + ] = {0.05,0.1,0.05,0.05,0.05,0.1}; int root[n + ][n + ];//记录根节点
double w[n + ][n + ];//子树概率总和
double e[n + ][n + ];//子树期望代价 void optimalBST(double *p,double *q,int n)
{
//初始化只包括虚拟键的子树
for (int i = ;i <= n + ;++i)
{
w[i][i - ] = q[i - ];
e[i][i - ] = q[i - ];
} //由下到上,由左到右逐步计算
for (int len = ;len <= n;++len)
{
for (int i = ;i <= n - len + ;++i)
{
int j = i + len - ;
e[i][j] = MaxVal;
w[i][j] = w[i][j - ] + p[j] + q[j];
//求取最小代价的子树的根
for (int k = i;k <= j;++k)
{
double temp = e[i][k - ] + e[k + ][j] + w[i][j];
if (temp < e[i][j])
{
e[i][j] = temp;
root[i][j] = k;
}
}
}
}
} //输出最优二叉查找树所有子树的根
void printRoot()
{
cout << "各子树的根:" << endl;
for (int i = ;i <= n;++i)
{
for (int j = ;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[][n])
{
//输出整棵树的根
cout << "k" << rootChild << "是根" << endl;
printOptimalBST(i,rootChild - ,rootChild);
printOptimalBST(rootChild + ,j,rootChild);
return;
} if (j < i - )
{
return;
}
else if (j == i - )//遇到虚拟键
{
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 - ,rootChild);
printOptimalBST(rootChild + ,j,rootChild);
} int main()
{
optimalBST(p,q,n);
printRoot();
cout << "最优二叉树结构:" << endl;
printOptimalBST(,n,-);
}
我们将表e、w以及root旋转45°,便于查看上述程序的计算过程。上述代码核心在于函数optimalBST,其计算顺序是从下到上、从左到右。首先是依据概率数组pi、qi初始化:给最下面的一行赋值。然后是三个for循环:从下到上计算表中每一行的值,可以充分利用前面计算出来的结果。如果每当计算e[i][j]的时候都从头开始计算w[i][j],那么需要O(j-i)步加法,但是将这些值保存在表w[1...n+1][0...n]中,就避免这些复杂的计算。

输出结果:

OBST(Optimal Binary Tree最优二叉搜索树)的更多相关文章
- [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法
二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...
- OBST(最优二叉搜索树)
简述一下问题:假设有一颗词典二叉树,我们从中查找需要的单词,使用红黑树或平衡树这样的数据结构总是可以在O(lgN)时间内进行查找,但单词的出现频率是不同的,我们给每个单词加上一个搜索概率,然后通过这些 ...
- Ex 6_20 最优二叉搜索树..._第六次作业
假设关键字的总数为n,用c[i,j]表示第i个关键字到第j个关键字的最优二叉查找树的代价,我们的目标是求c[0,n-1].要求c[i,j],首先要从第i个关键字到第j个关键字中选一个出来作为根结点,选 ...
- LeetCode 501. Find Mode in Binary Search Tree (找到二叉搜索树的众数)
Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred ...
- [LeetCode] 98. Validate Binary Search Tree(是否是二叉搜索树) ☆☆☆
描述 解析 二叉搜索树,其实就是节点n的左孩子所在的树,每个节点都小于节点n. 节点n的右孩子所在的树,每个节点都大于节点n. 定义子树的最大最小值 比如:左孩子要小于父节点:左孩子n的右孩子要大于n ...
- LeetCode第[98]题(Java):Validate Binary Search Tree(验证二叉搜索树)
题目:验证二叉搜索树 难度:Medium 题目内容: Given a binary tree, determine if it is a valid binary search tree (BST). ...
- LeetCode OJ:Binary Search Tree Iterator(二叉搜索树迭代器)
Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the ro ...
- [leetcode]109. Convert Sorted List to Binary Search Tree链表构建二叉搜索树
二叉树的各种遍历方式都是可以建立二叉树的,例如中序遍历,就是在第一步建立左子树,中间第二步建立新的节点,第三步构建右子树 此题利用二叉搜索树的中序遍历是递增序列的特点,而链表正好就是递增序列,从左子树 ...
- LeetCode 538. Convert BST to Greater Tree (把二叉搜索树转换成较大树)
Given a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original B ...
随机推荐
- 【转载】Lucene.Net入门教程及示例
本人看到这篇非常不错的Lucene.Net入门基础教程,就转载分享一下给大家来学习,希望大家在工作实践中可以用到. 一.简单的例子 //索引Private void Index(){ Index ...
- 判断JS对象是否拥有某属性
两种方式,但稍有区别 1.in 运算符
- Android的px、dp和sp
Android的px.dp和sppx: 即像素,1px代表屏幕上一个物理的像素点:偶尔用到px的情况,是需要画1像素表格线或阴影线的时候. dp: 这个是最常用但也最难理解的尺寸单位.它与“像素密度” ...
- echarts 折线图动态x轴及数据
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...
- 炉石传说 C# 开发笔记 (续)
炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比 ...
- Windows下查看JDK是否安装以及安装路径
查看JDK是否已经安装,可以在cmd窗口里输入java -version,如果没有提示出错,就表示已经安装. 查看JDK的安装路径,可以输入java -verbose,会返回很多信息,其中就包含了JD ...
- TSwitch 中文简繁显示支持(XE6 Android)
说明: XE6 的 TSwitch 做了很多改进,包含多语显示处理,但 XE6 似乎只认定一种中文语系「zh」,它无法处理「zh_TW」.「zh_CN」.「zh_HK」.「zh_SG」等语系,不过可以 ...
- PHP组件化开发
设计思想中有两种极端:大而全.小而美. 一般我们常用的库是小而美,用的框架是大而全.从Symfony实现Component式开发开始,框架的组件化逐渐成为趋势.我们可以任意的组合各种Compoent来 ...
- Eclipse的SVN插件下载
Links for 1.8.x Release:Eclipse update site URL: http://subclipse.tigris.org/update_1.8.xsvn插件包下载: h ...
- 第 19 章 CSS 其他样式
学习要点: 1.颜色和透明度 2.盒子阴影和轮廓 3.光标样式 主讲教师:李炎恢 本章主要探讨 HTML5 中 CSS 其他剩下几个常用的样式,包括颜色.透明度.盒子的阴影轮廓以及光标的样式. 一.颜 ...