最優二叉搜索樹


問題定義

給定一個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