1. 程式人生 > >0020演算法筆記——【動態規劃】最優二叉搜尋樹問題

0020演算法筆記——【動態規劃】最優二叉搜尋樹問題

 1、問題描速:

設 S={x1, x2, ···, xn} 是一個有序集合,且x1, x2, ···, xn表示有序集合的二叉搜尋樹利用二叉樹的頂點儲存有序集中的元素,而且具有性質:儲存於每個頂點中的元素x 大於其左子樹中任一個頂點中儲存的元素,小於其右子樹中任意頂點中儲存的元素。二叉樹中的葉頂點是形如(xi, xi+1) 的開區間。在表示S的二叉搜尋樹中搜索一個元素x,返回的結果有兩種情形:

    (1) 在二叉樹的內部頂點處找到: x = xi
    (2) 在二叉樹的葉頂點中確定: x∈ (xi , xi+1)

    設在情形(1)中找到元素x = xi

的概率為bi;在情形(2)中確定x∈ (xi , xi+1)的概率為ai。其中約定x0= -∞ , xn+1= + ∞ ,有

    

    集合{a0,b1,a1,……bn,an}稱為集合S的存取概率分佈。

最優二叉搜尋樹在一個表示S的二叉樹T中,設儲存元素xi的結點深度為ci;葉結點(xj,xj+1)的結點深度為dj

     

 注:在檢索過程中,每進行一次比較,就進入下面一層,對於成功的檢索,比較的次數就是所在的層數加1。對於不成功的檢索,被檢索的關鍵碼屬於那個外部結點代表的可能關鍵碼集合,比較次數就等於此外部結點的層數。對於圖的內結點而言,第0層需要比較操作次數為1,第1層需要比較2次,第2層需要3次。

     p表示在二叉搜尋樹T中作一次搜尋所需的平均比較次數。P又稱為二叉搜尋樹T的平均路長,在一般情況下,不同的二叉搜尋樹的平均路長是不同的。對於有序集S及其存取概率分佈(a0,b1,a1,……bn,an),在所有表示有序集S的二叉搜尋樹中找出一棵具有最小平均路長的二叉搜尋樹。

     設Pi是對ai檢索的概率。設qi是對滿足ai<X<ai+1,0<=i<=n的識別符號X檢索的概率, (假定a0=--∞且an+1=+ ∞)。


      對於有n個關鍵碼的集合,其關鍵碼有n!種不同的排列,可構成的不同二叉搜尋樹有棵。(n個結點的不同二叉樹,卡塔蘭數)。如何評價這些二叉搜尋樹,可以用樹的搜尋效率來衡量。

例如:識別符號集{1, 2, 3}={do, if, stop}可能的二分檢索樹為:


     若P1=0.5, P2=0.1, P3=0.05,q0=0.15, q1=0.1, q2=0.05, q3=0.05,求每棵樹的平均比較次數(成本)。     

     Pa(n)=1 × p1 + 2 × p2+3 × p3 + 1×q0 +2×q1+ 3×( q2 + q3 ) =1 × 0.5+ 2 × 0.1+3 ×0.05 + 1×0.05 +2×0.1+ 3×( 0.05 + 0.05 ) =1.5

     Pb(n)=1 × p1 + 2 × p3+3 × p2 + 1×q0 +2×q3 + 3×( q1 + q2 ) =1 × 0.5+ 2 × 0.05 + 3 ×0.1 + 1×0.15 +2×0.05+ 3×( 0.1 + 0.05 ) =1.6

     Pc(n)=1 × p2 + 2 × (p1 +  p3) + 2×(q0 +q1 +q2 + q3 ) =1 × 0.1+ 2 × (0.5 + 0.05) + 2×(0.15 + 0.1 + 0.05 + 0.05) =1.9

     Pd(n)=1 × p3 + 2 × p1+3 × p2 + 1 × q3+2 × q0 +3 × (q1+ q2) =1 × 0.05 + 2 × 0.5 + 3 × 0.1 + 1×0.05 + 2 × 0.15 + 3 × (0.1 + 0.05) =2.15

     Pe(n)=1 × p3 + 2 × p2+3 × p1 + 1 × q3+2 × q2 +3 × (q0 + q1) =1 × 0.05 + 2 × 0.1+ 3 × 0.5 + 1×0.05 + 2 × 0.15 + 3 × (0.15 + 0.1) =2.85

     因此,上例中的最小平均路長為Pa(n)=1.5。

     可以得出結論:結點在二叉搜尋樹中的層次越深,需要比較的次數就越多,因此要構造一棵最小二叉樹,一般儘量把搜尋概率較高的結點放在較高的層次

 2、最優子結構性質

     假設選擇 k為樹根,則 1, 2, …, k-1 和a0, a1, …, ak-1 都將位於左子樹 L 上,其餘結點 (k+1, …, n 和 ak, ak+1, …, an)位於右子樹 R 上。設COST(L) 和COST(R) 分別是二分檢索樹T的左子樹和右子樹的成本。則檢索樹T的成本是:P(k)+ COST(L) + COST(R) + …… 。若 T 是最優的,則上式及 COST(L) 和COST(R) 必定都取最小值。

    證明:二叉搜尋樹T 的一棵含有頂點xi , ··· , xj和葉頂點(xi-1 , xi ) , ··· , ( xj , xj+1)的子樹可以看作是有序集{ x, ··· , xj}關於全集為 { xi-1 , xj+1 }的一棵二叉搜尋樹(T自身可以看作是有序集) 。根據S 的存取分佈概率,在子樹的頂點處被搜尋到的概率是:。{xi , ··· , xj}的儲存概率分佈為{ai-1, bi, …, bj, aj },其中,ah,bk分別是下面的條件概率:

     設Tij是有序集{xi , ··· , xj}關於儲存概率分佈為{ai-1, bi, …, bj, aj}的一棵最優二叉搜尋樹,其平均路長為pij,Tij的根頂點儲存的元素xm,其左子樹Tl和右子樹Tr的平均路長分別為pl和pr。由於Tl和Tr中頂點深度是它們在Tij中的深度減1,所以得到:


     由於Ti是關於集合{xi , ··· , xm-1}的一棵二叉搜尋樹,故Pl>=Pi,m-1。若Pl>Pi,m-1,則用Ti,m-1替換Tl可得到平均路長比Tij更小的二叉搜尋樹。這與Tij是最優二叉搜尋樹矛盾。故Tl是一棵最優二叉搜尋樹同理可證Tr也是一棵最優二叉搜尋樹。因此最優二叉搜尋樹問題具有最優子結構性質。

 3、遞推關係:

