Age wrinkles the body. Quitting wrinkles the soul. 歲月使皮膚起皺,放棄使靈魂起皺。——Douglas MacArthur(道格拉斯.麥克阿瑟)
《 算法導論》 學習筆記
1. 概述
利用最優二叉搜索樹來實現樹的搜索代價最小。樹上的每一個節點都有一個被搜索到的概率值
形式化定義:給定n個不同關鍵字已經排序的序列
2. 問題案例
n=5的關鍵字集合以及如下的搜索概率,構造二叉搜索樹。
期望搜索代價的計算公式:
我們已知:
3. 動態規劃思路解析
Step1 :最優二叉搜索樹的結構:爲了刻畫最有二叉搜索樹的結構我們從觀察子樹特徵開始。Op-BST的T的子樹T’肯定也是Op-BST,我們可以使用剪切-粘貼法來證明。
Step2:遞歸算法的構建:我們給出最優解的遞歸定義,我們選取子問題爲求包含關鍵字
j=i-1的情況最簡單,由於子樹只含有僞關鍵字
當j>=i時,我們需要從
現在需要考慮的是,當一棵樹成爲一個節點的子樹時,期望搜索代價怎麼變化?子樹中每個節點深度都增加1.期望搜索代價增加量爲子樹中所有概率的總和。
對一棵關鍵字ki,…,kj的子樹,定義其概率總和爲:
因此,如果
注意:
因此e[i,j]可以重寫爲
如果選取期望搜索代價最低者最爲根節點,可以得出最終的遞推公式:
Step3:計算二叉搜索樹的期望搜索代價:
僞代碼算法:
OPTIMAL-BST(p[],q[],n){
let e[1,...n+1,0,...n] and root[1,...n,1,...n] and w[1,...n+1,0,...n]
//e[1,0]表示只有僞關鍵字d0的代價,e[n+1,n]表示只有僞關鍵字dn的代價
//在w[1,...n+1,0,...n]的下標含義一致
//初始化e[i, i - 1]和 w[i, i - 1]
for i ← 1 to n + 1
do e[i, i - 1] ← qi-1
w[i, i - 1] ← qi-1
for l ← 1 to n
do for i ← 1 to n - l + 1
do j ← i + l - 1
e[i, j] ← ∞
w[i, j] ← w[i, j - 1] + pj + qj
for r ← i to j
do t ← e[i, r - 1] + e[r + 1, j] + w[i, j]
if t < e[i, j]
then e[i, j] ← t
root[i, j] ← r
return e and root
}
算法分析,在每一次的計算
Step4:構建最優解輸出:
算法設計:
CONSTRUCTOR-OPTIMAL-BST(root[][],i,j,r){
int rootChild = root[i][j];//子樹根節點
if (rootChild == root[1][n])
{
//輸出整棵樹的根
print("K"+rootChild+"是根");
CONSTRUCTOR-OPTIMAL-BST(root,i,rootChild - 1,rootChild);
CONSTRUCTOR-OPTIMAL-BST(root,rootChild + 1,j,rootChild);
return;
}
if (j < i - 1)
{
return;
}
else if (j == i - 1)//遇到虛擬鍵
{
if (j < r)
{
print( "d" + j + "是" + "k" + r + "的左孩子" );
}
else {
print( "d" + j + "是" + "k" + r + "的右孩子" );
}
return;
}
else//遇到內部結點
{
if (rootChild < r)
{
print ("k" + rootChild + "是" + "k" + r + "的左孩子" );
}
else{
print ("k" + rootChild + "是" + "k" + r + "的右孩子" );
}
}
CONSTRUCTOR-OPTIMAL-BST(root[],i,rootChild - 1,rootChild);
CONSTRUCTOR-OPTIMAL-BST(root[],rootChild + 1,j,rootChild);
}
4. 最優二叉搜索樹Java實現模擬
算法Java實現源碼:
package lbz.ch15.dp.ins4;
/**
* @author LbZhang
* @version 創建時間:2016年3月10日 下午10:04:32
* @description 測試最優二叉搜索樹
*/
public class TestMain {
public static void main(String[] args) {
double[] p={0,0.15,0.1,0.05,0.1,0.2}; //n=5關鍵字有5個
double[] q={0.05,0.1,0.05,0.05,0.05,0.1}; //葉子結點有n+1 = 6個
///這裏的關鍵字長度爲5
int n = p.length;
System.out.println("輸出根節點輔助表");
int[][] root = Optimal_BST(p,q,n-1);
int temp = root.length-1;
for(int i=1;i<temp;i++){
for(int j=1;j<temp;j++){
System.out.print(root[i][j]+"-");
}
System.out.println();
}
printOptimalBST(root,1,5,root[1][5]);
}
/**
* DP在計算最優二叉樹的輔助表的算法實現
* @param p
* @param q
* @param n
* @return
*/
private static int[][] Optimal_BST(double[] p, double[] q, int n) {
double[][] e = new double[n+2][n+2];//
double[][] w = new double[n+2][n+2];
int[][] root = new int[n+2][n+2];
//初始化葉子結點的值
for(int i=1;i<=n+1;i++){
e[i][i-1]=q[i-1];
w[i][i-1]=q[i-1];
}
for(int l=1 ; l<=n ; l++){///最外層循環是逐漸的將關鍵字個數從一個擴展到n個
for(int i=1;i<=n-l+1;i++){
int j=i+l-1;
e[i][j]=Double.MAX_VALUE;
w[i][j]=w[i][j-1]+p[j]+q[j];
for(int r=i;r<=j;r++){
double t = e[i][r-1]+e[r+1][j]+w[i][j];
if(t<e[i][j]){
e[i][j]=t;
root[i][j]=r;///存儲根節點的位置
}
}
}
}
System.out.println("輸出當前的最小代價:"+e[1][n]);
return root;
}
/**
* 構建最優二叉搜索樹
* @param root
* @param i
* @param j
* @param k
*/
private static void printOptimalBST(int[][] root, int i, int j, int r) {
int rootChild = root[i][j];
if(rootChild==r){
System.out.println("K"+rootChild+"是根");
printOptimalBST(root,i,rootChild - 1,rootChild);
printOptimalBST(root,rootChild + 1,j,rootChild);
return;
}
if (j < i - 1)
{
return;
}
else if (j == i - 1)//遇到虛擬鍵
{
if (j < r)
{
System.out.println( "d" + j + "是" + "k" + r + "的左孩子" );
}
else {//j>=r
System.out.println( "d" + j + "是" + "k" + r + "的右孩子" );
}
return;
}
else//遇到內部結點
{
if (rootChild < r)
{
System.out.println ("k" + rootChild + "是" + "k" + r + "的左孩子" );
}
else{
System.out.println ("k" + rootChild + "是" + "k" + r + "的右孩子" );
}
}
printOptimalBST(root,i,rootChild - 1,rootChild);
printOptimalBST(root,rootChild + 1,j,rootChild);
}
}