算法導論 第15章 動態規劃

《算法導論》這門課的老師是黃劉生和張曙,兩位都是老人家了,代課很慢很沒有激情,不過這一章非常有意思。更多見:iii.run


前言:

書中列舉四個常見問題,分析如何采用動態規劃方法進行解決。
  裝配線調度問題
  矩陣鏈乘問題:
  最長公共子序列問題:
  最優二叉查找樹問題:

基本概念

動態規劃通常應用于最優化問題,此類問題可能包含多個可行解。每個解有一個值,而我們期望找到最大或者最小的解。

動態規劃算法的設計可以分為以下4個步驟:

  • 描述最優解的結構。
  • 遞歸定義最優解的值。
  • 按自底向上的方式計算最優解的值。(其實還應該有自頂向下的求解)
  • 由計算出的結果構造一個最優解。

動態規劃算法效率會非常高的原因在于,其特殊的實現方法,也就是第三步。

兩種等價的實現方法:

  • 帶備忘的自頂向下法,此方法按照正常的遞歸編寫過程,但過程會保存每個子問題的解(通常保存在一個數組或散列表中)。當需要一個子問題的解時,程序首先檢查是否已經保存過此解。如果是,直接返回;若不是,遞歸計算這個子問題。這種遞歸方式是帶備忘的
  • 自底而上法,這種方法需要恰當的定義子問題的規模,因為任何一個子問題的求解的依賴著更小的子問題。因此需要將問題進行排序,從小的問題開始處理。這樣可以確保,在處理到大的問題時,其所依賴的更小的子問題已經求解完畢,結果已經保存。

因此,我們會說 動態規劃算法屬于典型的用空間換取時間。由于沒有頻繁的遞歸函數的開銷,自底而上方法的時間復雜度會更好一些。

動態規劃與分治法之間的區別:

  • 分治法是指將問題分成一些獨立的子問題,遞歸的求解各子問題。
  • 動態規劃適用于這些子問題不是獨立的情況,也就是各子問題包含公共子問題。

動態規劃基礎

什么時候可以使用動態規范方法解決問題呢?這個問題需要討論一下,書中給出了采用動態規范方法的最優化問題中的兩個要素:最優子結構重疊子結構

最優子結構(自下向上)

最優子結構是指問題的一個最優解中包含了其子問題的最優解。在動態規劃中,每次采用子問題的最優解來構造問題的一個最優解。

尋找最優子結構,遵循的共同的模式:

  • 問題的一個解可以是做一個選擇,得到一個或者多個有待解決的子問題。

  • 假設對一個給定的問題,已知的是一個可以導致最優解的選擇,不必關心如何確定這個選擇。

  • 在已知這個選擇后,要確定哪些子問題會隨之發生,如何最好地描述所得到的子問題空間。

  • 利用“剪貼”技術,來證明問題的一個最優解中,使用的子問題的解本身也是最優的。

最優子結構在問題域中以兩種方式變化:

  • 有多少個子問題被使用在原問題的一個最優解中。
  • 在決定一個最優解中使用哪些子問題時有多少個選擇。

動態規劃按照自底向上的策略利用最優子結構,即:首先找到子問題的最優解,解決子問題,然后逐步向上找到問題的一個最優解

為了描述子問題空間,可以遵循這樣一條有效的經驗規則,就是盡量保持這個空間簡單,然后在需要時再擴充它。

注意:在不能應用最優子結構的時候,就一定不能假設它能夠應用。 警惕使用動態規劃去解決缺乏最優子結構的問題!

重疊子問題(自上向下)

用來解決原問題的遞歸算法可以反復地解同樣的子問題,而不是總是產生新的子問題。

重疊子問題是指當一個遞歸算法不斷地調用同一個問題。

動態規劃算法總是充分利用重疊子問題,通過每個子問題只解一次,把解保存在一個需要時就可以查看的表中,每次查表的時間為常數。

由計算出的結果反向構造一個最優解:把動態規劃或者是遞歸過程中作出的每一次選擇(記住:保存的是每次作出的選擇)都保存下來,在最后就一定可以通過這些保存的選擇來反向構造出最優解。

做備忘錄的遞歸方法:這種方法是動態規劃的一個變形,它本質上與動態規劃是一樣的,但是比動態規劃更好理解!
  (1) 使用普通的遞歸結構,自上而下的解決問題。
  (2) 當在遞歸算法的執行中每一次遇到一個子問題時,就計算它的解并填入一個表中。以后每次遇到該子問題時,只要查看并返回表中先前填入的值即可。

鋼條切割問題

題目

給定一個長度為n的鋼條,以及一個價格表p,p中列出了每英寸鋼條的價格,將長度為n的鋼條切割為若干短鋼條出售,求一個鋼條的切割方案,使得收益最大,切割工序沒有成本。比如價格表p如下:


長度為n的鋼條,一共有$2^{n-1}$種不同的切割方案,因為可以再距離鋼條左邊為i(i=1,2,…,n-1)處,我們總是可以選擇切割或者不切割。比如下圖表示了n=4的切割情況:

mark

理論依據

我們稱鋼條切割問題滿足最優子結構性質:問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解。

我們可以這樣理解鋼條問題:將鋼條從左邊切下一段長度為i的一段,對剩下的n-i的部分繼續進行切割(遞歸求解),而不對左邊長度為i的一段在進行切割。

$r_n = \mathop{max}\limits_{1 \leq i \leq n} (p_i + r_{n-i})$

這樣問題的解就轉化為最優解了。

自頂向下遞歸實現

CUT-ROD(p,n)
if  n == 0
    return  0
q = -∞
for  i = 1 to n
   q = max(q,  p[i]+CUT-ROD(p, n-i))
return  q

CUT-ROD的效率很差,這是因為CUT-ROD反復的求解一些相同的子問題,下圖顯示了當n==4時的調用情況:


mark

帶備忘的自頂向下

MEMOIZED-CUT-ROD(p,n)
let  r[0..n] be a new array
for  i = 0 to n
    r[i]= -∞
return  MEMOIZED-CUT-ROD-AUX(p, n, r)
 
MEMOIZED-CUT-ROD-AUX(p,n, r)
if  r[n] >= 0
    return  r[n]
if  n == 0
    q = 0
else  q = -∞
    for  i = 1 to n
        q= max(q,  p[i]+MEMOIZED-CUT-ROD-AUX(p, n-i,r))
r[n] = q
return q

該方法與之前的普通遞歸方法類似,只是會在過程中保存子問題的解,當需要一個子問題的解的時候,先查看是否已經保存過了,如果是,則直接使用即可。否則,按常規的遞歸方式計算子問題。所以稱為帶備忘的,因為它記住了之前已經計算出的結果。

自底向上的方法

BOTTOM-UP-CUT-ROD(p,n)
let  r[0..n] be a new array
r[0]= 0
for  j = 1 to n
    q= -∞
    for  i = 1 to j
        q = max(q, p[i]+r[j-i])
    r[j]= q
return  r[n]

方法采用子問題的自然順序,因此過程中依次求解規模為$j=0,1,2,3,4...,n$的問題。

這兩種算法具有相同的時間復雜度,BOTTOM-UP-CUT-ROD主要是雙層嵌套循環,所以時間復雜度$Θ(n2)$。MEMOIZED-CUT-ROD的時間復雜度也是$Θ(n2)$。可以使用子問題圖進行分析。

python實現切割鋼條問題

def cut_rod():
    p = [0,1,5,8,9,10,17,17,20,24,30]
    n = len(p)
    r = [0 for i in range(n)]
    s = [0 for i in range(n)]
    for j in range(n):
        q = -10
        for i in range(j+1):
            if q < (p[i]+r[j-i]):
                q = (p[i]+r[j-i])
                s[j] = i
            #q = max(q,p[i]+r[j-i])
        r[j] = q
        
def find_way(n):
    cut_rod()
    print(n,'--->',r[n])
    while n > 0:
        print(s[n])
        n = n - s[n]

調用find_way(9)輸出

矩陣鏈乘法

題目

給定n 個矩陣的序列,希望求它們的乘積:$A_1A_2A_3...A_n$ 。因為矩陣的乘法滿足結合律,所以可以對n個矩陣序列加括號,來改變乘積順序。比如對于矩陣鏈< $A_1$, $A_2$,$A_3$,$A_4$>可以有下面的加括號方案:

<center>
<center>

不同的加括號的方案,對于乘積運算的代價影響很大.

兩個矩陣相乘,A為p * q矩陣,B為q * r矩陣。所以A 的乘法次數為pqr。

如果A(10,100 ),B(100,5), C(5,50 )三個矩陣相乘。

如果按照((AB)C)的順序,則需要101005 + 10550 = 7500次乘法運算,如果按照(A(BC))的順序,則需要100550 + 1010050 = 75000次乘法運算。所以,不同的加括號方案,對于矩陣鏈乘法的代價影響很大。

解題步驟

刻畫最優解的結構特征

通過尋找最優子結構,利用最優子結構從子問題的最優解中構造出原問題的最優解。
假設$A_iA_{i+1}...A_j$的最優括號花方案的分割點是在$A_k$和$A_{K+1}$之間,一個非平凡的矩陣鏈乘法任何時候都是需要劃分鏈的,任何最優解都是有子問題的最優解構成的

遞歸的定義最優解的值

令$m[i,j]$表示計算矩陣$A_{i,j}$所需標量乘法的最小值,也即原問題的最優解,計算$A_{1..n}$的最低代價就是$m[1,n]$。

  • 對于i == j 的平凡問題,矩陣鏈只包含唯一的矩陣$A_{i,j}$。
  • 對于$A_iA_{i+1}...A_j$的最優括號化方案的切割點在$A_k$和$A_{k+1}$之間。那么$m[i,j]$的解相當于計算$A_{i..k}$和$A_{k+1..j}$的代價加上,合并這兩個子答案所需要的代價$p_{i-1}p_k p_j$

因此,我們得到

$$m[i,j] = m[i,k]+m[k,j]+p_{i-1}p_k p_j$$

計算最優解的值

算法應當按照長度遞增的順序求解矩陣鏈括號化問題,并按照對應的順序填寫表m。對舉證連$A_{i,j}$,其規模為鏈的長度j-i+1

偽代碼就不寫了,直接寫python代碼

python實現矩陣鏈乘法問題

def MATRIX_CHAIN_ORDER(p):  
    n = len(p)  
    s = [[0 for j in range(n)] for i in range(n)]  
    m = [[0 for j in range(n)] for i in range(n)]  
    for l in range(2, n):           #l is the chain length  
        for i in range(1, n-l+1):  
            j = i + l - 1  
            m[i][j] = 1e9  
            for k in range(i, j):  
                q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]  
                if q < m[i][j]:  
                    m[i][j] = q  
                    s[i][j] = k  
    PRINT_OPTIMAL_PARENS(s, 1, n-1)
    return m
    
      
def PRINT_OPTIMAL_PARENS(s, i, j):  
    if i == j:  
        print('A', end = '')  
        print(i, end = '')  
    else:  
        print('(', end = '')  
        PRINT_OPTIMAL_PARENS(s, i, s[i][j])  
        PRINT_OPTIMAL_PARENS(s, s[i][j]+1, j)  
        print(')', end = '')  
   

if __name__ == "__main__":
    A = [30, 35, 15, 5, 10, 20,25]
    m = MATRIX_CHAIN_ORDER(A) 
    print('\n','共計需要',m[1][n-1],'次相乘')

以上

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容