您的位置:首页 > 其它

算法导论:第15章 动态规划_5最优二叉搜索树

2016-03-18 22:35 429 查看
/*
最优二叉搜索树:给定一个n个不同关键字的已经排序的序列K=<k1,k2,...,kn>(因此k1<k2<...<kn),希望用这些关键字构造一颗二叉搜索树。
对每个关键字ki,都有一个概率pi表示其搜索频率。有些要搜索的值可能不在K中,因此我们还有n+1个“伪关键字”do,d1,d2,...,dn表示不
在K中的值。d0表示所有小于k1的值,dn表示所有大于kn的值,对i=1,2,...,n-1,伪关键字di表示所有在ki和ki+1之间的值。对于每个伪关
键字di,也都有一个概率pi表示对应的搜索频率。下图显示了n=5个关键字的集合构造的两颗二叉搜索树。每个关键字ki是一个内部结点,
而每个关键字di是一个叶结点。每次搜索要么成功(找到某个关键字ki),要么失败(找到某个伪关键字di),因此有如下公式:
i属于[1,n] pi的和 + i属于[0,n] qi的和 = 1
关键字ki的两个伪关键字为di-1,di
k2
k1						k4
d0		d1			k3				k5
d2		d3		d4		d5

动态规划分析:
S1:最优二叉搜索树的结构
二叉搜索树的任意子树,必须包含连续关键字ki,...,kj, 1<=i<=j<=n,而其叶节点必然是伪关键字di-1,...,dj。
最优子结构:如果一颗最优二叉搜索树T有一颗包含关键字ki,...,kj的子树T',那么T'必然是包含关键字ki,...,kj和伪关键字di-1,...,dj的
子问题的最优解。
剪切-粘贴法证明:如果存在子树T'',使得T''的期望搜索代价比T'的期望搜索代价要小,那么将T'从T中删除,将T''粘贴到该位置,就得到一颗
期望搜索代价比T的期望搜索代价更小的树,这与T是最优二叉搜索树矛盾,所以T'是子问题的最优解,即最优二叉搜索树有
最优子结构
最优子结构---->可以用子问题的最优解构造原问题的最优解,给定关键字序列ki,...,kj,设其中某个关键字为kr(i<=r<=j),是这些关键字的最
优子树的根节点。那么kr的左子树就包含关键字ki,...,kr-1(和伪关键字di-1,...,dr-1),而右子树包含关键字kr+1,...,kj
(和伪关键字dr,...,dj)。只要我们检查所有可能的根节点kr(i<=r<=j),并对每种情况分别求解包含ki,...,kr-1及kr+1,...,
kj的最优二叉搜索树,即可保证找到原问题的最优解
易错点:注意空子树。假设对于包含关键字ki,...,kj的子问题,我们选定ki为根节点,其实有一个伪关键字di-1,若kj为根节点,那么其右子树
包含伪关键字dj

S2:一个递归算法
子问题域:求解包含关键字ki,...,kj的最优二叉搜索树,其中i>=1,j<=n且j>=i-1(当j=i-1时,子树不包含实际关键字,只包含伪关键字di-1)。
定义e[i,j]为在包含关键字ki,...,kj的最优二叉搜索树中进行搜索的期望代价。
计算结果:e[1,n]
j=i-1的情况最为简单,子树只包含伪关键字di-1,期望搜索代价为
e[i,i-1]=qi-1                                                        公式(1)
当j>=i时,我们需要从ki,...,kj中选择一个根节点kr,然后构造一颗包含关键字ki,...,kr-1的最优二叉搜索树作为其左子树,以及一颗包含关键
字kr+1,...,kj的二叉搜索树作为其右子树。
关键:当一颗子树成为一个节点的子树时,期望搜索代价有何变化?由于每个节点的深度都增加了1,这颗子树的期望搜索代价的增加值为所有概率
之和。对于包含关键字ki,...,kj的子树,所有概率之和为:
w(i,j)=l从i到j累加pl + l从i-1到j累加ql                               公式(2)
因此,若kr为包含关键字ki,...,kj的最优二叉搜索树的根节点,有如下公式:
e[i,j]=pr + ( w[i,r-1] + e[i,r-1] ) + ( w[r+1,j] + e[r+1,j] )        公式(3)
w(i,j) = pr + w(i,r-1) + w(r+1,j)                                    公式(4)
由上述两个公式得到e[i,j] = w(i,j) + e[i,r-1] + e[r+1,j]              公式(5)
递归公式
e[i,j]={qi-1                                          ,若j=i-1       公式(6)
{min i<=r<=j { e[i,r-1] + e[r+1,j] + w(i,j) }  ,若i<=j
用root[i,j]保存根节点kr的下标r

S3:计算最优二叉搜索树的期望搜索代价
用表保存结果
关键:
e[1...n+1, 0...n]来保存e[i,j]值。 第一维下标上街为n+1而不是n,因为只含有伪关键字dn的子树,需要计算e[n+1,n],看n+1在第一维度
第二维下标下界为0,是因为对于只包含伪关键字d0的子树,需要计算e[1,0],看0在第二维度
还需要一个表提高计算效率,为了避免每次计算e[i,j]时都重新计算w(i,j),我们将这些值保存在表w[1...n+1, 0...n]中。
w[i,i-1]=qi-1           (1<=i<=n+1)                                           公式(7)
w[i,j]=w[i,j-1]+pj+qj   (j>=i)                                                公式(8)
公式(8)来源于对公式(4)中带入r=j得到,这个不容易想到

输入关键字个数n,接下来有两行,第一行是n个数字,表示p1~pn,第二行是n+1个数字,表示q0~qn
输出最优二叉搜索树的期望搜索代价

输入:
5
0.15 0.1 0.05 0.1 0.2
0.05 0.1 0.05 0.05 0.05 0.1
输出:
2.75

关键:
1 对于含有长度累加递增的递归公式可以转化为递推,也就是要熟悉下面这种计算方式
即计算的是这种:
第一趟:e[1,1],e[2,2],...,........,e[n,n]
第二趟:e[1,2],e[2,3],...,e[n-1,n]
...
第n趟 :e[1,n]
观察可发现,每趟使得两个下标之间的差值从0~n-1
因此分析差值的范围:0~n-1
对应i的范围由j来产生的
因为我们知道 j=i+l-1 <= n
因此得到 i <= n - (l-1)

2 所有搜索概率为浮点数,因此
double p[N+1];
double optimalBinarySearchTree(int i, int j, int n)
返回值都是浮点数

3  公式
计算e[1]

e[i][j]={qi-1  , j=i-1
{min i<=r<=j { e[i][r-1] + w[i][j] + e[r+1][j] }
w[i][i-1]=q[i-1]
w[i][j]=w[i][r-1] + pr + w[r+1][j]
w[i][j]=w[i][j-1] + pj + qj , j >= i

4setiosflags(ios::fixed)固定浮点显示,
*/