根據最優二叉搜尋樹問題的最優子結構性質可建立計算pij的遞迴式如下:

     初始時:

     記 wi,j pi,j為m(i,j) ,則m(1,n)=w1,n p1,n=p1,n為所求的最優值。計算m(i,j)的遞迴式為:

    

 4、求解過程:

    1)沒有內部節點時,構造T[1][0],T[2][1],T[3][2]……,T[n+1][n]

    2)構造只有1個內部結點的最優二叉搜尋樹T[1][1],T[2][2]…, T[n][n],可以求得m[i][i] 同時可以用一個數組存做根結點元素為:s[1][1]=1, s[2][2]=2…s[n][n]=n

    3)構造具有2個、3個、……、n個內部結點的最優二叉搜尋樹。

    ……

    r ( 起止下標的差)
    0   T[1][1], T[2][2]       , …,     T[n][n],
    1   T[1][2], T[2][3], …,T[n-1][n],
    2   T[1][3], T[2][4], …,T[n-2][n],
    ……
    r   T[1][r+1], T[2][r+2], …,T[i][i+r],…,T[n-r][n]
    ……
    n-1   T[1][n] 

    具體程式碼如下:

  1. //3d11-1 最優二叉搜尋樹 動態規劃
  2. #include "stdafx.h"
  3. #include <iostream> 
  4. usingnamespace std;  
  5. constint N = 3;  
  6. void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);  
  7. void Traceback(int n,int i,int j,int **s,int f,char ch);  
  8. int main()  
  9. {  
  10.     double a[] = {0.15,0.1,0.05,0.05};  
  11.     double b[] = {0.00,0.5,0.1,0.05};  
  12.     cout<<"有序集的概率分佈為:"<<endl;  
  13.     for(int i=0; i<N+1; i++)  
  14.     {  
  15.         cout<<"a"<<i<<"="<<a[i]<<",b"<<i<<"="<<b[i]<<endl;  
  16.     }  
  17.     double **m = newdouble *[N+2];  
  18.     int **s = newint *[N+2];  
  19.     double **w =newdouble *[N+2];  
  20.     for(int i=0;i<N+2;i++)    
  21.     {    
  22.         m[i] = newdouble[N+2];    
  23.         s[i] = newint[N+2];    
  24.         w[i] = newdouble[N+2];    
  25.     }   
  26.     OptimalBinarySearchTree(a,b,N,m,s,w);  
  27.     cout<<"二叉搜尋樹最小平均路長為:"<<m[1][N]<<endl;  
  28.     cout<<"構造的最優二叉樹為:"<<endl;  
  29.     Traceback(N,1,N,s,0,'0');  
  30.     for(int i=0;i<N+2;i++)    
  31.     {    
  32.         delete m[i];  
  33.         delete s[i];  
  34.         delete w[i];  
  35.     }   
  36.     delete[] m;  
  37.     delete[] s;  
  38.     delete[] w;  
  39.     return 0;  
  40. }  
  41. void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)  
  42. {  
  43.     //初始化構造無內部節點的情況
  44.     for(int i=0; i<=n; i++)  
  45.     {  
  46.         w[i+1][i] = a[i];  
  47.         m[i+1][i] = 0;  
  48.     }  
  49.     for(int r=0; r<n; r++)//r代表起止下標的差
  50.     {  
  51.         for(int i=1; i<=n-r; i++)//i為起始元素下標
  52.         {  
  53.             int j = i+r;//j為終止元素下標
  54.             //構造T[i][j] 填寫w[i][j],m[i][j],s[i][j]
  55.             //首選i作為根,其左子樹為空,右子樹為節點
  56.             w[i][j]=w[i][j-1]+a[j]+b[j];  
  57.             m[i][j]=m[i+1][j];  
  58.             s[i][j]=i;  
  59.             //不選i作為根,設k為其根,則k=i+1,……j
  60.             //左子樹為節點:i,i+1……k-1,右子樹為節點:k+1,k+2,……j
  61.             for(int k=i+1; k<=j; k++)  
  62.             {  
  63.                 double t = m[i][k-1]+m[k+1][j];  
  64.                 if(t<m[i][j])  
  65.                 {  
  66.                     m[i][j]=t;  
  67.                     s[i][j]=k;//根節點元素
  68.                 }  
  69.             }  
  70.