這一篇主要關於最優二叉查詢樹的分析與實現,以及演算法導論動態規劃一章的幾道思考題。
最優二叉查詢樹,是經過優化的二叉查詢樹。在知曉每個鍵被查詢到的概率的情況下,生成一棵二叉查詢樹,使得查詢的期望深度最小。最優二叉查詢樹仍然保持二叉查詢樹的性質,所以查詢概率最大的鍵不一定是樹的跟。
形式地,給定由 $n$ 個互異關鍵字組成的有序序列 $k=\,k_,...k_\}$,對每個關鍵字 $k_$,被查詢的概率是 $p_$,對每兩個關鍵字 $k_$ 和 $k_$,查詢落在區間 $(k_, k_)$ (而查詢失敗的)的概率為 $q_$,構造一棵二叉查詢樹使一次查詢的期望深度 $e$ 最低。令序列 $d_$ 為虛擬鍵,表示區間 $(k_, k_)$ 。
查詢期望深度為:
$$e_=\sum_^(depth(k_)+1)\cdot p_+\sum_^(depth(d_)+1)\cdot q_$$
遞迴的,查詢期望深度也可以寫成:
$$e_=(e_+1)\cdot p_+(e_+1)\cdot p_+1\cdot p_$$
解決該問題的思路是:
序列 $k_...k_$ 的最優二叉查詢樹,就是以下這些查詢樹中,深度最小的一棵:
根據遞迴的查詢期望深度,對 $k_$ 為根的二叉查詢樹,如果它是最優二叉查詢樹,則兩個子樹也一定是最優二叉查詢樹。需要記錄的子問題就是,序列 $k_...k_$ 的最優二叉查詢樹深度,以及最優二叉查詢樹。在一張二維表中維護該深度,最後查詢 $k_...k_$ 的最有二叉查詢樹就可以。
我的實現如下:為了簡便,僅僅是求出了最優二叉查詢樹的深度,沒有真正去維護樹本身。
double getdepth(const std::vectordouble>>&depthtable,傳入資料 $p=\$ 和 $q=\$,輸出結果為 $2.75$,與演算法導論上相符。const
inti,
const
intj)
else
}double getprob(const std::vector&p,
const std::vector&q,
const
inti,
const
intj)
double rslt = 0
;
for (int s=i; s<=j; s++)
rslt += q[j+1
];
return
rslt;
}void makeobst(const std::vectorstring>&k,
const std::vector&p,
const std::vector&q,
std::vector
double>>&depthtable,
std::vector
&treetable,
int i, int
j)
double mindepth =int_max;
for (int s=i; s<=j; s++)
}depthtable[i][j] =mindepth;
}void obst(const std::vectorstring>&k,
const std::vector&p,
const std::vector&q)
const
int n =k.size();
std::vector
treetable(n, std::vector
(n));
std::vector
double>> depthtable(n, std::vector(n, -1
)); makeobst(k, p, q, depthtable, treetable,
0, n-1
); std::cout
<0][n-1
];}
思考題15-1雙調歐幾里得問題 歐幾里得旅行商問題是指平面上給定的 $n$ 個點,確定一條連線各點的最短閉合折線。這是乙個np完全問題。雙調歐幾里得問題對其進行了簡化,給出從 最左側的 點出發,嚴格從左至右經過各點,到達最右側的點,再從最右側的點嚴格從右至左回到最左側的點。下圖顯示了7點歐幾里得旅行商問題的解(左圖)以及雙調版本問題的解(右圖)。
思路:將所有點從左向右排序,為 $k_...k_$ ,該序列的兩個子集 $p_...p_$ 和 $q_...q_$,兩個子集的交集為只有 $\k_$和$k_$兩個元素。問題可以這樣考慮:最短路徑就是以下兩條路徑較短的一條:
可遞迴的並需要維護的子問題就是:一條路徑以 $k_$ 為終點,一條路徑以 $k_$ 為終點,路徑不交叉,不遺漏 $k_...k_$ 間的任意一點,的最短路徑之和。那就是以下兩條路徑中最短的(認為 $j>i$):
思考題15-2整齊列印 在印表機上整齊地列印乙個段落,段落由長度為 $l=\...l_\}$ 的 $n$ 個單詞組成,每一行至多輸入 $m$ 個字元。如果某一行包括了單詞 $l_...l_$,那麼行末的空餘字元格數為 $m-j+i-\sum_^l_$。要求整個段落行末多餘空格字元數的立方和最小。
思路:可遞迴的並需要維護的子問題就是:段落 $l_...l_$ 的整齊列印後的多餘空格字元數立方和。假設第一行最多列印 $s$ 個字元,那麼該子問題就的最優解就是如下數個問題中的最優的那個解:
整個問題就是求段落 $l_...l_$ 的整齊列印。
思考題15-4計畫公司聚會 乙個公司具有層次式結構,所有職員形成了一棵以總裁為根的樹。每乙個職員都有對聚會的喜愛程度(乙個實數),但每個職員都不想在聚會上遇到自己的直接上司。生成一張名單,使所有參與者對聚會喜愛程度的和最大,又避免任意兩位參與者是直接的上下級關係。
思路:需要維護的子問題是,以某個職員為根的樹,按照如上規則參加聚會的最大喜愛程度之和。為每個職員維護這個域。
當這個成員不是葉子是,而該成員喜愛度為負,問題的解同上一條中的第一小條。
最後考察以總裁為根的樹,即整個公司,參加聚會的成員。
思考題15-6在棋盤上移動 一張 $n×n$ 的方格期盼,棋子從頂邊移到底邊,只能直行,斜行,類似於西洋棋中的小卒(只不過是從底邊開始,而且斜行並不一定要吃掉棋子)。沒走一次都會獲得一筆金錢,選擇合適的底邊格仔,完成上述移動過程(移動到頂邊),並盡可能多地收集金錢。
思路:很簡單,為每個格仔維護乙個域,表示從這個格仔開始移動到頂邊獲取的最大金錢數。頂邊上都是0,從頂邊開始逐行生成,直到底邊。在底邊選取最大的域就可以了。
思考題15-7最高效益排程 一台機器,在此機器上可處理 $n$ 個作業 $a_,a_...a_$ ,每個左右有乙個處理時間 $t_$,以及最後期限 $d_$ 和 效益 $p_$。假設處理時間都是整數,如何安排處理哪些作業,使效益最高。
思路:所有作業的最後期限,和開始作業的最早時間,之間的時間段,分割成整數段 $0~m$(每一段對應於處理時間中整數的單位)。然後為 $0...m$ 中的每個點 $i$ 維護乙個域,表示 $0...i$ 時間段的最高效益及其作業分配情況。
總算有點明白了「動態規劃」四個字的含義。我想,如果將遞迴版本的演算法全部展開為非遞迴的,那麼大概可以看到,演算法在不斷產生區域性子問題的最優解,並從最優解中尋找較大子問題的最優價,所有的子問題最優解都被妥善地維護起來,而不是立刻拋棄,這樣下次遇到該子問題就可以直接使用結果了。
演算法導論 動態規劃
動態規劃這個演算法,我一直都搞不明白,也許因為我數學能力太差的緣故,總是不得其要領,每次學習這個演算法的時候,總是不知道所謂的狀態轉移方程到底是怎麼樣推導出來的。其實就在我寫這篇部落格的時候,我依然不清楚。什麼問題能用動態規劃來解決呢?動態規劃問題的特徵就是最優子結構,乙個遞迴結構 該問題需要求乙個...
演算法導論 2 動態規劃
動態規劃與分治方法類似,都是通過組合子問題的解來求解原問題 需要注意的是,動態規劃 dynamic programming 這裡的programming並不是指編寫電腦程式,而是指一種 法 分治方法將問題劃分為互不相交的子問題,遞迴地求解子問題,再將它們的解組合起來,求出原問題的解 動態規劃也是通過...
動態規劃 鋼條切割《演算法導論》
給定各長度鋼鐵單位 以及乙個長度為n的鋼條,求最大效益 1 10 分別為p include includeint memorized cut rod aux int p,int len,int r 樸素遞迴演算法 演算法複雜度為2 n級這裡寫 int cut rod int p,int len in...