算法課筆記系列(四)—— 動態規劃

這兩週都太忙,所以上週也沒總結。這週一起補上。

上週講的是Matroid,一個非常抽象的概念,中文翻譯爲“擬陣”。這個內容放到下一篇博文中。這篇總結一下這周講的“動態規劃(Dynamic Programming, 簡稱DP)”。動態規劃可以應用在生物信息學,控制理論,信息理論,運籌學(Operations research)和計算機科學中的理論、圖像、人工智能、編譯器等等。

首先,對着之前的算法思路進行一個對比:

【貪心算法】是對問題增量地建立一個解決方法,僅考慮當前情況下的最優,並不斷優化標準;

【分治算法】是將一個問題分解爲若干個規模相等的子問題,解出子問題,然後將子問題的解進行組合,得到原問題的解;

而【動態規劃】,則是將一個問題分解爲有重合部分的子問題,將子問題的解不斷地擴大成更大的子問題的解,最終得出滿足條件的原問題的解。

針對課堂上講的案例,這裏做了一個list總結:

(1)Weighted Intervan Scheduling

   

是不是覺得跟之前的貪心算法中的區間調度的例子很像?哈哈~其實就是一個問題,不過與之前貪心算法中不同的是,該區間調度是對每一個時間段給了一個權重,要解決的問題是同時兼容的任務的含有最大權重的子集。

當然,當所有任務的權重都等於1的時候,就變成典型的貪心算法了。我們回憶一下,在貪心算法中,對於該問題,我們是考慮最早結束時間,如果一個任務與之前選中的任務可以兼容,那麼就加入到集合中。但是這種思路在權重任意並且不定的情況下是不可行的。反例如下:

   

對於該問題的考慮如下,首先按照任務的結束時間先後對任務進行排序編號,爲f1,f2,…fn, 定義p(j)是任務i與任務j兼容的最大的索引項i。令OPT(j)爲問題的最優解,包含任務1,2…任務j。

第一種情況:OPT選擇任務j

此時,需要將任務j的權重加進來,不能加入不兼容的任務{p(j)+1,…,j-1},包含剩下的兼容的任務1,2,…p(j)問題的最優解。

第二種情況:OPT不選擇任務j

這樣,OPT一定包含剩下的兼容的任務1,2,…j-1問題的最優解。

   

針對該算法,使用暴力算法的話將會使得複雜度指數級增長,因此採用製表法/記憶法(Memoization), 將每一個子問題的結果放在一個緩存中;可以是一個查找表。

(2)Segmented Least Squares

給出一個平面中的n個點:(x1,y1), (x2, y2),…(xn,yn).目標是找到一條直線y = ax + b使得所有點與該直線上的垂直點的平方差的和最小。

   

(3)揹包問題

有一個揹包和n個物品,第i個物品的重量爲wi(kg)>0,價值爲vi>0。揹包的容量爲W(kg),目標是裝滿揹包使得總價值最大。

這是最典型的動態規劃的算法之一。

  

(4)RNA secondary Structure

有一個字符串B =b1b2,…bn. 全爲字母{A, C, G, U}組成。因爲RNA是單鏈的結構,因此可以擴展爲迴環,組成鹼基對。這樣的結構對於理解分子結構很重要,該結構稱爲RNA的二級結構。目標是給定一個分子B = b1b2,…bn,找出一個二級結構S能夠最大化鹼基對的數目

  

(5)序列對齊

給定兩個字符串,找出兩個字符串對齊的最小損耗(可gap)。如下圖所示:

   

   

後面幾個問題的分析暫時沒寫,先把結論放這裏。最近實在是太忙,有時間會補上。

=====================分===割===線===============2016.4.4更新=============

前天晚上寫得蠻匆促的,覺得沒有很細緻。現在補充一下。根據九度算法筆記整理。

動態規劃的算法很多時候都是遞推求解,像N階樓梯上樓問題(一次可以走兩階或者一階,問有多少種上樓方式,要求採用非遞歸)就是經典的遞推求解問題。若我們把N分別等於1,2,3…的答案依次排列爲一個數列,即需要求這個數列每一個數的值。定義f[n]爲數列中第n個數,同時F[n]爲臺階總數爲n時的上臺階方式總數。首先,當數據規模較小時,我們可以直接得到答案,比如F[1] = 1,f[2]= 2。其次,我們必須確定數列之間的遞推關係。當n大於2時,我們考慮每種上臺階方式的最後一步,到底是兩步還是一步(只有這兩種走法)。分別考慮這兩種走法,即我們將此時所有的上樓梯方式按照最後一步走法的不同分成兩類,分別確定這兩類的上樓梯方式數目。經n-1階到達n階,因爲其最後一步是確定的,所有其上樓梯方式數量與原問題中到達n-1階的方式數量相同,同爲F[n-1];同理,經n-2階到達n階,其上樓梯方式數量與原問題中到達n-2階的方式數量相同,同爲F[n-2]。這樣,我們就確定了達到n階樓梯總的上樓方式個數爲F[n-1]和F[n-2]的和,即F[n] = F[n-1]+F[n-2]。這就是該數列的遞推關係。初始值F[1] = 1,f[2]= 2,由此可以而出所有F[n]的值。

下面轉到動態規劃問題的其他一些例子。

1.最長遞增子序列(LIS)

最長遞增子序列是動態規劃中最經典的問題之一。

