算法导论之动态规划(最长公共子序列和最优二叉查找树)
動態規劃師通過組合子問題的解而解決整個問題,將問題劃分成子問題,遞歸地求解各子問題,然后合并子問題的解而得到原問題的解。和分治算法思想一致,不同的是分治算法適合獨立的子問題,而對于非獨立的子問題,即各子問題中包含公共的子子問題,若采用分治法會重復求解,動態規劃將子問題結果保存在一張表中,避免重復子問題重復求解。
動態規劃在多值中選擇一個最優解,其算法設計一般分為4個步驟:描述最優解的結構;遞歸定義最優解的值;按自底向上的方式計算最優解的值;由計算出的結果構造一個最優解。
1)裝配線調度
求解最快通過工廠裝配線路線的問題,問題描述:有兩條裝配線,用于安裝汽車底盤的零件,建造時間不同采用技術有差異導致安裝時間有快慢;每條裝配線有n個裝配站,裝配線i的第j個裝配站表示為Sij,相應裝配時間為aij,每條裝配線上一樣編號的裝配站執行相同的功能,如S1j和S2j。
三個時間點說明:
進入時間:一個汽車地盤進入裝配線(i為1或2),花費時間為ei;
移動時間:在同一條裝配線上的相鄰裝配站(從j到j+1)移動沒有時間開銷,但如果從一條裝配線的裝配站Sij移動到另一條裝配線上,移動需花費時間tij;
離開時間:在完成一條裝配線n個裝配站后,花費xi時間離開裝配線。
要求解的問題是:求解分別通過裝配線1的幾個站和裝配線2的幾個站,才能使汽車底盤零件完成安裝的時間最小。
如果已知一個序列的,在裝配線1使用那些站,在裝配線2使用那些站,則可以在線性時間內得出一個底盤通過工廠裝配線要花的時間。但要知道那條路線是時間花費最小,卻有2n中可能,一條裝配線所使用的裝配站可以看成是{1,2,…,n}的子集。動態規劃法就是從2n中可能發現最優解,我們按照動態規劃法四個步驟來說明:
第一步驟:描述最優解的結構
這一步其實就是找到最優解的特征,從而能夠將問題轉化為若干個子問題來求解。對于裝配線調度花費時間最小問題,要描述其最優解,要從一個最基本的假設出發。假設通過裝配站S1j的最快路線通過裝配站S1j-1,那么底盤是利用了最快的路線從開始點到裝配站S1j-1,如果不是,就是存在另一個條從開始點到裝配站S1j-1的更快路線,這就形成矛盾。這個思路理解起來很簡單,就是假設你是最快的途經點,那么該點前面的路線也一定是最快,否則就不會經過該店。
照此,找出通過裝配站S1j的最快路線的問題最優解,可以轉化為尋找S1j-1或S2j-1最快路線的子問題的最優解。這個就是:要尋找該點的最快,那么就找出該點前面必經的點的最快,這里裝配線只有兩個點會是子結構,裝配線1的j-1站或裝配線2的j-1站。這和路線規劃一致,要找到某一點最短路徑,只要找出這一點前面所有點的最短路徑即可,把問題拆借成子問題。
總結下,裝配線最快時間調度問題的最優解結構:
通過裝配站S1j的最快路線有兩個可能:一是通過裝配站S1j-1的最快路線,然后直接通過裝配站S1j;二是通過裝配站S2j-1的最快路線,從裝配線2移動到轉配線1,然后通過裝配站S1j;類似,裝配站S2j的最快路線也有兩個可能。
第二步驟:遞歸定義最優解的值
輸入:裝配站執行時間aij,裝配線之間裝配站移動時間tij,裝配線進入時間ei和裝配線退出時間xi及每條裝配線裝配站的數量n
Fun_FastestWay(a,t,e,x,n){
f1[1]=e1+a11
f2[1]=e2+a21
for j=2 to n
??????? do if f1[j-1]+ a1j=<f2[j-1]+ a1j +t2j-1
??????????? then f1[j]=f1[j-1]+a1j
???????????????? l1[j]=1 //裝配線
??????????? else f1[j]= f2[j-1]+a1j +t2j-1
???????????????? l1[j]=2
??????? if f2[j-1]+ a2j=<f1[j-1]+ a2j +t1j-1
then f2[j]=f2[j-1]+a2j
???????????????? l2[j]=2
??????????? else f2[j]= f1[j-1]+a2j +t1j-1
???????????????? l2[j]=1
??? if f1[n]+ x1 =< f2[n]+x2
??????? then f= f1[n]+ x1
??????????????????? l=1
??? else f= f2[n]+ x2
??????????????????? l=2
}
這個算法過程就是計算出每一個裝配站的值,記錄在表格中并保存。
第四步驟:構造最優解
上個步驟計算出的每個裝配站時間,就可以輸出最快路線的最優解。
2)矩陣鏈乘法
提出用動態規劃法解決矩陣鏈乘法,有兩個前提,一個是矩陣乘法滿足結合律;另一個是當兩個矩陣相容時才能相乘,所謂相容,就是A的列數等于B的行數。對于n個矩陣鏈相乘,用括號分組求解,然而不同的加括號分組順序,在求積的時間上有很大不同。矩陣A(pxq)和B(qxr)相乘,運算次數為pxqxr次,一個矩陣組的不同分組順序顯然在時間性能上會有很大不同。
這就提出了矩陣鏈乘法的問題:由n個矩陣構成的一個鏈<A1,A2,…,Ai,…,An>,其中,i=1,2,…,n,矩陣Ai的維數(行X列)為pi-1xqi,以最小化的運算次數對矩陣鏈進行括號分組,求得A1A2…An乘積。針對問題,采用動態規劃法四個步驟來求解。
第一步:描述最優解的結構
動態規劃方法的第一步就是尋找最優子結構,利用子結構,可以根據子問題的最優解構造出原問題的一個最優解。一個問題的最優解可以由子問題的最優解構成,而這種最優子結構顯然是可以構成遞歸的。對于矩陣鏈乘法的最優子結構也很好理解,Ai,j表示矩陣乘積AiAi+1…Aj求值的結果,其中i<=j,取k為i和j之間的取值的,可以將矩陣Ai,j的求值分成AiAi+1…Ak到AkAk+1…Aj兩個區間并求其最優解,類似裝配線調度問題,假設AiAi+1…Aj為最優解,則從k分開的兩個區間是最優加括號。
有了這樣的最優子結構,就可以根據子問題的最優解來構造原問題的一個最優解。矩陣鏈乘法求解的問題,可以構造出最優子結構,或者說存在子結構可以求得最優解,即分割乘積,問題的最優解包含了子問題的最優解。
第二步:定義遞歸最優解
這一步就是根據子問題的最優解來遞歸定義一個最優解的代碼。矩陣鏈乘法的問題就是確定AiAi+1…Aj加全部括號的乘積最小代價問題。設m[i,j]為計算矩陣Ai,j所需的標量乘法運算次數的最小值,就是給矩陣鏈怎么分割(用加括號)才能讓乘積運算次數代價最小。那么,對整個問題來說,計算A1,n的最小代價就是m[1,n]。
假設最優加全部括號將乘積A iA i+1…A j從A k和A k+1之間分開,其中i=<k<j,使m[i,j]為計算子乘積A iA i+1…A k和A kA k+1…A j的代價,再加上兩個矩陣相乘的代價,假設每個矩陣A i從是p i-1xp i的,則A i…kA k+1…j要進行p i-1p kp j次標量乘法,從而得出:
第三步:計算最優代價
同樣的,如果用遞歸來實現,算法是指數級時間,而矩陣鏈乘法問題的子問題顯然滿足重疊這個性質,可以用自底向上的表格法來計算最優代價。適合動態規劃法的問題,第一個特點就是具有最優子結構,第二個特點就是子問題重疊。
假設矩陣Ai的維數是pi-1xpi,i=1,2,…,n。輸入一個序列p=<p0,p1,…,pn>,length[p]=n+1,設計一個輔助表m[1…n,1…n]來保存m[i,j]的代價,并使用輔助表s[1…n,1…n]來記錄m[i,j]取得最優解時k的值。
Fun_Matrix_chain_order(p){
??? n= length[p]-1;
??? for i=1 to n
??????? do m[i,j]=0;//i=j下代價為0
??? for l=2 to n
??????? do for i=1 to n-l+1
??????????? do j=i+l-1;
??????????? m[i,j]=∞;//初始化i<j的每一個區間代價
??????????? for k=i to j-1
??????????????? do q=m[i,k]+m[k+1,j]+ pi-1pkpj
????????????????????? ??if q<m[I,j]
???????????????????? then m[i,j]=q;
????????????????????????? s[i,j]=k;
??? return m and s;
}
這個算法比較好理解,就是循環計算每個區間的代價,然后用兩個輔助表記錄,三層循環,算法時間為O(n3),空間需要兩個表的耗費。第四步:構造最優解
有了第三步的矩陣鏈乘積最優標量乘法次數,利用m和s表很容易構造出最優解。
3)動態規劃的特性
動態規劃方法的四步驟通過矩陣鏈乘法和裝配線調度兩個問題已經比較清晰勾勒了,需要總結下現實中怎樣的問題適合用動態規劃法來解決呢?
前文已經提到,適合采用動態規劃方法來求解最優問題需要滿足兩個特性:具有最優子結構和重疊子問題,同時子問題是獨立的。
如果問題的一個最優解中包含了子問題的最優解,則該問題具有最優子結構。尋找最優子結構,可以遵循如下模式:
問題的一個解是一個選擇,對于給定的問題存在最優解的選擇,且子問題的最優解具有相同結構,可以由子子問題的最優解構成。
通俗地說:這個問題可以分成具有相同結構的子問題,這樣就可以把求問題最優解分到求子問題的最優解。相同結構也將是說問題是重疊的,可以將解原問題的遞歸算法反復地用來解子問題。這個分治法解決的問題不一樣,分治法產生的子問題都是全新的,不具有相同結構。換句話說,動態規劃法要解決的問題,具有俄羅斯套娃一樣的性質,而分治法面對的則是子問題都具有不同結構和求解性質,不能將同樣算法用于問題和不同子問題。這里還需要提到的是貪心算法和動態規劃法一樣適用于問題具有最優子結構,不同的是,貪心算法是以自頂向下的方式來適用最優子結構,而動態規劃師自底向上;就是說,貪心算法和動態規劃法面對最優子結構,貪心算法是先定最優解再求子問題最優解,而動態規劃正好相反,先求子問題最優解在推問題最優解;貪心算法是先選擇最優而后求解,動態規劃法是先求解最優而后選擇。
值得說明的是,動態規劃要求其子問題重疊同時也要求其獨立。何謂子問題獨立?就是同一個問題的兩個子問題不共享資源,則他們是獨立。而重疊是指子問題是相同的,只不過作為不同問題的子問題。對于理解子問題是獨立的,導論中給出了無權最短路徑和無權最長簡單路徑的分析,回到現實中,也是很好理解這個案例所說明的全權最長簡單路徑的子問題不獨立,無法應用動態規劃法來解決。最短路徑就不說了,最長問題分解到子問題,顯然在某個圖節點上不是獨立的。
更有現實應用意義的當屬應用動態規劃法尋找最長公共子序列和最優二叉查找樹。最大公共子序列(LCS)文中提到可用于DNA串相似匹配,就是尋找兩個字符串之間具有公共序列的最長度。最優二叉查找樹文中提到用于單詞翻譯,并根據頻率構造最優二叉樹,這顯然也適合于根據頻率來排序的場景。這兩個案例的動態規劃法構造四步驟過程就不描述,原理類似,最主要在現實應用中,尋找滿足動態規劃法解決的問題,并改良以適用。
后續如遇到類似 最長公共子序列和最優二叉查找樹的場景,再研究其動態規劃法來解決。總結
以上是生活随笔為你收集整理的算法导论之动态规划(最长公共子序列和最优二叉查找树)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MapReduce基础开发之六Map多输
- 下一篇: MapReduce基础开发之七Hive外