《算法導論》讀書筆記之第15章 動態規划—最優二叉查找樹


  1、前言:

  接着學習動態規划方法,最優二叉查找樹問題。二叉查找樹參考http://www.cnblogs.com/Anker/archive/2013/01/28/2880581.html。如果在二叉樹中查找元素不考慮概率及查找不成功的情況下,可以采用紅黑樹或者平衡二叉樹來搜索,這樣可以在O(lgn)時間內完成。而現實生活中,查找的關鍵字是有一定的概率的,就是說有的關鍵字可能經常被搜索,而有的很少被搜索,而且搜索的關鍵字可能不存在,為此需要根據關鍵字出現的概率構建一個二叉樹。比如中文輸入法字庫中各詞條(單字、詞組等)的先驗概率,針對用戶習慣可以自動調整詞頻——所謂動態調頻、高頻先現原則,以減少用戶翻查次數,使得經常用的詞匯被放置在前面,這樣就能有效地加快查找速度。這就是最優二叉樹所要解決的問題。

2、問題描述

    給定一個由n個互異的關鍵字組成的有序序列K={k1<k2<k3<,……,<kn}和它們被查詢的概率P={p1,p2,p3,……,pn},要求構造一棵二叉查找樹T,使得查詢所有元素的總的代價最小。對於一個搜索樹,當搜索的元素在樹內時,表示搜索成功。當不在樹內時,表示搜索失敗,用一個“虛葉子節點”來標示搜索失敗的情況,因此需要n+1個虛葉子節點{d0<d1<……<dn},對於應di的概率序列是Q={q0,q1,……,qn}。其中d0表示搜索元素小於k1的失敗結果,dn表示搜索元素大於kn的失敗情況。di(0<i<n)表示搜索節點在ki和k(i+1)之間時的失敗情況。因此有如下公式:

  由每個關鍵字和每個虛擬鍵被搜索的概率,可以確定在一棵給定的二叉查找樹T內一次搜索的期望代價。設一次搜索的實際代價為檢查的節點個數,即在T內搜索所發現的節點的深度加上1。所以在T內一次搜索的期望代價為:

需要注意的是:一棵最優二叉查找樹不一定是一棵整體高度最小的樹,也不一定總是把最大概率的關鍵字放在根部。

(3)動態規划求解過程

1)最優二叉查找樹的結構

  如果一棵最優二叉查找樹T有一棵包含關鍵字ki,……,kj的子樹T',那么這棵子樹T’對於對於關鍵字ki,……kj和虛擬鍵di-1,……,dj的子問題也必定是最優的。

2)一個遞歸解

  定義e[i,j]為搜索一棵包含關鍵字ki,……,kj的最優二叉查找樹的期望代價,則分類討論如下:

當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]重寫為:

最終的遞歸式如下:

3)計算一棵最優二叉查找樹的期望搜索代價

  將e[i,j]的值保存到一個二維數組e[1..1+n,0..n]中,用root[i,j]來記錄關鍵字ki,……,kj的子樹的根,采用二維數組root[1..n,1..n]來表示。為了提高效率,防止重復計算,需要個二維數組w[1..n+1,0...n]來保存w(i,j)的值,其中w[i,j] = w[i,j-1]+pj+qj。數組給出了計算過程的偽代碼:

 1 OPTIMAL_BST(p,q,n)
 2     for i=1 to n+1    //初始化e和w的值
 3        do e[i,i-1] = qi-1;
 4           w[i,i-1] = qi-1;
 5      for l=1 to n
 6         do for i=1 to n-l+1
 7                   do j=i+l-1;
 8                        e[i,j] = MAX;
 9                        w[i,j] = w[i,j-1]+pj+qj;
10                        for r=i to j
11                                do t=e[i,r-1]+e[r+1,j]+w[i,j]
12                                     if t<e[i,j]
13                                          then e[i,j] = t;
14                                               root[i,j] = r;
15 return e and root;

4)構造一棵最優二叉查找樹

  根據地第三步中得到的root表,可以遞推出各個子樹的根,從而可以構建出一棵最優二叉查找樹。從root[1,n]開始向下遞推,一次找出樹根,及左子樹和右子樹。

4、編程實現

  針對一個具體的實例編程實現,現在有5個關鍵字,其出現的概率P={0.15,0.10,0.05,0.10,0.20},查找虛擬鍵的概率q={0.05,0.10,0.05,0.05,0.05,0.10}。采用C++語言是實現如下:

 1 #include <iostream>
 2  using namespace std;
 3  #define N 5
 4  #define MAX 999999.99999
 5  void optimal_binary_search_tree(float *p,float *q,int n,float e[N+2][N+1],int root[N+1][N+1]);
 6  void construct_optimal_bst1(int root[N+1][N+1],int i,int j);
 7  void construct_optimal_bst2(int root[N+1][N+1],int i,int j);
 8  int main()
 9  {
10      float p[N+1] = {0,0.15,0.10,0.05,0.10,0.20};
11      float q[N+1] = {0.05,0.10,0.05,0.05,0.05,0.10};
12      float e[N+2][N+1];
13      int root[N+1][N+1];
14      int i,j;
15      optimal_binary_search_tree(p,q,N,e,root);
16      cout<<"各個子樹的期望代價如下所示:"<<endl;
17      for(i=1;i<=N+1;i++)
18      {
19          for(j=i-1;j<=N;j++)
20              cout<<e[i][j]<<" ";
21          cout<<endl;
22      }
23      cout<<"最優二叉查找樹的代價為: "<<e[1][N]<<endl;
24      cout<<"各個子樹根如下表所示:"<<endl;
25      for(i=1;i<=N;i++)
26      {
27          for(j=i;j<=N;j++)
28              cout<<root[i][j]<<" ";
29          cout<<endl;
30      }
31      cout<<"構造的最優二叉查找樹如下所示:"<<endl;
32      construct_optimal_bst1(root,1,N);
33      cout<<"\n最優二叉查找樹的結構描述如下:"<<endl;
34      construct_optimal_bst2(root,1,N);
35      cout<<endl;
36      return 0;
37  }
38  void optimal_binary_search_tree(float *p,float *q,int n,float e[N+2][N+1],int root[N+1][N+1])
39  {
40      int i,j,k,r;
41      float t;
42      float w[N+2][N+1];
43      for(i=1;i<=N+1;++i) //主表和根表元素的初始化
44      {
45          e[i][i-1] = q[i-1];
46          w[i][i-1] = q[i-1];
47      }
48      for(k=1;k<=n;++k)  //自底向上尋找最優子樹
49          for(i=1;i<=n-k+1;i++)
50          {
51              j = i+k-1;
52              e[i][j] = MAX;
53              w[i][j] = w[i][j-1]+p[j]+q[j];
54 
55              for(r=i;r<=j;r++) //找最優根
56              {
57                  t = e[i][r-1] + e[r+1][j] +w[i][j];
58 
59                  if(t < e[i][j])
60                  {
61                      e[i][j] = t;
62                      root[i][j] = r;
63                  }
64              }
65          }
66  }
67  void construct_optimal_bst1(int root[N+1][N+1],int i,int j)
68  {
69 
70      if(i<=j)
71      {
72          int r = root[i][j];
73          cout<<r<<" ";
74          construct_optimal_bst1(root,i,r-1);
75          construct_optimal_bst1(root,r+1,j);
76      }
77  }
78  void construct_optimal_bst2(int root[N+1][N+1],int i,int j)
79  {
80       if(i==1 && j== N)
81          cout<<"k"<<root[1][N]<<"是根"<<endl;
82       if(i<j)
83       {
84           int r = root[i][j];
85           if(r != i)
86             cout<<"k"<<root[i][r-1]<<"是k"<<r<<"的左孩子"<<endl;
87           construct_optimal_bst2(root,i,r-1);
88           if(r!= j)
89             cout<<"k"<<root[r+1][j]<<"是k"<<r<<"的右孩子"<<endl;
90           construct_optimal_bst2(root,r+1,j);
91       }
92       if(i==j)
93       {
94           cout<<"d"<<i-1<<"是k"<<i<<"左孩子"<<endl;
95           cout<<"d"<<i<<"是k"<<i<<"右孩子"<<endl;
96       }
97       if(i>j)
98           cout<<"d"<<j<<"是k"<<j<<"右孩子"<<endl;
99  }

程序測試結果如下所示:

參考資料:http://www.cnblogs.com/lpshou/archive/2012/04/26/2470914.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM