動態規劃(dynamic programming,簡稱dp)與分治方法相比而言:算法
相似點在於都是經過組合子問題的解來求解原問題。
不一樣點在於分治方法將問題劃分爲互不相交的子問題,遞歸的求解子問題;而動態規劃在於子問題重疊的狀況,不一樣子問題的解是遞歸進行的,反覆的求解公共子問題。數組
動態規劃的主題在於對每一個子問題只求解一次並做記錄。
動態規劃的應用場景在於求解最優化問題(optimization problem)。函數
動態規劃算法步驟:優化
輸入源:鋼條長度n
以及每一個尺寸的鋼條單價pi
目標:價格rn
最大spa
先考慮當兩截(不切屬於特殊的兩截)最優切割收益:
rn = max(pn, r1 + rn-1 + r2 + rn-2, ..., rn-1 + r1)code
上式能夠用反證法證實確實在此時取到最大值(略)。blog
切割兩截後的兩根鋼條組合知足最優子結構(optional substructure)性質:問題得最優解由相關子問題的最優解組合而成,而這些子問題能夠獨立求解。排序
這種思路殊途同歸的變種是,假設從左邊(右邊也隨意)割下長度爲i的一段,只對n-i的一段繼續進行切割(遞歸),左邊不用進行切割。這樣的好處是很明顯的,在一層級上只作一個方向遞歸調用,粗略來講能夠減小一半次數的函數調用。
問題能夠如此描述:遞歸
第一段長度爲n,收益爲pn,剩餘部分長度爲0(左邊,不用切割),對應的收益爲r0,能夠獲得新的公式: rn = max(pi + rn-i) (1 <= i <= n)圖片
僞代碼以下:
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
上述算法效率在輸入規模大的時候就會變成渣渣,緣由很簡單,計算機在不斷重複計算一個已經計算過的問題。
主題是空間換時間,時空權衡(time-memory trade-off)。
自頂向下僞代碼:
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]
擴展版本,保存最優解的切割方法
EXTENDED-BOTTOM-UP-CUT-ROD(p, n) let r[0..n] and s[0..n] be new array r[0] = 0 for j = 1 to n q = -∞ for i = 1 to j if q < p[i] + r[j-i] q = p[i] + r[j-i] s[j] = i r[j] = q return r and s PRINT-CUT-ROD-SOLUTION(p, n) (r, s) = EXTENDED-BOTTOM-UP-CUT-ROD(p, n) while n > n print s[n] n = n - s[n]
注:自底向上是須要排序,由於本慄中,價格跟尺寸長短成正比,故已經爲非遞減序列~
func extendedBottomUpCutRod(priceList: [Int], rotLength: Int) -> (result: [Int], solution: [Int]) { var result = Array (count: rotLength + 1 , repeatedValue: 0 ) var solution = Array (count: rotLength + 1 , repeatedValue: 0 ) for var j = 1; j <= rotLength; ++j { var optNext = -10000 for var i = 1; i <= j; i++ { if optNext < priceList[i] + result[j - i] { optNext = priceList[i] + result[j - i] solution[j] = i } } result[j] = optNext } return (result, solution) } func printCutRodSolution(priceList: [Int], var rotLength: Int) { var result: [Int] var solution: [Int] (result, solution) = extendedBottomUpCutRod(priceList, rotLength) while rotLength > 0 { println(solution[rotLength]) rotLength = rotLength - solution[rotLength] } } var priceArray: [Int] = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30] printCutRodSolution(priceArray, 4)
輸入源:數組X, 數組Y
目標:求兩數組最長公共交集子數組
遇到最長公共子序列問題(longest-common-subsequence problem)時,能夠堅決果斷的使用dp來解決LCS問題。
DP問題步驟1:刻畫一個最優解的結構特徵
暴力求解的效率很可怕,假如X相對Y較短,長度爲m,那麼其子序列有2^m個。也就是說解的可能有2^m個。輸入數組規模大了天然就呵呵了。。
首先看LCS問題是否具備最優子結構性質。LCS的子問題(小規模)天然考慮是兩個序列的前綴【注:爲嘛不是後綴呢?由於前綴都不一樣哪來的必要考慮後綴。。。】。
各類反證法是能夠證實LCS問題是具備最優子結構,證實略了。。不是搞數學。。
LCS最優子結構的特徵以下:
若是X = <x1, x2, ..., xm>,Y = <y1, y2, ..., yn>是兩個序列, Z = <z1, z2, ..., zk>是X與Y的任意LCS,則
不斷的1,2,3能夠得到一個解(遞歸解)
DP問題步驟2:求遞歸解
根據LCS最優子結構的性質1,2,3能夠有以下公式(遞歸解):
DP問題步驟3:自底向上計算最優解
整一個二維數組c[0..m, 0..n]來保存c[i, j]的解。按行主次序(即外圍for循環爲1到m),還須要一個輔助列表b[1..m, 1..n]來幫住構造最優解。b[i, j]存的枚舉型能夠記錄在計算c[i, j]時的子問題最優解。c[m, n]保存了X和Y的LCS長度。
DP問題步驟4:給出最優解 上僞代碼
LCS-LENGTH(X, Y) m = X.length n = Y.length let b[1..m, 1..n] and c[0..m, 0..n] be new tables for i = 1 to m // 不從0開始的緣由,寫一個二維矩陣就懂了 c[i, 0] = 0 for j = 0 to n c[0, j] = 0 for i = 1 to m for j = 1 to n if xi == yi c[i, j] = c[i-1, j-1] + 1 b[i, j] = left-top // left-top枚舉值 elseif c[i-1, j] >= c[i, j-1] c[i, j] = c[i-1, j] b[i, j] = top // top枚舉值 else c[i, j] = c[i, j-1] b[i, j] = left // left枚舉值 return c and b
經過表b咱們能夠快速的查閱追蹤出X和Y的LCS。打印LCS僞代碼以下:
PRINT-LCS(b, X, i, j) // 公共子序列X,Y皆可 if i == 0 or j == 0 return if b[i, j] == left-top PRINT-LCS(b, X, i-1, j-1) print xi elseif b[i, j] = top PRINT-LCS(b, X, i-1, j) else PRINT-LCS(b, X, i, j-1)
實際上咱們能夠在常數時間內判斷出c[i, j]與c[i-1, j-1],c[i-1, j],c[i, j-1]三者之間的關係。故能夠徹底不須要輔助表b。
// c[i, j]與c[i-1, j-1],c[i-1, j],c[i, j-1]三者之間的關係枚舉 enum LCSDirection: Int { case left_top = 0 case top = 1 case left = 2 } // 二維數組 struct Matrix { let rows: Int, columns: Int var grid: [Int] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(count: rows * columns, repeatedValue: -10000) } func indexIsValidForRow(row: Int, column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int, column: Int) -> Int { get { assert(indexIsValidForRow(row, column: column), "Index out of range") return grid[(row * columns) + column] } set { assert(indexIsValidForRow(row, column: column), "Index out of range") grid[(row * columns) + column] = newValue } } } func LCSLength(var rowArray: [Int], var columnArray: [Int]) -> (b: Matrix, c: Matrix){ var m = rowArray.count var n = columnArray.count var b: Matrix = Matrix(rows: m+1, columns: n+1) var c: Matrix = Matrix(rows: m+1, columns: n+1) for var i = 1; i <= m; i++ { c[i, 0] = 0 } for var j = 0; j <= n; j++ { c[0, j] = 0 } for var i = 1; i <= m; i++ { for var j = 1; j <= n; j++ { if rowArray[i-1] == columnArray[j-1] { c[i, j] = c[i-1, j-1] + 1 b[i, j] = LCSDirection.left_top.rawValue } else if c[i-1, j] >= c[i, j-1] { c[i, j] = c[i-1, j] b[i, j] = LCSDirection.top.rawValue } else { c[i, j] = c[i, j-1] b[i, j] = LCSDirection.left.rawValue } } } return (b, c) } func printLCS(b: Matrix, rowArray: [Int], rowIndex: Int, columnIndex: Int) { if rowIndex == 0 || columnIndex == 0 { return } if b[rowIndex, columnIndex] == LCSDirection.left_top.rawValue { printLCS(b, rowArray, rowIndex-1, columnIndex-1) println(rowArray[rowIndex-1]) } else if b[rowIndex, columnIndex] == LCSDirection.top.rawValue { printLCS(b, rowArray, rowIndex-1, columnIndex) } else { printLCS(b, rowArray, rowIndex, columnIndex-1) } } var A: [Int] = [1, 2, 3, 2, 4, 1, 2] var B: [Int] = [2, 4, 3, 1, 2, 1] var bRecord: Matrix var cCommon: Matrix (bRecord, cCommon) = LCSLength(A, B) printLCS(bRecord, A, A.count, B.count)
續