一、概述
1.設計思想
動態規划法將待求解問題分解成若干個相互重疊的子問題,每個子問題對應決策過程的一個階段,通過組合子問題而解決整個問題的解。
2.基本要素
(1)最優子結構
最優性原理體現為問題的最優子結構特性。當一個問題的最優解中包含了子問題的最優解時,則稱該問題具有最優子結構特性。
(2)重疊子問題
通常,子問題的重疊關系表現在對給定問題求解的遞推關系(稱為動態規划函數)中,將子問題的解求解一次並填入表中,當需要再次求解該子問題時,通過查表獲得,從而避免了大量重復計算。
(3)問題狀態須滿足無后效性。
所謂無后效性是指:“下一時刻的狀態只與當前狀態有關,而和當前狀態之前的狀態無關,當前狀態是對以往決策的總結”。
3.求解過程
一般來說,動態規划算法的求解過程由以下三個階段組成:
(1)分析最優解結構:將原問題分解為若干個子問題,每個子問題對應一個決策階段,根據子問題找出最優解的性質,並刻畫其結構特征;
(2)建立遞歸關系:根據子問題之間的重疊關系找到子問題滿足的遞推關系式(即動態規划函數),遞歸地定義最優值,這是算法關鍵;
(3)計算最優值:設計表格,以自底向上的方式計算各個子問題的解並填表,得到最優值(目標函數極值),實現動態規划過程。
如果要求出具體的最優解,通常在動態規划過程中記錄必要的信息,再根據最優決策序列構造最優解。
4.算法比較
| 名稱 | 描述 | 是否填表 | 遞歸方式 | 適用范圍 |
| 動態規划算法 | 略 | 填表 | 自底向上 | 當該問題的所有子問題都需要求解一次時使用 |
| 備忘錄方法 | 動態規划算法的變形 | 填表 | 自頂向下 | 當子問題空間的部分子問題不必求解時使用 |
| 直接遞歸方法 | 直接或間接調用自身 | 不填表 | 自頂向下 |
二、例題總結
1.矩陣連乘問題
【問題】給定n個矩陣{A1,A2, ... ,An},其中Ai與Ai+1是可乘的,考察這n個矩陣的連乘積A1A2...An。
【算法設計】
(1)分析最優解結構
考察A[1:n]的最優計算次序:設這個計算次序在Ak和Ak+1之間將矩陣連斷開,依此次序,總計算量 = A[1:k]的計算量 + A[k+1:n]的計算量 + A[1:k]和A[k+1:n]相乘的計算量。
該問題的一個關鍵特征是:計算A[1:n]的最優次序所包含的計算子矩陣鏈A[a:k]和A[k+1:n]的次序也是最優的,這種性質稱為最優子結構性質。
(2)建立遞歸關系
設計算A[i:j]的最少數乘次數為m[i][j],即原問題的最優值為m[1][n],遞歸定義如下:

將對應m[i][j]的斷開位置k記為s[i][j],在計算出最優值m[i][j]后,可遞歸地由s[i][j]構造出相應的最優解。
(3)計算最優值
動態規划算法解此問題,可依據其遞歸式以自底向上的方式進行計算,在計算過程中保留已解決的子問題答案,方便后邊查找,避免大量重復計算。
【算法實現】
下面給出動態規划算法MatrixChain,輸入參數{p0,p1,...,pn}存儲於數組p中,除了輸出最優值數組m,還給出記錄最優斷開位置的數組s。 
算法分析:

2.最長公共子序列
【問題】給定兩個序列X和Y,當序列Z既是X的子序列又是Y的子序列時,稱z是X和Y的公共子序列。最長公共子序列問題:給定兩個序列X={x1,x2,...,xm}和Y={y1,y2,...,yn},找出X和Y的最長公共子序列。
【算法設計】
(1)分析最優解結構

兩個序列的最長公共子序列包含了這兩個序列前綴的最長公共子序列,因此,該問題具有最優子結構性質。
(2)建立遞歸關系


(3)計算最優值
由於所考慮的子問題空間中,共θ(mn)個不同的子問題,因此用動態規划算法自底向上計算最優值能提高算法效率。
【算法實現】設序列X存儲在數組x[m]中,序列Y存儲在數組y[n]中,最長公共子序列存儲在數組z[k]中,二維數組L[m+1][n+1]存儲最長公共子序列的長度,S[m+1][n+1]存儲相應的狀態,算法如下:

【算法分析】
時間復雜度O(mn)
3.最大字段和
【問題描述】給定由n個整數(可正可負)組成的序列a1,a2,...,an,求該序列子段和的最大值。當所有整數均為負整數時定義其最大字段和為0。
【蠻力算法】

【分治算法】
(1)划分:按照平衡子問題原則,將序列划分為長度相同的兩個子序列,則會出現以下三種情況:
①a[1:n]的最大字段和與a[1:n/2]的最大字段和相同;
②a[1:n]的最大字段和與a[n/2+1:n]的最大字段和相同;
③a[1:n]的最大字段和既包含左半段又包含右半段。
(2)求解子問題:對於划分后的情況1和2可遞歸求解,情況3需要分別計算左右兩邊的最大字段和s1和s2,則s1+s2為情況3的最大字段和。
(3)合並:比較三種情況的最大字段和,選擇較大者作為原問題的解。

【動態規划算法】
(1)分析最優子結構
用f(i)表示以第i個數結尾的子數組的最大和,則
當以第i-1個數結尾的所有數字和為負時,f(i)的結果為a[i]本身;
當以第i-1個數結尾的所有數字和為正時,f(i)的結果為f(i-1) + a[i]。
(2)建立遞歸關系

(3)計算最優值,算法實現

4.流水作業調度
【問題描述】n個作業在兩台機器M1和M2組成的流水線上完成加工,先在M1上加工,然后在M2上加工,M1和M2加工作業i的時間分別是ai和bi。流水作業調度問題要求確定這n個作業的最優加工順序,使得從第一個作業在機器M1上開始加工,到最后一個作業在機器M2上加工所需的時間最少。


5.0-1背包問題(*)
【問題描述】

【算法設計】
(1)分析最優解結構
設V(n,C)表示將n個物品裝入容量為C的背包獲得的最大價值,定義一個子問題:
①初始子問題:V(i,0) = V(0,j) = 0;
②原問題的一部分:設V(i,j)表示將前i個物品裝入容量為j的背包獲得的最大價值,在決策xi時,已確定了前i-1個物品的狀態,則問題可分為以下兩種情況;
情況一:如果裝不下當前物品,那么前n個物品的最大價值(最佳組合)與前n-1個物品的是一樣的;(不用給當前物品預留空間)
情況二:如果裝的下當前物品,則有兩種選擇:
選擇1:裝當前物品(先默認給當前物品預留空間,容量只剩下j-wi),背包中物品價值等於把前i-1個物品裝入容量為j-wi的背包的價值加上當前物品價值vi;
選擇2:不裝當前物品,如情況一,然后選取選擇1和選擇2中較大價值,作為當前最大價值(最佳組合)。
(2)建立遞歸關系

(3)計算最優值
如何獲取在最佳組合的情況下,選取了哪幾個物品?
從表的右下角開始回溯,如果發現前n個物品的最佳組合的價值和前n-1個物品的最佳組合價值一樣,則沒裝入,否則裝入了。
由此得到以下函數:


【算法實現】設n個物品的重量存儲在數組w[n]中,價值存儲在數組v[n]中,背包容量為C,數組V[n+1][C+1]存放迭代結果,其中V[i][j]表示前i個物品裝入容量為i的背包中獲得的最大價值,數組x[n]存儲裝入背包的物品,動態規划算法求解0/1背包問題的算法如下:

【算法分析】
第一個for循環:O(n),第二個for循環:O(C),第三個for循環:O(nC),第四個for循環:O(n) ,
因此,KnapSack算法的時間復雜度為O(nC)。
6.最優二叉查找樹
【問題描述】最優二叉查找樹是以n個記錄構成的二叉查找樹中具有最少平均比較次數的二叉查找樹。具有以下性質:存儲在每個結點中的元素x大於其左子樹中任一結點所存儲的元素,小於其右子樹中任一結點所存儲的元素。
【算法設計】
(1)該算法具有最優子結構特征;
(2)定義子問題:設C(1,n)是最優二叉查找樹T(1,n)的平均比較次數,顯然初始子問題是只有一個記錄,則對應的二叉查找樹只有根結點。考慮原問題的部分解,記錄rk為T(i,j)的根結點,則可得到如下動態規划函數:

【算法實現】
設n個字符的查找概率存儲在數組p[n]中,為避免在函數之間傳遞參數,將矩陣C[n+1][n+1]和R[n+1][n+1]設為全局變量,動態規划算法求解最優二叉查找樹算法如下:

【算法分析】
算法OptimalBST的基本語句顯然是三層for循環中最內層的條件語句,因此該算法的時間復雜度是O(n3)。
