最優二叉查詢樹—動態規劃C++

LZL_lu發表於2020-12-26

問題描述

1、問題描述:
基於統計先驗知識,我們可統計出一個數表(集合)中各元素的查詢概率,理解為集合各元素的出現頻率。比如中文輸入法字型檔中各詞條(單字、片語等)的先驗概率,針對使用者習慣可以自動調整詞頻——所謂動態調頻、高頻先現原則,以減少使用者翻查次數。這就是最優二叉查詢樹問題:查詢過程中鍵值比較次數最少,或者說希望用最少的鍵值比較次數找到每個關鍵碼(鍵值)。為解決這樣的問題,顯然需要對集合的每個元素賦予一個特殊屬性——查詢概率。這樣我們就需要構造一顆最優二叉查詢樹。
  n個鍵{a1,a2,a3…an},其相應的查詢概率為{p1,p2,p3…pn}。構成最優BST,表示為T1n ,求這棵樹的平均查詢次數C[1, n](耗費最低)。換言之,如何構造這棵最優BST,使得C[1, n] 最小。

動態規劃演算法解題思路

動態規劃法策略是將問題分成多個階段,逐段推進計算,後繼例項解由其直接前趨例項解計算得到。對於最優BST問題,利用減一技術和最優性原則,如果前n-1個節點構成最優BST,加入一個節點 an 後要求構成規模n的最優BST。按 n-1, n-2 , … , 2, 1 遞迴,問題可解。自底向上計算:C[1, 2]→C[1, 3] →… →C[1, n]。為不失一般性用C[i, j] 表示由{a1,a2,a3…an}構成的BST的耗費。其中1≤i ≤j ≤n。這棵樹表示為Tij。從中選擇一個鍵ak作根節點,它的左子樹為Tik-1,右子樹為Tk+1j。要求選擇的k 使得整棵樹的平均查詢次數C[i, j]最小。左右子樹遞迴執行此過程。(根的生成過程)
引用書上求解遞迴式:

以輸入節點概率 :0.10 0.15 0.20 0.25 0.30為例
在計算C[ i ] [ j ]是需要從上往下按對角線計算,:
在這裡插入圖片描述

下面我們來計算C [ 1 ] [ 2 ] :
1<=i<=j<=2;
I<=k<=j;
在這裡插入圖片描述
在這裡插入圖片描述

解題思路圖形化

下面介紹一種更為直觀的三角形計算方法,其實本質沒有發生改變,只是把數學公式圖形化,方便填表。
求C[i][j]時,設以C[i][i-1],C[j+1][j],C[i][j]三個點形成的直角三角形的次斜邊上的值累加和為S(斜),直角邊上對應兩點的和為S(直),顯然S[直]有j-i+1個,則C[i][j]=S[斜]+min{S[直]}。更方便的理解為將最優樹劃分為左右子樹,尋求其最小和值(構建最優二叉樹),同時也能由k值確認最優的根節點值。
下面舉個例子求C[1][3]的值,如圖所示:
在這裡插入圖片描述
S(斜)=0.1+0.2+0.4=0.7,S(直)=min{S1(直),S2(直),S3(直)}=min{0+0.8,0.1+0.4,0.4+0}=0.4,則C[1][3]=0.7+0.4=1.1。同理,求C[[2]][[4]],如下圖所示,C[[2]][[4]]=1.4:
在這裡插入圖片描述

思考:為什麼輸入概率相同(無序),輸出結果不一樣

總結一句話就是:輸入資料是有序的,12345分別代表其根值大小,即第一個輸入概率對應的值是1,第二個是2,查詢的時候並不是根據概率查詢,而是根據根值來構造最優二叉查詢樹。下面詳細分析:

  1. 輸出樹不一致
    遇到的問題是輸入同樣的資料,就是各個概率順序不一樣,為什麼得到的最優二叉查詢樹不一樣。
    例如:每個節點對應的查詢概率值:0.10 0.15 0.20 0.25 0.30

在這裡插入圖片描述

