这个题目比较难想象,因为数组下标(表格的标号)跟问题之间的关系不像之前的问题那么的明确,一个推出一个。要注意的是,e[i,j]保存在表e[1...n+1,0...n]中,第一维下标到n+1是因为有一个包含虚拟键dn的子树,我们需要计算和保存e[n+1,n]。第二维下标从0开始,是为了保存e[1,0],需要画出一个树的格式,帮助理解这些概念。所有e[i,i-1]都是包含虚拟键的
//动态规划,最优二叉查找树
//假设正在设计一个程序,用于将文章从英文翻译为法语,对于出现在文章内的每一个英文单词,
// 需要查看与它等价的法语。执行这些搜索操作的一种方式是建立一棵二叉查找树,,,
// 因为要为文章中的每个单词搜索这棵树,古故希望搜索所花费的总时间尽可能的小,
// 因此我们希望文章中出现频繁的单词呗放置在距离根部较近的地方,而且文章中可能会有些单词没有法语的翻译,
// 这些单词可能根本就不会出现在二叉查找树中。
//
// n个关键字,对于每个关键字ki,一次搜索ki的概率为pi,,,,
// 树中还存在n+1个虚拟的关键字di,一尺搜索di的概率为qi,假设n=5个的关键字的集合上的二叉查找树的概率如下
// ,现在要求根据上表构造一棵二叉查找树,使得二叉查找树的期望搜索代价最低:
//定义e[i,j]为搜索一棵包含关键字ki,,,kj的最优二叉查找树的期望代价,最终要计算e[1,n]。。。。
//
// 当j=i-1时,只有虚拟键di-1,期望的搜索代价是e[i,i-1]=qi-1。
//
// 当j>=i时,需要从ki,...,kj中选择一个根kr,然后用关键字ki,...,kr-1来构造一棵最优二叉查找树作为其左子树,
// 并用关键字kr+1,...,kj来构造一棵最优二叉查找树作为其右子树。。。注意当一棵树成为一个节点的子树时,
// 它的期望搜索代价增加量将为该子树中所有概率的总和。对于一棵有关键字ki,...,kj的子树,定义概率的总和为:
//
// w[i,j]=pl(l=i到j)的总和+ql(l=i-1到j的总和)
//
// 因此,有
//
// e[i,j]=qi-1 j=i-1
//
// e[i,j]=min{e[i,r-1]+e[r+1,j]+w[i,j]} i<=j
//
// 另外定义root[i,j]为kr的下标r。
const int n =5;
double optimalBest(double p[], double q[], int root[][n+1])
{
int i=0,len=0,j=0,r=0;
double t =0.0;
double e[n+2][n+1];
double w[n+2][n+1];
for(i=1;i<=n+1;++i)
{
e[i][i-1]=q[i-1];
w[i][i-1]=q[i-1];
}
for(len=1; len <=n;++len)//内节点的个数
{
for(i=1;i<=n-len+1;++i)//设置起点i
{
j=i+len-1;//设置终点l
e[i][j]=0x7fffffff;
w[i][j]= w[i][j-1]+p[j]+q[j];
for(r=i;r<=j;++r)
{
t=e[i][r-1]+e[r+1][j]+w[i][j];
if(t<e[i][j])
{
e[i][j]=t;
root[i][j]=r;
cout<<"e"<<t<<endl;
cout<<r<<endl;
}
}
}
}
return e[1][n];
}
void printBest(int root[][n+1],int i,int j)
{
int k=0;
if(i<=j)
{
cout<<root[i][j];
k=root[i][j];
printBest(root,i,k-1);
printBest(root,k+1,j);
}
}
int main()
{
int root[n+1][n+1];
double p[n+1]={0,0.15,0.10,0.05,0.10,0.20};
double q[n+1]={0.05,0.10,0.05,0.05,0.05,0.10};
double k=optimalBest(p,q,root);
cout<<"最小期望搜索代价:"<<k<<endl;
printBest(root,1,n);
}