问题定义
给定一个n个不同关键词的已排序的序列\(K=<k_1,k_2,...,k_n>(k_1<k_2<...<k_n)\),用这些关键字构建一棵二叉搜索树T。
- 对每个关键字\(k_i\),都有一个频率\(p_i\)表示其搜索频率
- 有n+1个“伪关键字”\(d_0,d_1,d_2...,d_n\)表示不在K中的值,每个值都有一个频率\(q_i\)表示对应的搜索频率
- \(d_0\):所有小于\(k_1\)的值
- \(d_n\):所有大于\(k_n\)的值
- \(d_i(i=1,2,...n-1)\):所有介于\((k_i,k_{i+1})\)之间的值
- 每个关键字\(k_i\)是一个内部节点
- 每个伪关键字\(d_i\)是一个叶子节点
对于每次搜索,有两种情况:
- 成功:找到某个关键字\(k_i\)
- 失败:找到某个伪关键字$d_i $
所以:
\[\sum_{i=1}^{n}p_i+\sum_{i=0}^{n}q_i=1 \]
搜索代价
假定一次搜索的代价等于访问的节点数,即此次搜索找到的节点在T中的深度加1。
在T中进行一次搜索的期望代价:
\[E[T中搜索代价]=\sum_{i=1}^{n}(depth_T(k_i)+1)\cdot p_i+\sum_{i=0}^{n}(depth_T(d_i)+1)\cdot q_i\\ =1+\sum_{i=1}^{n}depth_T(k_i)\cdot p_i+\sum_{i=0}^{n}depth_T(d_i)\cdot q_i\\ depth_T:一个节点在树T中的深度 \]
搜索期望最小的二叉搜索树,称为最优二叉搜索树
应用动态规划算法
1. 最优二叉搜索树的结构
-
子树特征
考虑一棵最优二叉搜索树,它必然包含连续关键字\(k_i,...k_j,1\le i\le j \le n\),而且其叶节点必然是伪关键字\(d_{i-1},...,d_j\)
-
最优子结构
如果一棵最优二叉搜索树T有一棵包含关键字\(k_i,...,k_j\)的子树\(T'\),那么\(T'\)必然是包含关键字\(k_i,...,k_j\)和伪关键字\(d_{i-1},...,d_j\)的子问题的最优解
2. 一个递归算法
-
对于包含关键字\(k_i,...,k_j\)的子树,所有概率之和\(w(i,j)\)为
\[w(i,j) = \sum_{l=i}^{j}{p_l}+\sum_{l=i-1}^{j}{q_l} \] -
若\(k_r\)为包含关键字\(k_i,...,k_j\)的最优二叉搜索树的根结点:
\[e[i,j]=e[i,r-1]+e[r+1.j]+w(i,j) \] -
递归公式
\[e[i,j]=\begin{cases}q_{i-1}\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\,若j=i-1\\ \min_{i\le r\le j}\{e[i,r-1]+e[r+1,j]+w(i,j)\}\quad若i\le j\end{cases}\\ \]
- \(e[i,j]\)给出最优二叉搜索树的期望搜索代价
3. 计算最优二叉搜索树的期望搜索代价
- \(e[1..n+1,0..n]\):保存\(e[i,j]\)的值
- \(root[i,j]\):记录包含关键字\(k_i,...,k_j\)的子树的根
- \(w[1..n+1,0..n]\):\(w[i,i-1]=q_{i-1}(1\le i\le n+1)\\w[i,j]=w[i,j-1]+p_j+q_j(j\ge i)\)
OPTIMAL-BST(p,q,n)
let e[1..n+1,0..n],w[1..n+1],and root[1..,1..n] be new tables
for i <- 1 to n+1 do
e[i,i-1] = q[i-1]
w[i,i-1] = q[i-1]
for l <- 1 to n do
for i <- 1 to n-l+1 do
j = i+l-1
e[i,j] = INFNITY
w[i,j] = w[i,j-1] + p[j] + q[j]
for r <- i to j do
t = e[i,r-1]+e[r+1,j]+w[i,j]
if t < e[i,j] then
e[i,j] = t
root[i,j] = r
return e and root
4. 构建最优解
PRINT-TREE(root,i,j,parent)
r = root[i][j]
/* parent==0 说明当前为根结点 */
if (parent == 0) do
print 根是k[r]
/* parent==1 说明当前结点为左孩子 */
else if (parent == 1) do
print k[r]是k[i-1]的左孩子
/* parent==1 说明当前结点为左孩子 */
else
print k[r]是k[i-1]的右孩子
/* i==j 说明当前节点的左右子节点都为叶子节点 */
if (j == i) do
print d[r-1]是k[r]的左孩子
print d[r]是k[r]的右孩子
/* i==r 说明当前节点的左子节点为叶子节点,右子树可继续遍历 */
else if (i == r) do
print d[r-1]是k[r]的左孩子
PRINT_TREE(root,r+1,j,2)
/* j==r 说明当前节点的左子树可继续遍历,右子节点为叶子节点 */
else if (j == r) do
PRINT_TREE(root,i,r-1,1)
print d[r]是k[r]的右孩子
/* 否则左右子树均可继续遍历 */
else
PRINT_TREE(root,i,r-1,1)
PRINT_TREE(root,r+1,j,2)
return
代码实现
#include <iostream>
#include <vector>
#define MMAX 999999
using namespace std;
void print_tree(vector<vector<int>>root, int i, int j, int parent)
{
int r = root[i][j];
if (!parent) {
cout << "根是k" << r << endl;
}
else if (parent == 1) {
cout << "k" << r << "是k" << j + 1 << "的左孩子" << endl;
}
else {
cout << "k" << r << "是k" << i - 1 << "的右孩子" << endl;
}
if (j == i) {
cout << "d" << r - 1 << "是k" << r << "的左孩子" << endl;
cout << "d" << r << "是k" << r << "的右孩子" << endl;
return;
}
else if (i == r) {
cout << "d" << r - 1 << "是k" << r << "的左孩子" << endl;
print_tree(root, r + 1, j, 2);
return;
}
else if (j == r) {
print_tree(root, i, r - 1, 1);
cout << "d" << r << "是k" << r << "的右孩子" << endl;
return;
}
else {
print_tree(root, i, r - 1, 1);
print_tree(root, r + 1, j, 2);
}
}
void optimal_bst(vector<float>p, vector<float>q, int n)
{
vector<vector<float>>e(n + 2);
vector<vector<float>>w(n + 2);
vector<vector<int>>root(n + 1);
for (int i = 0; i < n + 2; i++) {
e[i].resize(n + 1);
w[i].resize(n + 1);
}
for (int i = 0; i < n + 1; i++) {
root[i].resize(n + 1);
}
for (int i = 1; i <= n + 1; i++) {
e[i][i - 1] = q[i - 1];
w[i][i - 1] = q[i - 1];
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n - k + 1; i++) {
int j = i + k - 1;
e[i][j] = MMAX;
w[i][j] = w[i][j - 1] + p[j] + q[j];
for (int r = i; r <= j; r++) {
float 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:" << e[1][n] << endl;
print_tree(root, 1, n, 0);
}
int main()
{
int n;
cin >> n;
vector<float> p(n + 1);
vector<float> q(n + 1);
for (int i = 1; i <= n; i++) {
cin >> p[i];
}
for (int i = 0; i <= n; i++) {
cin >> q[i];
}
optimal_bst(p, q, n);
}