那麼最優二叉樹畫出來就是:
在這裡插入圖片描述
但是當輸入概率是:0.25 0.10 0.15 0.20 0.30
在這裡插入圖片描述
那麼最優二叉樹畫出來就是:
在這裡插入圖片描述
從這兩個圖可以看出來,它們的最優平均查詢次數不一樣,連樹畫出來也不一樣。
剛開始我是考慮怎樣最優二叉樹的查詢,如果按照我剛開始的理解就是按照概率查詢,其實是錯的。那麼按照概率查詢,第二種二叉樹的畫法就出錯了,因為從根節點0.15 看,它的左右子節點都比它大,下一步就不知道該查詢那邊的節點了。
那麼我想了一種解決方法,就是像圖一的二叉樹一樣,將二叉樹的概率進行升序排序,那麼它的查詢樹就會符合最優二叉樹的查詢方法,它的左邊節點全部小於根節點,右邊的左根節點都大於根節點。如果一定要排序的話那麼就說明並不具有通用性,這個動態規劃的查詢最優二叉樹的演算法就出錯了。

那麼我就開始考慮是否是我錯了,演算法算出的每一種結果都是對的,然後我就將所有概率換成了根節點。經過多次的結果驗證,發現每個圖都符合最優二叉樹查詢的規律,經過仔細的思考我終於理解了:因為查詢的鍵值是根據根節點,而不是根據概率,而且每個鍵值都是升序。就像跟幾點是1,2 ,3 ,4 ,5,那麼它的鍵值大小排序就是1<2<3<4<5,這也就解釋了求概率矩陣的時候為什麼c[ I ] [ j ]是i到j時連續的,而不是跳變的。所以這個問題隱藏了一個條件,就是輸入的概率對應的鍵值是升序,那麼第二個最優二叉樹應該是:
在這裡插入圖片描述

流程

詳細流程圖
在這裡插入圖片描述

例項

輸入輸入概率:0.25 0.10 0.15 0.20 0.30
在這裡插入圖片描述
對應最優二叉查詢樹:
在這裡插入圖片描述

程式碼

C++寫的程式碼:

//最優二叉查詢樹
#include<bits/stdc++.h>

using namespace std;

//const int maxval = 9999;

double BST(int n,double p[],double c[][100],int r[][100])
{
    for(int i=1;i<=n;i++)
    {
        c[i][i-1]=0;   //Ci矩陣初始化
        c[i][i]=p[i];
        r[i][i]=i;      //R根矩陣初始化

    }

    c[n+1][n]=0;
    for(int d=1;d<n;d++)        //安對角線計算,從第二條對角線開始
    {
        for(int i=1;i<=n-d;i++)     //行的取值範圍
        {

            int j=i+d;          //j求出在對角線上的i對應的
            double minval=9999;
            int mink=i;         //最小值對應根點
            double sum=0;
            for(int k=i;k<=j;k++)
            {
                sum=sum+p[k];
                if(c[i][k-1]+c[k+1][j]<minval)   //三角形比較法,選最小值
                {
                    minval=c[i][k-1]+c[k+1][j];
                    mink=k;
                }
            }
            c[i][j]=minval+sum;//得到了最小值
            r[i][j]=mink;//記錄取得最小值時的根節點
        }
    }

    return c[1][n];
}






int main()
{
    while(1)
    {
        cout<<"input operand :  1  enter   ,2   exit system"<<endl;
        int ch;
        cin>>ch;
        if(ch==2)
            return 0;
        else if(ch==1)
        {
            system("cls");
            int n;
            cout << "input the point number" << endl;
            cin>>n;  //節點個數
            double p[n];  // 概率陣列
            memset(p,0,sizeof(p));
            // 將概率陣列排序,保證正確
            cout<<"input each point probability" <<endl;
            for(int i=1;i<=n;i++)
            {
                cin>>p[i];
            }
            double c[n+2][100];  //動態ci矩陣
            int r[n+2][100];    //根矩陣
            memset(r,0,sizeof(r));
            memset(c,0,sizeof(c));
            double s=BST(n,p,c,r);

            cout << "the minimum compare times is  " << s<<endl;
            cout << "the minimum probability  matrix is" << endl;
            for(int i=1;i<=n+1;i++)
            {
                for(int j=0;j<=n;j++)
                {
                    cout<<std::setw(3);
                    cout << c[i][j] << "    ";
                }
                cout << endl;
             }

            cout << "the point matrix " << endl;
            for(int i=1;i<=n+1;i++)
            {
                for(int j=0;j<=n;j++)
                {
                    cout << r[i][j] << "   ";
                }
                cout << endl;
             }
        }

    }
}

相關文章