最优二叉搜索树


问题定义

给定一个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);
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM