您的位置:首页 > 其它

算法导论_15.5 最优二叉查找树

2020-06-04 08:15 876 查看

一、问题

将英文翻译成法语,使用平衡二叉树建立英语-法语单词的键值对,便于搜索;
文章中单词出现次数有频率,有些单词出现频繁, 有些单词出现概率很小;应当将频繁出现的单词放在距离根较近的地方;
此外,注意到有部分英语单词没有对应的法语翻译
访问一个结点的代价是:结点的深度+1;
我们已知英语文章中各类单词出现的频率,试图构造一棵最优二叉查找树,使得翻译文章的速度最快(访问的结点数最小);

翻译整篇英语文章,搜索单词的代价是:

我们已知pi, qi; 对于不同的depth(ki) 和depth(di), 求出E的最小值;不同的二叉树结构,会得到不同的depth(ki) 和depth(di);

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

一个二叉查找树(i, j),其根节点是r, 这个二叉查找树具有最优化结构, 那么这个二叉查找树的左子树(i, r-1) 也具有最优化结构, 其右子树(r+1, j)也具有最优化结构;
因为树(i, j)具有最优化结构(e(i, j)的值最小), 如果左子树不具有最优化结构,可以选用另外一个子树T’代替现有的左子树,但这会导致树e(i, j)的值变得更小,矛盾;
e(i, j) = e(i, r-1)+e(r+1, j)+w(i, r-1), w(r+1, j)+pr;
树的期望代价等于左子树的期望代价+右子树的期望代价, 由于左子树和右子树现在变为子树,其上的所有结点深度+1, 所以加上w(i, r-1) 和w(r+1, j); 再加上pr*1(根结点只要一次访问);

三、寻找动态规划算法的边界(难点)

分析式子:
e(i, j) = e(i, r-1)+e(r+1, j)+w(i, r-1), w(r+1, j)+pr;
定义e(i, i-1) = q(i-1)
当i == j的时候,即e(i, i)代表什么,代表着以ki为根的树的期望代价
得到:
e(i, i) = e(i, i-1) + e(i+1, i) + w(i, i);
我们得到
e(1, 1) = e(1, 0) + e(2, 1) + w(1, 1) = q0+q1 +q0+q1+k1 = 0.45; //没有k0

在分析e(3, 4) = e(3, 3) + e(5, 4) + w(3, 4) = e(3, 3) + d4 + w(3, 4) //选择A
=e(3, 2)+e(4, 4) + w(3, 4) = d2 + e(4, 4) + w(3, 4) //选择B
这两个选择对应两种树的结构;在动态规划的过程中,不断的选择就相当于不断的比较不同的二叉树结构;

因此,关于最优二叉查找树中的边界问题,需要记住两个点:
1) e(i, i-1)相当于q(i-1);
2)很难理解e(k, k)的含义,是一个根为k的树,但是这个树只有虚拟键;
区分e(i, j)的树,其根是k; 这也是一个根为k的树,但是这个树有实键也有虚拟键;
只有虚拟键的树e(k, k)在动态规划的选择过程中,经过左旋转或是右旋转, 变成了带有实键的树;例如, 上图中的k4子树, 一开始只有虚键值,选过过程中k4树将实键值k3变成自己的子树;

四、相关代码

#include <stdio.h>
#include <stdlib.h>

#define n 5

//不建议将这些数组变成自变量存放在栈中,在栈中的变量没有初始化,其值是不确定的
double e[n + 2][n + 2];
double w[n + 2][n + 2];
int root[n+1][n+1];

//对于A(i, j)的边界条件,一般是当i == j的时候,为边界
//这里的边界条件确定比较复杂;
//状态方程是: e(i, j) = e(i, r-1)+e(r+1, j) + w(i, j);
//考虑i == j: e(i, i+1) = e(i, i) + e(i+1, i+1) + w(i, i+1);//到这一步就不能在往下分解了,于是e(i, i-1)就是边界条件
//这里我们定义e(i, i) = qi;
void optimal_bst(double* p, double* q/*, double e[][n+1], double w[][n+1], int root[]
*/) {
//边界条件
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];
//之前的想法是i<r<j,即l最小是2,这样就少了一种情况, 即子树都是虚拟键的例子
for (int r = i; r <= j; r++) {
double tmp = e[i][r - 1] + e[r + 1][j] + w[i][j];
if (tmp < e[i][j]) {
e[i][j] = tmp;
root[i][j] = r;
}
}
}
}
}

//打印子树(i, j),这个子树的根是r,注意子树(i,j)或是左子树,或是右子树;
void print_optimal_bst(int i, int j, int r) {
int root_child;
if (i >= 0 && j >= 0 && i<=n+1 && j<=n+1)
root_child = root[i][j];
else
return;
//处理根的情况
if (root_child == root[1][n]) {
printf("k%d is root\n", root_child);
print_optimal_bst(i, root_child - 1, root_child);
print_optimal_bst(root_child + 1, j, root_child);
return;
}

if (j == i-1) {
if (j < r)
printf("d%d is root k%d  left child\n", j, r);
else
printf("d%d is root k%d right child\n", j, r);
return;
}
else {
if (root_child < r)
printf("k%d is root k%d left child\n", root_child, r);
else
printf("k%d is root k%d right child\n", root_child, r);
}
print_optimal_bst(i, root_child - 1, root_child);
print_optimal_bst(root_child + 1, j, root_child);
}

int main()
{
double p[] = { -1, 0.15, 0.1, 0.05, 0.1, 0.2 };
double q[] = { 0.05, 0.1, 0.05, 0.05, 0.05, 0.1 };

optimal_bst(p, q, e, w, root);
print_optimal_bst(1, n, -1);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: