最优二叉搜索树
问题:
给出一系列节点,以及虚节点(不在树中的数值区间)的概率,确定一个平均查找路径最短的二叉搜索树。
1)最优子结构
说明这问题之前,先引入一些符号的说明:
a
i
a_i
ai代表失败节点发生的概率
b
i
b_i
bi代表节点发生的概率
T
(
i
,
j
)
T(i,j)
T(i,j)是由节点
a
i
−
1
a_{i-1}
ai−1,
a
i
a_{i}
ai,
b
i
b_{i}
bi,…
a
j
a_{j}
aj,
b
j
b_{j}
bj构成的查找树
假设一棵二叉查找树的最优查找树为
T
(
i
,
j
)
T(i,j)
T(i,j),对应的,最短平均路径为P。以k为跟节点,能拆分为
T
(
i
,
k
−
1
)
T(i,k-1)
T(i,k−1)及
T
(
k
+
1
,
j
)
T(k+1,j)
T(k+1,j)两棵子树,对应的最短平均路径为
P
l
P_l
Pl,
P
r
P_r
Pr。
对于
T
(
i
,
k
−
1
)
T(i,k-1)
T(i,k−1),如果其不是最优查找树,则存在一个
P
l
′
P_{l'}
Pl′<
P
l
P_l
Pl,使得这样T(i,j)能够得到更优的查找树,其平均路径P’<P,与
T
(
i
,
j
)
T(i,j)
T(i,j)是最优查找树矛盾。
所以一棵最优查找树,其子树也需要为最优的结构。
2)状态转移方程
在考虑动态规划的方案之前,当然先得考虑子问题的规模。在子规模能够接受的范围内,才考虑使用动态规划的方法。
在这个问题当中,子问题相对好确定,关键还是状态转移处的问题,这里就先把这问题说清楚。
w ( i , j ) w(i,j) w(i,j)= Σ i − 1 j a i \Sigma_{i-1}^ja_i Σi−1jai+ Σ i j b i \Sigma_i^jb_i Σijbi,节点概率之和。
p ( i , j ) p(i,j) p(i,j)代表树 T ( i , j ) T(i,j) T(i,j)的最短平均查找路径
对于一棵树 T ( i , j ) T(i,j) T(i,j),假定以k为根节点,能够得到一棵最优查找树,则其 p ( i , j ) p(i,j) p(i,j)可以拆解为:
p ( i , j ) p(i,j) p(i,j)= b k w ( i , j ) ∗ 1 + w ( i , k − 1 ) w ( i , j ) ∗ ( p ( i , k − 1 ) + 1 ) + w ( k + 1 , j ) w ( i , j ) ∗ ( p ( k + 1 , j ) + 1 ) \frac{b_k}{w(i,j)}*1+\frac{w(i,k-1)}{w(i,j)}*(p(i,k-1)+1)+\frac{w(k+1,j)}{w(i,j)}*(p(k+1,j)+1) w(i,j)bk∗1+w(i,j)w(i,k−1)∗(p(i,k−1)+1)+w(i,j)w(k+1,j)∗(p(k+1,j)+1)
其中,
b k w ( i , j ) \frac{b_k}{w(i,j)} w(i,j)bk:指以k节点作为根节点的概率,是一个条件概率。
b k w ( i , j ) \frac{b_k}{w(i,j)} w(i,j)bk*1:指以k节点作为根节点的平均查找长度,根节点查找长度为1。
w ( i , k − 1 ) w ( i , j ) \frac{w(i,k-1)}{w(i,j)} w(i,j)w(i,k−1):表示在T(i,j)条件下,T(i,i-k)作为一个子树的概率。
p ( i , k − 1 ) + 1 p(i,k-1)+1 p(i,k−1)+1:由于以k节点为根节点,T(i,i-k)所有节点深度+1,T(i,i-k)的最短平均查找路径+1。
w ( i , k − 1 ) w ( i , j ) ∗ ( p ( i , k − 1 ) + 1 ) \frac{w(i,k-1)}{w(i,j)}*(p(i,k-1)+1) w(i,j)w(i,k−1)∗(p(i,k−1)+1):表示在当前的划分下,左子树的最短平均查找长度。右子树同理。
对等式化简:
p
(
i
,
j
)
p(i,j)
p(i,j)=
b
k
w
(
i
,
j
)
∗
1
+
w
(
i
,
k
−
1
)
w
(
i
,
j
)
∗
(
p
(
i
,
k
−
1
)
+
1
)
+
w
(
k
+
1
,
j
)
w
(
i
,
j
)
∗
(
p
(
k
+
1
,
j
)
+
1
)
\frac{b_k}{w(i,j)}*1+\frac{w(i,k-1)}{w(i,j)}*(p(i,k-1)+1)+\frac{w(k+1,j)}{w(i,j)}*(p(k+1,j)+1)
w(i,j)bk∗1+w(i,j)w(i,k−1)∗(p(i,k−1)+1)+w(i,j)w(k+1,j)∗(p(k+1,j)+1)
p ( i , j ) p(i,j) p(i,j)= b k + w ( i , k − 1 ) + w ( k + 1 , j ) w ( i , j ) + w ( i , k − 1 ) w ( i , j ) ∗ p ( i , k − 1 ) + w ( k + 1 , j ) w ( i , j ) ∗ p ( k + 1 , j ) \frac{b_k+w(i,k-1)+w(k+1,j)}{w(i,j)}+\frac{w(i,k-1)}{w(i,j)}*p(i,k-1)+\frac{w(k+1,j)}{w(i,j)}*p(k+1,j) w(i,j)bk+w(i,k−1)+w(k+1,j)+w(i,j)w(i,k−1)∗p(i,k−1)+w(i,j)w(k+1,j)∗p(k+1,j)
p ( i , j ) = w ( i , j ) w ( i , j ) + w ( i , k − 1 ) w ( i , j ) ∗ p ( i , i − k ) + w ( k + 1 , j ) w ( i , j ) ∗ p ( k + 1 , j ) p(i,j)=\frac{w(i,j)}{w(i,j)}+\frac{w(i,k-1)}{w(i,j)}*p(i,i-k)+\frac{w(k+1,j)}{w(i,j)}*p(k+1,j) p(i,j)=w(i,j)w(i,j)+w(i,j)w(i,k−1)∗p(i,i−k)+w(i,j)w(k+1,j)∗p(k+1,j)
p ( i , j ) ∗ w ( i , j ) = w ( i , j ) + w ( i , k − 1 ) ∗ p ( i , k − 1 ) + w ( k + 1 , j ) ∗ p ( k + 1 , j ) p(i,j)*w(i,j)=w(i,j)+w(i,k-1)*p(i,k-1)+w(k+1,j)*p(k+1,j) p(i,j)∗w(i,j)=w(i,j)+w(i,k−1)∗p(i,k−1)+w(k+1,j)∗p(k+1,j)
实际上,k这点是需要确认的,需要比较所有可能的情况,所以上述式子为:
p
(
i
,
j
)
∗
w
(
i
,
j
)
=
m
i
n
i
⩽
k
⩽
j
(
w
(
i
,
j
)
+
w
(
i
,
k
−
1
)
∗
p
(
i
,
k
−
1
)
+
w
(
k
+
1
,
j
)
∗
p
(
k
+
1
,
j
)
)
p(i,j)*w(i,j)=min_{i\leqslant k\leqslant j}(w(i,j)+w(i,k-1)*p(i,k-1)+w(k+1,j)*p(k+1,j))
p(i,j)∗w(i,j)=mini⩽k⩽j(w(i,j)+w(i,k−1)∗p(i,k−1)+w(k+1,j)∗p(k+1,j))
=
w
(
i
,
j
)
+
m
i
n
i
⩽
k
⩽
j
(
w
(
i
,
k
−
1
)
∗
p
(
i
,
k
−
1
)
+
w
(
k
+
1
,
j
)
∗
p
(
k
+
1
,
j
)
)
=w(i,j)+min_{i\leqslant k\leqslant j}(w(i,k-1)*p(i,k-1)+w(k+1,j)*p(k+1,j))
=w(i,j)+mini⩽k⩽j(w(i,k−1)∗p(i,k−1)+w(k+1,j)∗p(k+1,j))
不妨令 m ( i , j ) = p ( i , j ) ∗ w ( i m(i,j)=p(i,j)*w(i m(i,j)=p(i,j)∗w(i,j),则进一步化为:
m ( i , j ) = w ( i , j ) + m i n i ⩽ k ⩽ j ( m ( i , k − 1 ) + m ( k + 1 , j ) ) m(i,j)=w(i,j)+min_{i\leqslant k\leqslant j}(m(i,k-1)+m(k+1,j)) m(i,j)=w(i,j)+mini⩽k⩽j(m(i,k−1)+m(k+1,j))
一开始看递推关系式的时候,比较容易迷惑的是,子树高度发生了改变,但是关系式中似乎没有体现出来?从推导过程看出,每次的增加的高度其实都“合并”到w(i,j)当中去了。
3) 子问题
在动态规划中,要求,每一步进行计算的时候,子问题都已经得到解决。
从上一部分的讨论中,我们可以看到,子问题可以按照如下图的方式来进行规划:
m(1,1)->m(2,2)->…->m(n,n)->…->m(1,2)->m(2,3)->…->m(1,n)
4)代码实现
对于包含了从i到j节点的树T(i,j):
w数组是保存的T(i,j)的所有节点的概率之和
m数组是保存T(i,j)的平均最短路径
s数组是保存T(i,j)平均最短路径对应的根节点的编号
#include <vector>
#include <string>
void OBSTa(std::vector<double> &a,std::vector<double> &b,int n ,std::vector<std::vector<double>> &m,std::vector<std::vector<double>> &s,std::vector<std::vector<double>> &w)
{
for (int i=0;i<=n;i++)
{
w[i+1][i]=a[i];
m[i+1][i]=0;
}
for (int r=0;r<n;r++) //node number in array
for (int i=1;i<=n-r;i++) {//head of array
int j=i+r; //tail of array
w[i][j]=w[i][j-1]+a[j]+b[j-1];
m[i][j]= m[i+1][j];
s[i][j]=i;
for (int k=i+1;k<=j;k++) {//find the smallest m[i][j]
double t=m[i][k-1]+m[k+1][j];
if (t<m[i][j])
{
m[i][j]=t;
s[i][j]=k;
}
}
m[i][j]+=w[i][j];
}
}
//根据s结果,构建并查集
void unionFind(int i,int j,int root,std::vector<std::vector<double>> &s,std::vector<int> &result){
int newRoot=s[i][j];
result[newRoot]=root;
if(i<newRoot)
unionFind(i,newRoot-1,newRoot,s,result);
if(newRoot<j)
unionFind(newRoot+1,j,newRoot,s,result);
}
int main(){
std::vector<double> b(9,0.06);
std::vector<double> a{0.1,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04};
int n=b.size();
std::vector<std::vector<double>> m(a.size()+1,std::vector<double>(a.size(),0));
std::vector<std::vector<double>> s(a.size(),std::vector<double>(a.size(),0));
std::vector<std::vector<double>> w(a.size()+1,std::vector<double>(a.size(),0));
OBSTa(a,b,n,m,s,w);
std::vector<int> array(b.size()+1,0);
unionFind(1,b.size(),-1,s,array);
printf("Union Find of BST:\nNode Number:");
for(int i=1;i<b.size()+1;i++)
printf("%2d ",i);
printf("\nValue :");
for(int i=1;i<b.size()+1;i++)
printf("%2d ",array[i]);
printf("\n");
printf("\nShortest average search length:%5.3f",m[1][b.size()]);
return 0;
}