算法导论_15.5 最优二叉查找树
一、问题
将英文翻译成法语,使用平衡二叉树建立英语-法语单词的键值对,便于搜索;
文章中单词出现次数有频率,有些单词出现频繁, 有些单词出现概率很小;应当将频繁出现的单词放在距离根较近的地方;
此外,注意到有部分英语单词没有对应的法语翻译
访问一个结点的代价是:结点的深度+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; }
- 算法导论15.5 最优二叉查找树
- 【算法导论】动态规划之最优二叉查找树
- 【算法导论】动态规划之最优二叉查找树
- 最优二叉查找树详解(算法导论学习笔记)
- 最优二叉查找树 算法导论216
- 二叉排序树(Binary Sort Tree,二叉查找树,二叉搜索树)--【算法导论】
- 《算法导论》笔记 第15章 15.5 最优二叉查找树
- 【算法学习】最优二叉查找树(动态规划)
- 动态规划之最优二叉搜索树(算法导论)
- 二叉查找树相关算法实现(算法导论12章)
- 动态规划之最优二叉搜索树(算法导论)
- 二叉查找树 (算法导论12)
- 算法导论——(2)二叉查找树的实现
- 【算法学习】最优二叉查找树(动态规划)
- 【算法学习】最优二叉查找树(动态规划)
- 【算法导论】动态规划之“最优二叉搜索树”
- 【算法导论】最优二叉搜索树
- 【算法导论】二叉查找树的操作C++实现
- 算法笔记_053:最优二叉查找树(Java)
- 【算法学习】最优二叉查找树(动态规划)