算法分析-動態規划(最優二叉搜索樹)


前面說過動態規划最典型的就是解決最優化問題的(具有最優子結構的最優化問題),最優二叉查找樹就是一個典型的最優化問題。


問題描述:

給定一個n元素的中序序列,它可以有卡特蘭數個不同形狀的二叉排序樹。(卡特蘭數的定義及證明參見組合數學):



,如果我們知道每個鍵的查找概率,怎么來構造一個平均查找代價最小(查找成功)的最優二叉查找樹呢?


-------------------------------------------------------------------------------------------------------------


用動態規划來求解,首先要找到它的最優子結構性質,然后根據這個最優子結構來描述和刻畫問題,得到狀態轉移的方程:


1)最優子結構性質:


看看一顆最優二叉查找樹是怎么得到的?逆向思維,如果現在有一棵最優二叉查找樹,root是ak,很容易得出:ak的左右子

樹也是最優二叉查找樹(如果它的子樹不是最優的,那就說明這個子樹還可以繼續調整,那么ak那顆樹就也不是最優的

了)。




2)根據最優子結構性質來描述和刻畫問題


用C[i , j]表示從 i 到 j 的最優二叉查找樹的代價,那么問題就被划分為了n^2個子問題了(頂點號從0計數),假設有n個頂

點,那么我們的目標是要求C[0 , n-1]。(編號從0還是1開始無所謂,在編程的時候注意下標范圍就行了)。


現在根據它的最優子結構來找狀態轉移方程:

從 i 到 j的一個最優二叉查找樹是怎么得到的?(即一個C[i , j]是怎么來的),它是從 i 到 j 之間的頂點中選出一個頂點來做

root,假設選出的這個做root的頂點是 k (i <= k <= j ),那么顯然有:



這個式子其實可以直接想到,不用那么復雜的推導,它就是要找一個能使得C[i , j]代價最小的 k (這個k的范圍在 i 到 j之

間),而后面為什么要加一個從i到j的概率呢?因為挑出了k后,它作root,每個點的查找長度都增加了1。當然,也有更嚴格

的推導,可以參考下:



3)有了狀態轉移方程,就可以畫個矩陣看看初始條件,以及每個C[i , j]依賴那些值(填表順序)。

初始條件有:C[i , i] = Pi,C[i , i-1] = 0

試探一下一個C[i , j]是怎么來的,就可以看出,應該沿對角線來填。

注意狀態轉移方程里當 k = i 或者 k = j 時,C[i , i - 1] 或者 C[j+1 , j]是沒有定義的,在編程中只需要特殊處理下就

行:
對於這種沒有定義的取0,其他的取矩陣中的值。


最后一點,至於具體的實現,tmd書上總喜歡畫一個不是從0開始的表,有時候甚至還橫坐標從0開始,縱坐標從1開始,雖說

是為了填矩陣的方便,但看起來很狗。我一般n規模的問題,就開n * n的矩陣,下表從0到n-1,對超出邊界的做一些特殊處

理就行了,就像上面的C[i , i-1]。看看書上的表(理解意思,具體實現我開的矩陣不一樣,下標控制不一樣):




它這樣來畫表其實就是為了解決C[i , i-1]不在定義范圍內,為了能直接從矩陣中取值才這么做的。


-------------------------------------------------------------------------------------------------------------


上面就構造出了最優二叉查找樹的最優代價的動態規划過程,利用上述狀態轉移方程可以填出所有的C[i , j]。

還有一個問題,跟上一篇文章中提到的一樣,怎么去不僅僅得到C[i , j]這個代價,更要知道對應於這個代價的二叉樹的形

狀?

仍然是構造一個矩陣 A[0...n-1,0...n-1] 來記錄動態規划的過程,每次選出一個 k 作root時,就把 k 記錄下來,即用

A[i , j] = k 表示從 i 到 j 的最優二叉查找樹的root是 k。(它還蘊含從 i 到 k - 1是左子樹,k+1到 j 是右子樹,注意我們

給定的從0到n-1頂點是一個中序序列!)

初始值 A[i , i] = i,表示只有自己的最優二叉查找樹的root就是它自己。最后將得到一個矩陣A。它表達了二叉查找樹的形

狀,當然,還得根據A的含義,從A中獲取從 i 到 j的最優二叉查找樹的形狀。




可以有下列算法,從A中輸出從 i 到 j 的最優二叉查找樹的形狀(輸出它的前序序列,因為中序序列是已知的):

已知前序序列和中序序列,一個二叉樹的形狀就確定了:




也是用遞歸(最優子結構),其實方法跟上一篇Floyd的也差不多。


-------------------------------------------------------------------------------------------------------------

實現:

復制代碼
package Section8;


/*第八章 動態規划 最優二叉查找樹(難!!!)*/

public class OptBST {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
float[] P = {(float) 0.1,(float) 0.2,(float) 0.4,(float) 0.3};

//若返回值是最小代價,測試最小代價是否正確
//System.out.println("輸出最優二叉排序樹的最小代價:\n");
//float result = OptBST(P);
//System.out.println(result);

//若返回值是表達最優二叉排序樹形狀的矩陣,測試矩陣是否正確
System.out.println("輸出表達最優二叉排序樹形狀的矩陣:\n");
int[][] R = OptBST(P);
for(int i = 0;i < R.length;i++)
{
for(int j = 0;j < R.length;j++)
System.out.print(R[i][j] + "  ");
System.out.println();
}

}


public static int[][] OptBST(float[] P){
//接受一個中序序列的點的查找概率數組,返回最優的二叉查找樹的代價(注意P中的概率按順序對應於點的中序序列)
int n = P.length; //結點個數
float[][] result = new float[n][n];

int[][] R = new int[n][n]; //表達二叉查找樹形狀的矩陣

for(int i = 0;i < n;i++)
{
result[i][i] = P[i]; //填充主對角線C[i,i] = P[i]
R[i][i] = i; //R[i][j]表示若只構造從i到j的樹,那么root是R[i][j]
}

for(int d = 1;d <= n - 1;d++) //共n-1條對角線需要填充
{
for(int i = 0;i <= n - d - 1;i++) //橫坐標的范圍與對角線編號d的關系
{
int j = i + d; //一旦橫坐標確定后,縱坐標可以用橫坐標與對角線編號表示出來
float min = 1000000;

int root = 0;

for(int k = i;k <= j;k++)
{
float C1 = 0,C2 = 0; //用C1,C2表示result[i,k-1]和result[k+1,j]
if(k > i)
C1 = result[i][k - 1];
if(k < j)
C2 = result[k + 1][j];

if(C1 + C2 < min)
{
min = C1 + C2;
root = k;
}
}

R[i][j] = root; //R[i][j]的值代表從i到j的最優二叉查找樹的根

float sum = 0;
for(int s = i;s <= j;s++)
//sum = sum + P[i]; //你媽啊,一個小錯誤找了半天
sum = sum + P[s];

result[i][j] = sum + min;
}
}

//return result[0][n-1]; //返回C[1,n],最小代價
return R; //返回表達最優二叉排序樹形狀的矩陣
}

}
復制代碼



最優代價的矩陣和表達形狀的矩陣在一起求的,需要哪個就返回哪個值,見代碼。

很容易看出時間復雜度是 n^3(k的選擇需要一個循環) 的,空間復雜度是 n^2的。

運行結果(返回表達二叉查找樹形狀的矩陣R):


輸出表達最優二叉排序樹形狀的矩陣:

0  1  2  2  
0  1  2  2  
0  0  2  2  
0  0  0  3  


-------------------------------------------------------------------------------------------------------------


思考:

1,分析最優二叉查找樹的時間復雜度,空間復雜度。(見分析講解)


2,寫一個線性時間的偽代碼,從根表(矩陣A)生成最優二叉查找樹(見分析講解)


3,判斷正誤:一顆最優二叉查找樹的根總是包含概率最高的鍵(錯,很容易舉反例,如果這句話是對的,那還要動態規划干

嗎,那就可以用更簡單的辦法來構造最優二叉排序樹了--直接根據概率選最大值就行了)


4,把最優二叉查找樹(查找成功的代價最小)推廣到成功和不成功總代價最小的情況(easy,改變C[i ,j]的含義,加上不成

功的概率即可)


5,證明:




重要:事實上卡特蘭數就是這樣定義的,(如果后面有機會,我會深入學習下卡特蘭數),該遞推關系的解是由卡特蘭數給

出的(現在都忘了,可以參見《組合數學》  盧開澄   北京大學出版社)。


如果不要求嚴格的證明,根據b(n)的含義,其實很容易寫出上述遞推式。(想一想)


-------------------------------------------------------------------------------------------------------------


最后一題:矩陣連乘問題,見下一篇文章,作為一個解題報告。

------------------------------------------------------------------------------------------------------------


總結:

1)怎么去分析一個問題,試圖用動態規划去解決?(先找最優子結構,從最有子結構去刻畫和描述問題

2)逆向思維去找狀態轉移方程

3)相對前面的幾個動態規划算法,最優二叉查找樹的填矩陣順序稍顯復雜(沿對角線),直接看遞推式的數學形式可能看不

出來,畫出矩陣,去嘗試一個C[i ,j]是怎么求出來的,就能清晰的看出來。

4)卡特蘭數非常重要,許多問題都是卡特蘭數問題(參見什么叫做professional?),習題5給出了一個很好的引導證明。


免責聲明!

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



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