在一個已知的序列{a1,a2,…an}中,取出若干數組成新的序列{ai1,ai2,…aim},其中下標i1,i2,…im保持遞增,即新數列中的各個數之間依舊保持原數列中的先後順序,那我們稱新的序列{ai1,ai2,…aim}爲原序列的一個子序列。若在子序列中,當下標ix>iy時,aix > aiy,那麼我們稱這個子序列爲原序列的一個遞增子序列。最長遞增子序列問題,就是在一個給定的原序列中,求得其最長遞增子序列長度。

有序列{a1,a2,…an},求其最長遞增子序列。按照遞推求解的思想,我們用F[i]代表遞增子序列以ai結束時它的最長長度。當i較小時,我們容易直接得出其值,如F[1] = 1.那麼,如何由已經求得的F[i]值推得後面的值呢。假設,F[1]到F[x-1]的值都已經確定,注意到,以ax結尾的遞增子序列,除了長度爲1的情況,其他情況中,ax都是緊跟在一個由ai(i<x)組成遞增子序列之後。要求以ax結尾的最長遞增子序列長度,我們依次比較ax與其之前所有的ai(i<x),若ai小於ax,則說明ax可以跟在以ai結尾的遞增子序列之後,行成一個新的遞增子序列。又因爲以ai結尾的遞增子序列最長長度已經求得,那麼在這種情況下,由以ai結尾的最長遞增子序列再加上ax得到的新的序列,其長度也可以確定,取所有這些長度的最大值,我們即能得到F[x]的值。特殊的,當沒有ai(i<x)小於ax,那麼以ax結尾的遞增子序列最長長度爲1.

即  

例:序列{1,4,3,2,6,5}的最長遞增子序列長度的所有F[i]如下表:

      

總結一下,求最長遞增子序列的遞推公式爲:

      

2.最長公共子序列(LCS)

另一個經典的動態規劃問題是最長公共子序列。

與最長遞增子序列的子序列定義相同,在字符串S中按照其先後順序依次取出若干個字符,並將它們排列成一個新的字符串,這個字符串就被稱爲原字符串的子串。

有兩個字符串S1和S2,求一個最長公共子串,即求字符串S3,它同時爲S1和S2的子串,且要求它的長度最長,並確定這個長度。這個問題被我們稱爲最長公共子序列問題。

與求最長遞增子序列一樣,我們首先將原問題分割成一些子問題,我們用dp[i][j]表示S1中前i個字符與S2中前j個字符分別組成的兩個前綴字符串的最長公共子串長度。顯然的,當i,j較小時我們可以直接得出答案,如dp[0][j]必須等於0.那麼,假設我們已經求得dp[i][j](0 <= i < x, 0 <=j < y)的所有值,考慮如何由這些值繼而推得dp[x][y],求得S1前x個字符組成的前綴子串和S2前y個字符組成的前綴子串的最長公共子序列長度。

若S1[x] =S2[y],即S1中的第x個字符和S2中的第y個字符相同,同時由於他們都是各自前綴子串的最後一個字符,那麼必須存在一個最長公共子串以S1[x]或S2[y]結尾,其他部分等價於S1中前x-1個字符和S2中前y-1個字符的最長公共子串。所以這個子串的長度比dp[x-1][y-1]又增加1,即dp[x][y] = dp[x-1][y-1] + 1.相反的,如果S1[x]≠ S2[y],此時其最長公共子串長度爲S1中前x-1個字符和S2中前y個字符的最長公共子串長度與S1中前x個字符和S2中前y-1個字符的最長公共子串長度的較大者,即在兩種情況下得到的最長公共子串都不會因爲其中一個字符串又增加了一個字符長度發生改變。綜上所述:   

總結一下,最長公共子序列問題的遞推條件:

假設有兩個字符串S1和S2,其中S1長度爲n,S2長度爲m,用dp[i][j]表示S1前i個字符組成的前綴子串與S2前j個字符組成的前綴子串的最長公共子串長度,那麼:

        

由這樣的遞推公式和顯而易見的初始值,我們即能一次求得個dp[i][j]的值,最終dp[n][m]中保存的值即爲兩個原始字符串的最長公共子序列長度。

3.揹包問題

揹包問題有多種變形,0-1揹包,完全揹包和多重揹包等。這裏只闡述0-1揹包的問題。

有個容量爲V的揹包和一些物品。這些物品分別有兩個屬性,體積w和價值v,每種物品只有一個。要求用這個揹包裝下價值儘可能多的物品,求該最大價值,揹包可以不被裝滿。

因爲最優解中,每個物品都有兩種可能的情況,就是在揹包中或者不在揹包中(揹包中有0個該物品或者1個),所以把這個問題稱爲0-1揹包問題。

用dp[i][j]表示在總體積不超過j的情況下,前i個物品所能達到的最大價值。初始時,dp[0][j](0 <= j < V)爲0.依據每種物品是否被放入揹包,每個狀態有兩個狀態轉移的來源。若物品i被放入揹包,設其體積爲w,價值爲v,則dp[i][j] = dp[i-1][j-w]+v,即在總體積不超過j-w時前i-1件物品可組成的最大價值的基礎上再加上i物品的價值v;若物品不加入揹包,則dp[i][j] = dp[i-1][j],即此時與總體積不超過j的前i-1件物品組成的價值最大值等價。選擇他們之中的較大的值成爲dp[i][j]的值。綜上所述,0-1揹包的狀態轉移方程爲:

        

狀態轉移中可以發現,dp[i][j]的轉移僅僅與dp[i-1][j-list[i].w]和dp[i-1][j]有關,即僅與二維數組中本行的上一行有關。因此,上面方程可以優化爲一維:

        


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章