1. 程式人生 > 實用技巧 >最優二叉查詢樹

最優二叉查詢樹

最優二叉樹也就是哈夫曼樹,最優二叉樹和最優二叉查詢樹是不一樣的。我們說一下他們的定義

最優二叉樹:

給你n個節點,每一個節點有一個權值wi。我們設一棵樹的權值是所有節點的權值乘於每一個節點的深度,但是我們可以構造出來許多二叉樹,我們稱構造出來的那個權值最小的二叉樹就是我們找的最優二叉樹

求解最優二叉樹:

(1) 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結點);

(2) 在森林中選出兩個根結點的權值最小的樹合併,作為一棵新樹的左、右子樹,且新樹的根結點權值為其左、右子樹根結點權值之和;

(3)從森林中刪除選取的兩棵樹,並將新樹加入森林;

(4)重複(2)、(3)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。

最優二叉查詢樹:

給定n個節點的值key,假設x是二叉搜尋樹中的一個結點。如果L是x的左子樹的一個結點,那麼L.key ≤ x.key。如果R是x的右子樹的一個結點,那麼R.key ≥ x.key。使用<key1,key2,key3....keyn>表示,且我們設定key1<key2<key3<keyn

對於n個節點都有一個訪問概率pi,使用<p1,p2,p3....pn>表示。還有未找到訪問點概率qi,我們使用<q0,q1,q2,q3....qn>表示。

例如訪問到[-∞,key1)的概率是q0,訪問到(key1,key2)的概率是q1,,,,訪問到(keyn,

)的概率是qn。

我們設定[-∞,key1)區間為d0(key1,key2)區間為d1,,,,(keyn,∞)區間是dn。

所以是不會出現對於i,j(1<=i,j<=n)滿足keyi==keyj的情況出現

我們需要把2*n+1個節點放在一個二叉樹上,其中n個節點是keyi,還有n+1個節點di。

最後形成的二叉樹中葉節點肯定是di。且∑ni=1pi+∑ni=0qi=1

假定一次搜尋的代價等於訪問的結點數,也就是此次搜尋找到的結點在二叉搜尋樹中的深度再加1。給定一棵二叉搜尋樹T,我們可以確定進行一次搜尋的期望代價如下:

其中depthT​表示一個結點在二叉搜尋樹T中的深度。

E =∑n

i=1(depthT(keyi)+1)*pi +∑ni=0(depthT(keyi)+1)*qi

 = 1 + ∑ni=1(depthT(keyi))*pi +∑ni=0(depthT(keyi))*qi

我們要找到那個期望E最小的滿足題意二叉樹,這就是最優二叉查詢樹

最優二叉樹是符合最優子結構的,假設由關鍵字子序列<keyi,keyi+1,,,,keyj>和偽關鍵字子序列<di-1,di,di+1,,,,,dj>構成的一棵最優二叉搜尋樹以kr ( i ≤ r ≤ j )為根結點。那麼它的左子樹由子序列<keyi,,,,keyr-1>和<di-1,,,,dr-1>構成,這顆左子樹顯然也是一棵最優二叉搜尋樹。同樣,它的右子樹由子序列<keyr+1,,,,keyj>和<dr,,,,dj>構成,這顆右子樹顯然也是一棵最優二叉搜尋樹。

注意一下空子樹,也就是由關鍵字<keyi,,,,keyj>,且當選ki為根節點的時候,它的左子樹<keyi,keyi-1>就只包含di-1,同理選kj為根節點時,右子樹只包含dj。

用e[i,j]表示包含關鍵字子序列<keyi,keyi+1,,,,keyj>的最優二叉搜尋樹的期望搜尋代價。我們最終希望計算出e[1,n]。

當j=i-1時,說明此時只有di-1,故e[i,i-1] = qi-1

當j≥i時,需要從ki,……,kj中選擇一個跟kr,然後用關鍵字ki,……,kr-1來構造一棵最優二叉查詢樹作為左子樹,用關鍵字kr+1,……,kj來構造一棵最優二叉查詢樹作為右子樹。定義一棵有關鍵字ki,……,kj的子樹,定義概率的總和為:

因此如果kr是一棵包含關鍵字ki,……,kj的最優子樹的根,則有:

整理得:

最終遞推式:


e[i,j]給出了最優二叉搜尋樹子問題的期望搜尋代價。我們還需要記錄最優二叉搜尋樹子問題的根結點,用root[i,j]來記錄。

給出虛擬碼:

程式碼:

#include<iostream>
using namespace std;

const int MAX=9999999;
//const int N=5;  //這是第一個例子
//float p[N+1]={0,0.15,0.10,0.05,0.1,0.20};
//float q[N+1]={0.05,0.10,0.05,0.05,0.05,0.10};
const int N=7;    //這是第二個例子
float p[N+1]={0.04 ,0.06, 0.08, 0.02, 0.10, 0.12, 0.14};
float q[N+1]={0.06 ,0.06, 0.06, 0.06, 0.05, 0.05, 0.05,    0.05};

float e[N+2][N+1];
int root[N+1][N+1];
float w[N+2][N+1];

void optimal_bst_search_tree(float p[],float q[],int n)
{
    int i;
    for(i=1;i<=n+1;i++)
    {
        e[i][i-1]=q[i-1];
        w[i][i-1]=q[i-1];
    }
    int l,j,r;
    for(l=1;l<=n;l++)  //列舉區間長度,也就是e[i][j]的j-i+1的長度
    {
        for(i=1;i<=n-l+1;i++)
        {
            j=i+l-1;
            e[i][j]=MAX;
            w[i][j]=w[i][j-1]+p[j]+q[j];
            for(r=i;r<=j;r++)
            {
                double 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;
                }
            }
        }
    }
}

void print_root()
{
    int i,j;
    cout<<"各子樹的根:"<<endl;
    for(i=1;i<=N;i++)
    {
        for(j=1;j<=N;j++)
            cout<<root[i][j]<<" ";
        cout<<endl;
    }
}

void construct_optimal_bst(int i,int j)
{
    if(i<=j)
    {
        int r=root[i][j];
        cout<<r<<" ";
        construct_optimal_bst(i,r-1);
        construct_optimal_bst(r+1,j);
    }
}
void print_bst(int i,int j)
{
    if(i==1&&j==N)
        cout<<"root is "<<root[i][j]<<endl;
    if(i<j)
    {
        int r=root[i][j];
        if(i!=r)
            cout<<"left child root "<<root[i][r-1]<<endl;
        print_bst(i,root[i][j]-1);
        if(j!=r)
            cout<<"right child root "<<root[r+1][j]<<endl;
        print_bst(root[i][j]+1,j);
    }
}
int main()
{
    optimal_bst_search_tree(p,q,N);
    print_root();
    cout<<"構造的最優二叉樹:"<<endl;
    construct_optimal_bst(1,5);
    cout<<endl;
    print_bst(1,N);
    return 0;
}
//0.04 ,0.06, 0.08, 0.02, 0.10, 0.12, 0.14
//0.06 0.06 0.06 0.06 0.05 0.05 0.05    0.05

第二個例子的最優二叉查詢樹如下:算出來期望是3.12