動態規劃:Swift實現

中心思想

動態規劃(dynamic programming,簡稱dp)與分治方法相比而言:算法

相似點在於都是經過組合子問題的解來求解原問題
不一樣點在於分治方法將問題劃分爲互不相交的子問題,遞歸的求解子問題;而動態規劃在於子問題重疊的狀況,不一樣子問題的解是遞歸進行的,反覆的求解公共子問題。數組

動態規劃的主題在於對每一個子問題只求解一次並做記錄。
動態規劃的應用場景在於求解最優化問題(optimization problem)函數

動態規劃算法步驟:優化

  1. 刻畫一個最優解的結構特徵
  2. 遞歸地定義最優解
  3. 計算最優解的值,這裏通常用自底向上的方法
  4. 利用計算出的信息構造一個最優解

舉個栗子

栗子1)鋼條切割

輸入源:鋼條長度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)。

  • 帶備忘的自頂向下法(top-down with memoization)。天然遞歸(就是正常人的想法,大問題變小問題)編寫。每一個過程保存一個子問題的解。在解的過程判斷此子問題是否已經解過。
  • 自底向上法(bottom-up method)。將子問題按規模排序,按由小至大的順序進行求解。當解某個子問題所涉及更小的問題都已經解答完畢,能夠直接使用(不用作判斷是否是解答過了,省略了if判斷)。

自頂向下僞代碼:

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]

Swift實現

注:自底向上是須要排序,由於本慄中,價格跟尺寸長短成正比,故已經爲非遞減序列~

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)

栗子2)最長公共子序列

輸入源:數組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. 若是xm = yn, 那麼zk = xm = yn而且Zk-1是Xm-1和Yn-1的一個LCS。
  2. 若是xm != yn, 那麼zk != xm意味着Z是Xm-1和Y的一個LCS。【注:由於LCS中一定不包含xm,問題規模縮減,X序列成員剔除xm,下同】
  3. 若是xm != yn, 那麼zk != yn意味着Z是X和Yn-1的一個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。

Swift實現

// 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)

栗子3)最優二叉搜索樹

相關文章
相關標籤/搜索