#include <iostream>
#include <string.h>
#include <iomanip> //io输出,manip表示manipulator操纵器

using namespace std;

const int N = 100;
double e[N+1]
;//实际e[i][j]表示包含关键字ki,...,kj的最优二叉搜索树的搜索期望代价, 由于dn=e[n+1,n],d0=e[1,0],因此e[1...n+1,0...n]
double w[N+1]
;//w[i][j]表示包含关键字ki,...,kj的子树成为另一个结点的子树时,子树增加的搜索代价
int root

;//root[i][j]表示包含关键字ki,...,kj的最优二叉搜索树的根节点kr的下标r,因为这里的i和j下标都是1~N,所以用root

初始化
double p[N+1];//p[i]表示关键字ki搜索概率,注意概率是double类型
double q[N+1];//q[i]表示伪关键字di的搜索概率

/*
计算e[1]

e[i][j]={qi-1  , j=i-1
{min i<=r<=j { e[i][r-1] + w[i][j] + e[r+1][j] }
w[i][i-1]=q[i-1]
w[i][j]=w[i][r-1] + pr + w[r+1][j]
w[i][j]=w[i][j-1] + pj + qj , j >= i
*/

//计算搜索代价增加数组
void getW(int n)
{
//初始化w和e矩阵中的值为特殊值
for(int i = 1; i <= n+1 ; i++)
{
for(int j = 0; j <= n ; j++)
{
w[i][j] = -1;
e[i][j] = -1;
}
}
//初始化搜索代价增加值
for(int i = 1 ; i <= n+1 ; i++)
{
w[i][i-1] = q[i-1];
//初始化e
e[i][i-1] = q[i-1];
}
//用递推公式,计算搜索代价增加数组的其他值
for(int i = 1 ; i <= n + 1 ; i++)
{
for(int j = i ; j <= n ; j++)
{
w[i][j] = w[i][j-1] + p[j] + q[j];
}
}
}

//递归版本,计算最优二叉搜索树的期望搜索代价
double optimalBinarySearchTree(int i, int j, int n)
{
if(e[i][j] != -1)
{
return e[i][j];
}
else if(j == i-1)
{
e[i][j] = q[i-1];
return e[i][j];
}
else
{
int iMin = INT_MAX;
for(int r = i ; r <= j ; r++)
{
int iValue = optimalBinarySearchTree(i,r-1,n) + w[i][j] + optimalBinarySearchTree(r+1,j,n);
if(iValue < iMin)
{
iMin = iValue;
root[i][j] = r;
}
}
e[i][j] = iMin;
return e[i][j];
}
}

//递推版本,注意返回值也是浮点数
double optBinSeaTree(int n)
{
//初始化e和w
for(int i = 1 ; i <= n + 1 ; i++)
{
e[i][i-1] = q[i-1];
w[i][i-1] = q[i-1];
}
//设置步长来更新存在相邻递推关系的公式
for(int l = 1 ; l <= n ; l++)
{
for(int i = 1 ; i <= n - (l-1) ; i++)
{
int j = i + l - 1;//设定后面的下标是前面的下标加上步长
e[i][j] = INT_MAX;//初始化需要计算的值为无穷大
w[i][j] = w[i][j-1] + p[j] + q[j];
for(int r = i ; r <= j ; r++)
{
double iTemp = e[i][r-1] + w[i][j] + e[r+1][j];//注意是浮点数不是整数
if(iTemp < e[i][j])
{
e[i][j] = iTemp;
root[i][j] = r;
}
}
}
}
return e[1]
;
}

void process()
{
int n;
while(cin >> n)
{
for(int i = 1 ; i <= n ; i++)
{
cin >> p[i];
}
for(int j = 0 ; j <= n ; j++)
{
cin >> q[j];
}
double iResult = optBinSeaTree(n);
//double iResult2 = optimalBinarySearchTree(1, n, n);
cout << setiosflags(ios::fixed) << setprecision(2) << iResult << endl;//setiosflags(ios::fixed)固定浮点显示,
//cout << setiosflags(ios::fixed) << setprecision(2) << iResult2 << endl;//setiosflags(ios::fixed)固定浮点显示,
}
}

int main(int argc, char* argv[])
{
process();
getchar();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息