程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 最優二叉搜索樹問題

最優二叉搜索樹問題

編輯:C++入門知識

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 + 3×( q2 + q3 ) =1 × 0.5+ 2 × 0.05 + 3 ×0.1 + 1×0.15 +2×0.05+ 3×( 0.05 + 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 × 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      因此,上例中的最小平均路長為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)的子樹可以看作是有序集{ xi , ··· , 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]      具體代碼如下:      [cpp] //3d11-1 最優二叉搜索樹 動態規劃   #include "stdafx.h"   #include <iostream>    using namespace std;      const int N = 3;      void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);   void Traceback(int n,int i,int j,int **s,int f,char ch);      int main()   {       double a[] = {0.15,0.1,0.05,0.05};       double b[] = {0.00,0.5,0.1,0.05};          cout<<"有序集的概率分布為:"<<endl;       for(int i=0; i<N+1; i++)       {           cout<<"a"<<i<<"="<<a[i]<<",b"<<i<<"="<<b[i]<<endl;       }          double **m = new double *[N+2];       int **s = new int *[N+2];       double **w =new double *[N+2];          for(int i=0;i<N+2;i++)         {             m[i] = new double[N+2];             s[i] = new int[N+2];             w[i] = new double[N+2];         }           OptimalBinarySearchTree(a,b,N,m,s,w);       cout<<"二叉搜索樹最小平均路長為:"<<m[1][N]<<endl;       cout<<"構造的最優二叉樹為:"<<endl;       Traceback(N,1,N,s,0,'0');          for(int i=0;i<N+2;i++)         {             delete m[i];           delete s[i];           delete w[i];       }        delete[] m;       delete[] s;       delete[] w;       return 0;   }      void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)   {       //初始化構造無內部節點的情況       for(int i=0; i<=n; i++)       {           w[i+1][i] = a[i];           m[i+1][i] = 0;       }          for(int r=0; r<n; r++)//r代表起止下標的差       {           for(int i=1; i<=n-r; i++)//i為起始元素下標           {               int j = i+r;//j為終止元素下標                  //構造T[i][j] 填寫w[i][j],m[i][j],s[i][j]               //首選i作為根,其左子樹為空,右子樹為節點               w[i][j]=w[i][j-1]+a[j]+b[j];               m[i][j]=m[i+1][j];               s[i][j]=i;                  //不選i作為根,設k為其根,則k=i+1,……j               //左子樹為節點:i,i+1……k-1,右子樹為節點:k+1,k+2,……j               for(int k=i+1; k<=j; k++)               {                   double t = m[i][k-1]+m[k+1][j];                      if(t<m[i][j])                   {                       m[i][j]=t;                       s[i][j]=k;//根節點元素                   }               }               m[i][j]+=w[i][j];           }       }   }      void Traceback(int n,int i,int j,int **s,int f,char ch)   {       int k=s[i][j];       if(k>0)       {           if(f==0)           {               //根               cout<<"Root:"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;           }           else           {               //子樹               cout<<ch<<" of "<<f<<":"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;           }              int t = k-1;           if(t>=i && t<=n)           {               //回溯左子樹               Traceback(n,i,t,s,k,'L');           }           t=k+1;           if(t<=j)           {               //回溯右子樹               Traceback(n,t,j,s,k,'R');           }       }   }        4、構造最優解:    算法OptimalBinarySearchTree中用s[i][j]保存最優子樹T(i,j)的根節點中的元素。當s[i][n]=k時,xk為所求二叉搜索樹根節點元素。其左子樹為T(1,k-1)。因此,i=s[1][k-1]表示T(1,k-1)的根節點元素為xi。依次類推,容易由s記錄的信息在O(n)時間內構造出所求的最優二叉搜索樹。      5、復雜度分析與優化:    算法中用到3個數組m,s和w,故所需空間復雜度為O(n^2)。算法的主要計算量在於計算。對於固定的r,它需要的計算時間O(j-i+1)=O(r+1)。因此算法所耗費的總時間為:。事實上,由《動態規劃加速原理之四邊形不等式》可以得到:而此狀態轉移方程的時間復雜度為O(n^2)。由此,對算法改進後的代碼如下: [cpp] //3d11-1 最優二叉搜索樹 動態規劃加速原理 四邊形不等式   #include "stdafx.h"   #include <iostream>    using namespace std;      const int N = 3;      void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w);   void Traceback(int n,int i,int j,int **s,int f,char ch);      int main()   {       double a[] = {0.15,0.1,0.05,0.05};       double b[] = {0.00,0.5,0.1,0.05};          cout<<"有序集的概率分布為:"<<endl;       for(int i=0; i<N+1; i++)       {           cout<<"a"<<i<<"="<<a[i]<<",b"<<i<<"="<<b[i]<<endl;       }          double **m = new double *[N+2];       int **s = new int *[N+2];       double **w =new double *[N+2];          for(int i=0;i<N+2;i++)         {             m[i] = new double[N+2];             s[i] = new int[N+2];             w[i] = new double[N+2];         }           OptimalBinarySearchTree(a,b,N,m,s,w);       cout<<"二叉搜索樹最小平均路長為:"<<m[1][N]<<endl;       cout<<"構造的最優二叉樹為:"<<endl;       Traceback(N,1,N,s,0,'0');          for(int i=0;i<N+2;i++)         {             delete m[i];           delete s[i];           delete w[i];       }        delete[] m;       delete[] s;       delete[] w;       return 0;   }      void OptimalBinarySearchTree(double a[],double b[],int n,double **m,int **s,double **w)   {       //初始化構造無內部節點的情況       for(int i=0; i<=n; i++)       {           w[i+1][i] = a[i];           m[i+1][i] = 0;           s[i+1][i] = 0;       }          for(int r=0; r<n; r++)//r代表起止下標的差       {           for(int i=1; i<=n-r; i++)//i為起始元素下標           {               int j = i+r;//j為終止元素下標               int i1 = s[i][j-1]>i?s[i][j-1]:i;               int j1 = s[i+1][j]>i?s[i+1][j]:j;                  //構造T[i][j] 填寫w[i][j],m[i][j],s[i][j]               //首選i作為根,其左子樹為空,右子樹為節點               w[i][j]=w[i][j-1]+a[j]+b[j];               m[i][j]=m[i][i1-1]+m[i1+1][j];               s[i][j]=i1;                  //不選i作為根,設k為其根,則k=i+1,……j               //左子樹為節點:i,i+1……k-1,右子樹為節點:k+1,k+2,……j               for(int k=i1+1; k<=j1; k++)               {                   double t = m[i][k-1]+m[k+1][j];                      if(t<m[i][j])                   {                       m[i][j]=t;                       s[i][j]=k;//根節點元素                   }               }               m[i][j]+=w[i][j];           }       }   }      void Traceback(int n,int i,int j,int **s,int f,char ch)   {       int k=s[i][j];       if(k>0)       {           if(f==0)           {               //根               cout<<"Root:"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;           }           else           {               //子樹               cout<<ch<<" of "<<f<<":"<<k<<" (i:j):("<<i<<","<<j<<")"<<endl;           }              int t = k-1;           if(t>=i && t<=n)           {               //回溯左子樹               Traceback(n,i,t,s,k,'L');           }           t=k+1;           if(t<=j)           {               //回溯右子樹               Traceback(n,t,j,s,k,'R');           }       }   }   運行結果如圖:  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved