1. 程式人生 > >《演算法導論》之最優二叉搜尋樹

《演算法導論》之最優二叉搜尋樹

最優二叉搜尋樹

假定我們正在設計一個程式,實現英語文字到法語的翻譯。對英語文字中出現的每個單詞,我們需要查詢對應的法語單詞。為了實現這些查詢操作,可以建立一棵二叉搜尋樹,將n個英語單詞作為關鍵字,對應的法語單詞作為關聯資料。由於文字中的每個單詞都要進行搜尋,我們希望花費在搜尋上的總時間儘量少。

通過紅黑樹或其他平衡搜尋樹結構,我們可以假定每次搜尋時間為O(lgn) 。但由於單詞出現的頻率是不同的,像“the”這種頻繁使用的單詞有可能位於搜尋樹中遠離根的位置上,而像“machicolation”這種很少使用的單詞可能位於靠近根的位置上。這樣的結構會減慢翻譯的速度,因為二叉樹搜尋樹中搜索一個關鍵字的權重是深度+1

。我們希望文字中頻繁出現的單詞被置於靠近根的位置。在給定單詞出現頻率的前提下,我們應該如何組織一棵二叉搜尋樹,使得所有搜尋操作訪問的結點總數最少呢?

這個問題稱為最優二叉搜尋樹(optimal binary search tree)問題。其形式化定義如下:給定一個n個不同關鍵字已排序的序列 K=<k1,k2,...,kn>K=<k1,k2,...,kn> (因此 k1<k2<...<knk1<k2<...<kn

),我們希望用這些關鍵字構造一棵二叉搜尋樹。對每個關鍵字 kiki ,都有一個概率pi表示其搜尋頻率。有些要搜尋的值可能不在 KK 中,因此我們還有 n+1n+1 個“偽關鍵字” d0,d1,d2...dnd0,d1,d2...dn 表示不在K中的值。d0d0 表示所有小於 k1k1 的值,dndn 表示所有大於 knkn 的值,對 i=1,2,...,n1i=1,2,...,n-1 ,偽關鍵字di表示所有在 kikiki+1ki+1 之間的值。對每個偽關鍵字 d
idi
,也都有一個概率 pipi 表示對應的搜尋頻率。

下圖顯示了對一個n=5個關鍵字的集合構造的兩顆二叉搜尋樹。每個關鍵字 kiki 是一個內部結點,而每個偽關鍵字 didi 是一個葉結點。每次搜尋要麼成功(找到某個關鍵字 kiki )要麼失敗(找到某個偽關鍵字 didi ),因此有如下公式: 在這裡插入圖片描述

由於我們知道每個關鍵字和偽關鍵字的搜尋頻率,因而可以確定在一棵給定的二叉搜尋樹 TT 中進行一次搜尋的期望代價。假定一次搜尋的代價等於訪問的結點數,即此次搜尋找到的結點在 TT 中的深度再加11。那麼在TT中進行依次搜尋的期望代價為: 在這裡插入圖片描述 其中 depth(T)depth(T) 表示一個結點在樹T中的深度。最後一個等式是由公式(15.10)推導而來。在圖15-9(a)中,我們逐結點計算期望搜尋代價: 在這裡插入圖片描述

對於一個給定的概率集合,我們希望構造一棵期望搜尋代價最小的二叉搜尋樹,我們稱之為最優二叉搜尋樹。圖15-9(b)所示的二叉搜尋樹就是給定概率集合的最優二叉搜尋樹,其期望代價為2.75。這個例子顯示二叉搜尋樹不一定是高度最矮的。而且,概率最高的關鍵字也不一定出現在二叉樹搜尋樹的根結點。在此例中,關鍵字 k5k5 的搜尋概率最高,但最優二叉搜尋樹的根結點為 k2k2 (在所有以 k5k5 為根的二叉搜尋樹中,期望搜尋代價最小者為2.85)。

與矩陣鏈乘法問題相似,對本問題來說,採用窮舉法需檢查指數棵二叉搜尋樹。不出意外,將使用動態規劃方法求解此問題。

  • 步驟1:最優二叉搜尋樹的結構 為了刻畫最優二叉搜尋樹的結構,我們從觀察子樹特徵開始。考慮一棵二叉搜尋樹的任意子樹。它必須包含連續關鍵字 k(i),...k(j)1<=i<=j<=nk(i),...k(j),1<=i<=j<=n, 而且其葉結點必然是偽關鍵字 d(i1),...,d(j)d(i-1),...,d(j)

    我們現在可以給出二叉搜尋樹問題的最優子結構:如果一棵最優二叉搜尋樹T有一棵包含關鍵字 k(i),...,k(j)k(i),...,k(j) 的子樹T’,那麼T’必然是包含關鍵字 k(i),...,k(j)k(i),...,k(j) 和偽關鍵字 d(i1),...,d(j)d(i-1),...,d(j) 的子問題的最優解。我們依舊用“剪下-貼上”法來證明這一結論。如果存在子樹T’’,其期望搜尋代價比T’低,那麼我們將T’從T中刪除,將T’'貼上到相應的位置,從而得到一顆期望搜尋代價低於T的二叉搜尋樹,與T最優的假設矛盾。

    我們需要利用最優子結構性質來證明,我們可以用子問題的最優解構造原問題的最優解。給定關鍵字序列k(i),...,k(j)k(i),...,k(j),其中某個關鍵字,比如說k(r)(i<=r<=j)k(r)(i<=r<=j),是這些關鍵字的最優子樹的根結點。那麼k®的左子樹就包含關鍵字k(i),...,k(r1)k(i),...,k(r-1)(和偽關鍵字d(i1),...,d(r1)d(i-1),...,d(r-1)),而右子樹包含關鍵字k(r+1),...,k(j)k(r+1),...,k(j)(和偽關鍵字d(r),...,d(j)d(r),...,d(j))。只要我們檢查所有可能的根結點k(r)(i<=r<=j)k(r)(i<=r<=j),並對每種情況分別求解包含k(i),...,k(r1)k(i),...,k(r-1)及包含k(r+1),...,k(j)k(r+1),...,k(j)的最優二叉搜尋樹,即可儲存找到原問題的最優解。

    這裡還有一個值得注意的細節——“空子樹”。假定對於包含關鍵字ki,...,kjki,...,kj的子問題,我們選定kiki為根結點。根據前文論證,k(i)k(i)的左子樹包含關鍵字k(i),...,k(i1)k(i),...,k(i-1)的子問題,我們將此序列解釋為不包含任何關鍵字。但請注意,子樹仍然包含偽關鍵字。按照慣例,我們認為包含關鍵字序列k(i),...,k(i1)k(i),...,k(i-1)的子樹不包含任何實際關鍵字,但包含單一偽關鍵字d(i1)d(i-1)。對稱地,我們如果現在k(j)k(j)為根結點,那麼k(j)k(j)的右子樹包含關鍵字k(j+1),...,k(j)k(j+1),...,k(j)——此右子樹不包含任何實際關鍵字,但包含偽關鍵字d(j)d(j)

  • 步驟2:一個遞迴演算法

    我們已經準備好給出最優解值的遞迴定義。我們選取子問題域為:求解包含關鍵字k(i),...,k(j)k(i),...,k(j)的最優二叉搜尋樹,其中i>=1j<=ni>=1,j<=nj>=i1j>=i-1(當j=i1j=i-1時,子樹不包含實際關鍵字,只包含偽關鍵字d(i1)d(i-1)。定義e[i,j]e[i,j]為包含關鍵字k(i),...,k(j)k(i),...,k(j)的最優二叉搜尋樹中進行一次搜尋的期望代價,最終,我們希望計算出e[1,n]e[1,n]

    j=i-1的情況最為簡單,由於子樹只包含偽關鍵字d(i-1),期望搜尋代價為 e[i,i1]=q(i1)e[i,i-1]=q(i-1)

    j>=ij>=i時,我們需要從k(i),...,k(j)k(i),...,k(j)中選擇一個跟結點k(r)k(r),然後構造一棵包含關鍵字k(i),...,k(r1)k(i),...,k(r-1)的最優二叉搜尋樹作為其左子樹,以及一棵包含關鍵字k(r+1),...,k(j)k(r+1),...,k(j)的二叉搜尋樹作為其右子樹。當一棵子樹成為一個結點的子樹時,期望搜尋代價有何變化?由於每個結點的深度都增加了1,根據公式(15.11),這棵子樹的期望搜尋代價的增加值應為所有概率這和。對於包含關鍵字 k(i),...,k(j)k(i),...,k(j) 的子樹,所有概率之和為: 在這裡插入圖片描述

    因此,若k為包含關鍵字 k(i),...,k(j)k(i),...,k(j) 的最優二叉搜尋樹的根結點,我們有如下公式: e[i,j]=p(r)+(e[i,r1]+w(i,r1))+(e[r+1,j]+w(r+1,j))e[ i , j ] =p ( r ) + (e[ i , r-1 ] + w( i , r-1 ) ) + ( e[ r+1 , j ] + w( r+1 , j ) ) 注意,w(i,j)=w(i,r1)+p(r)+w(r+1,j)w( i , j ) = w( i , r-1 ) + p ( r ) + w( r + 1 , j ) 因此e[ i , j ]可重寫為: e[i,j]=e[i,r1]+e[r+1,j]+w(i,j)e[ i , j ] = e[ i , r-1 ] + e[ r+1 , j ] + w( i , j ) (15.13) 遞迴公式(15.13)假定我們知道哪個結點k應該作為根結點。如果選取期望搜尋代價最低者作為根結點,可得最終遞迴公式(15.14): ①若j=i-1,e[i,j]=q(i1)e[i,j]=q(i-1) ②若i<=j,e[i,j]=mine[i,r1]+e[r+1,j]+w(i,j)(i&lt;=r&lt;=j)e[i,j]=min{e[i,r-1]+e[r+1,j]+w(i,j)} (i&lt;=r&lt;=j)