動態規劃被廣泛求解組合最優化問題,使用這種算法,不是遞歸調用自身,但是問題的基礎解通常使用遞歸函數的形式來說明。採取自底向上的方式遞推求值,並把中間結果存儲起來以便以後用來計算所需要求的解(即每個小問題不獨立)。
當子問題共享子問題時,分而治之的算法所做的工作比需要的多,重複地解決常見的子問題。動態規劃算法只解決每個子問題一次,然後將其答案保存在一個表中,從而避免了每次遇到子問題時重新計算答案的工作。
動態規劃 步驟
1. 描述最優解的結構
2. 遞歸地定義最優解的值
3.以自底向上的方式計算最優解的值
4. 根據計算出的信息構造最優解(如果只需要該值,可以省略此步驟)
最長公共子序列問題
給定兩個長度爲n和m的字符串A和B,確定A和B中最長公共子序列的長度。
如A=abcabc,B=bbccbc,bcb同時是A和B長度爲3的子序列,但不是最長的公共子序列,bcbc也是A和B的公共子序列,是最長的公共子序列,長度爲4,沒有更長的了。
蠻力搜索法:列舉A所有的個子序列,對於每一個子序列在時間內來確定它是否也是B的子序列,時間複雜性爲,是指數複雜性。
爲了使用動態規劃技術,首先尋找一個求最長公共子序列長度的遞推公式,令A=a1,a2,...an和B=b1,b2,...bm ,令L[i,j]表示A和B的最長公共子序列的長度。遞推公式:。
算法 LCS
輸入: 字母表上的兩個字符串A和B,長度分別爲n和m
輸出: A和B最長公共子序列的長度
for i <- 0 to n
L[i,0] <- 0
end for
for j <- 0 to m
L[0,j] <- 0
end for
for i <- 1 to n
for j <- 1 to m
if ai=bj then L[i,j] <- L[i-1,j-1]+1
else L[i,j] <- max{L[i,j-1],L[i-1,j]}
end if
end for
end for
return L[n,m]
最長公共子序列問題的最優解能夠在和空間內得到。
算法LCS可以方便地修改成讓它輸出最長公共子序列。
pro LCS
for i <- 0 to n
L[i,0] <- 0
end for
for j <- 0 to m
L[0,j] <- 0
end for
for i <- 1 to n
for j <- 1 to m
if ai=bj then L[i,j] <- L[i-1,j-1]+1, b[i,j] <- ”\”
else
if L[i-1,j] ≥L[i,j-1] then
L[i,j] <- L[i-1,j], b[i,j] <- ”↑”
else
L[i,j]←L[i,j-1], b[i,j]←”↑”
end if
end if
end for
end for
return L[n,m] and b[n,m]
print-LCS(b,A,i,j)
if i=0 or j=0 then return
if b[i,j]= ”\” then
print-LCS(b,A,i-1,j-1)
print ai
else
if b[i,j]= ”” then print-LCS(b,A,i-1,j)
else print-LCS(b,A,i,j-1)
end if
矩陣鏈相乘
舉一個簡單的例子:用標準的矩陣乘法計算M1,M2,M3三個矩陣的乘積M1M2M3,這三個矩陣的維數分別是2x10,10x2,2x10,(M1M2)M3的乘法次數爲2x10x2+2x2x10=80,而M1(M2M3)乘法次數爲10x2x10+2x10x10=400,執行乘法是前面的5倍。
一般來說,n個矩陣相乘的代價是M1M2…Mn取決於n-1乘法的執行順序。蠻力法:嘗試計算每一個可能順序的標量乘法的數目。時間複雜度爲
動態規劃:由於對於每個矩陣列數一定等於矩陣的行數,因此指定每個矩陣的行數和最右邊矩陣的列數就足夠了。假設有n+1維數,這裏分別是矩陣的行數和列數,用來記的乘積,耗費的乘法次數記爲。假設k使執行矩陣乘法所需要的數量乘法次數最小,則存在以下遞推式:,特別地,,避免大規模的重複遞歸調用,採用自底向上的遞推方法。
在這個圖中,對角線d用乘出各種d+1個相繼矩陣鏈的最小耗費填滿。由於不包含數量乘法,對角線0用0來填充,對角線1由兩個連續的矩陣相乘的耗費來填充,那麼餘下的對角巷根據上面所說的公式和先前存儲在表中的值來填。
C[2,5]的值爲以下三個耗費的最小值:
1)計算的耗費(這裏是0)加上計算的耗費,再加上乘以的耗費;
2)計算的耗費加上計算的耗費,再加上乘以的耗費;
3)計算的耗費加上計算的耗費,再加上乘以的耗費;
MATCHAIN
輸入: n個矩陣的鏈的維數對應於正整數數組r[1..n+1], 其中r[1..n]是n個矩陣的行數,r[n+1]是Mn的列數
輸出: n個矩陣相乘的數量乘法的最小次數
for i←1 to n {填充對角線d0}
C[i,i]←0
end for
for d←1 to n-1 {填充對角線d1 到 dn-1}
for i←1 to n-d {填充對角線di}
j←i+d
comment: 下列三行計算C[i,j]
C[i,j]←∞
for k←i+1 to j
C[i,j]←min{C[i,j],C[i,k-1]+C[k,j]+r[i]r[k]r[j+1]}
end for
end for
end for
return C[1,n]
算法的時間複雜性是,算法的所需要的工作空間取決於所需要的三角數組的大小,也就是。
所有點對的最短路徑問題
設G=(V, E)是一個有向圖,其中的每條邊(i,j)有一個非負的長度l[i,j],如果從頂點i到頂點j沒有邊,則l[i,j]=∞。問題是要找出從每個頂點到其他所有頂點的距離,這裏,從頂點x到頂點y的距離是指從x到的最短路徑的長度。爲了簡便,我們假設V= {1,2,.,n},設i和j是V中兩個不同的頂點,定義是從i到j,並且不經過{k+1.k+2.-.n}中任何頂點的最短路徑的長度。遞歸關係如下:
算法FLOYD
Input: nxn 維矩陣 l[1..n,1..n]以便對於有向圖G=({1,2,...,n},E)中的邊(i,j)的長度爲l[i,j]
Output: 矩陣D,使得D[i,j]=i到j的距離
D←l {將輸入矩陣l複製到D}
for k←1 to n
for i←1 to n
for j←1 to n
D[i,j]=min{D[i,j],D[i,k]+D[k,j]}
end for
end for
end for
算法的運行時間爲,空間複雜度。
揹包問題
揹包問題可以定義如下,設U={}是個準備放入容量爲C的揹包中的n項物品的集合。對於1≤j≤n,令和分別爲第j項物品的體積和價值,這裏,C,,和j都是正整數。我們要解決的問題是用U中的一些物品來裝滿揹包,這些物品的總體積不超過C,然而要使它們的總價值最大。不失一般性、假設每項物品的體積不大於C。更形式地,給出有n項物品的U,我們要找出一個子集合.使得
在約束條件下最大。
設V[i,j]用來表示從前i項中取出來的裝入體積爲j的揹包的物品的最大價值。遞推式如下:
KNAPSACK
Input: 物品集合U={u1...un} 體積分別爲s1,...,sn ,價值分別爲v1,...,vn ,容量爲C的揹包
Output: 最大總價值
for i←0 to n
V[i,0]←0
end for
for j←0 to C
V[0,j]←0
end for
for i←1 to n
for j←1 to C
V[i,j]←V[i-1,j]
if si≤j then V[i,j]←max{V[i,j],V[i-1,j-si]+vi}
end for
end for
return V[n,C]
揹包問題的最優解能夠在的時間內和的空間內得到。算法被認爲對於輸入是指數的。