【算法學習筆記八】動態規劃

動態規劃被廣泛求解組合最優化問題,使用這種算法,不是遞歸調用自身,但是問題的基礎解通常使用遞歸函數的形式來說明。採取自底向上的方式遞推求值,並把中間結果存儲起來以便以後用來計算所需要求的解(即每個小問題不獨立)。

當子問題共享子問題時,分而治之的算法所做的工作比需要的多,重複地解決常見的子問題。動態規劃算法只解決每個子問題一次,然後將其答案保存在一個表中,從而避免了每次遇到子問題時重新計算答案的工作。

動態規劃 步驟

1. 描述最優解的結構

2. 遞歸地定義最優解的值

3.以自底向上的方式計算最優解的值

4. 根據計算出的信息構造最優解(如果只需要該值,可以省略此步驟)

最長公共子序列問題

給定兩個長度爲n和m的字符串A和B,確定A和B中最長公共子序列的長度。

如A=abcabc,B=bbccbc,bcb同時是A和B長度爲3的子序列,但不是最長的公共子序列,bcbc也是A和B的公共子序列,是最長的公共子序列,長度爲4,沒有更長的了。

蠻力搜索法:列舉A所有的2^n個子序列,對於每一個子序列在\Theta (m)時間內來確定它是否也是B的子序列,時間複雜性爲\Theta (m2^n),是指數複雜性。

爲了使用動態規劃技術,首先尋找一個求最長公共子序列長度的遞推公式,令A=a1,a2,...an和B=b1,b2,...bm ,令L[i,j]表示A和B的最長公共子序列的長度。遞推公式:L[i,j]=\left\{\begin{matrix} 0 & i=0 or j=0\\ L[i-1,j-1] +1&i>0,j>0,a_i=b_j \\ max{L[i,j-1],L[i-1,j]}& i>0,j>0,a_i\neq b_j \end{matrix}\right.

從左往右,從上往下填寫,相同的就在斜左上角數字+1
算法 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]

最長公共子序列問題的最優解能夠在\Theta (nm)\Theta (min\{m,n\})空間內得到。

算法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乘法的執行順序。蠻力法:嘗試計算每一個可能順序的標量乘法的數目。時間複雜度爲\Omega (4^n/\sqrt{n})

動態規劃:由於對於每個矩陣M_i列數一定等於矩陣M_{i+1}的行數,因此指定每個矩陣的行數和最右邊矩陣M_n的列數就足夠了。假設有n+1維數r_1,r_2,...,r_{n+1},這裏r_i,r_{i+1}分別是矩陣M_n的行數和列數,用M_{i,j}來記M_iM_{i+1}...M_j的乘積,耗費的乘法次數記爲C[i,j]。假設k使執行矩陣乘法M_iM_{i+1}...M_j所需要的數量乘法次數最小,則存在以下遞推式:C[i,j]=\min_{i<k\leq j}\{C[i,k-1]+C[k,j]+r_ir_kr_{j+1}\},特別地,C[1,n]=\min_{1<k\leq n}\{C[1,k-1]+C[k,n]+r_1r_kr_{n+1}\},避免大規模的重複遞歸調用,採用自底向上的遞推方法。

在這個圖中,對角線d用乘出各種d+1個相繼矩陣鏈的最小耗費填滿。由於不包含數量乘法,對角線0用0來填充,對角線1由兩個連續的矩陣相乘的耗費來填充,那麼餘下的對角巷根據上面所說的公式和先前存儲在表中的值來填。

C[2,5]的值爲以下三個耗費的最小值:

1)計算M_{2,2}的耗費(這裏是0)加上計算M_{3,5}的耗費,再加上M_{2,2}乘以M_{3,5}的耗費;

2)計算M_{2,3}的耗費加上計算M_{4,5}的耗費,再加上M_{2,3}乘以M_{4,5}的耗費;

3)計算M_{2,4}的耗費加上計算M_{5,5}的耗費,再加上M_{2,4}乘以M_{5,5}的耗費;

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]

算法的時間複雜性是\Theta (n^3),算法的所需要的工作空間取決於所需要的三角數組的大小,也就是\Theta (n^2)

所有點對的最短路徑問題

設G=(V, E)是一個有向圖,其中的每條邊(i,j)有一個非負的長度l[i,j],如果從頂點i到頂點j沒有邊,則l[i,j]=∞。問題是要找出從每個頂點到其他所有頂點的距離,這裏,從頂點x到頂點y的距離是指從x到的最短路徑的長度。爲了簡便,我們假設V= {1,2,.,n},設i和j是V中兩個不同的頂點,定義d_k[i,j]是從i到j,並且不經過{k+1.k+2.-.n}中任何頂點的最短路徑的長度。遞歸關係如下:d_k[i.j]=\left\{\begin{matrix} l[i,j] &k=0 \\ min\{d_{k-1}[i,j],d_{k-1}[i,k],d_{k-1}[k,j]\}& 1\leq k\leq n \end{matrix}\right.

算法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

算法的運行時間爲\Theta (n^3),空間複雜度\Theta (n^2)

揹包問題

揹包問題可以定義如下,設U={u_1,u_2,...,u_n}是個準備放入容量爲C的揹包中的n項物品的集合。對於1≤j≤n,令s_jv_j分別爲第j項物品的體積和價值,這裏,C,s_j,v_j和j都是正整數。我們要解決的問題是用U中的一些物品來裝滿揹包,這些物品的總體積不超過C,然而要使它們的總價值最大。不失一般性、假設每項物品的體積不大於C。更形式地,給出有n項物品的U,我們要找出一個子集合S\subseteq U.使得\sum_{u_i\in S}v_i

在約束條件\sum_{u_i\in S}s_i\leq C下最大。

設V[i,j]用來表示從前i項\{u_1,u_2,...,u_i\}中取出來的裝入體積爲j的揹包的物品的最大價值。遞推式如下:V[i,j]=\left\{\begin{matrix} 0 & i=0 or j=0\\ V[i-1,j]& j<s_i\\ max\{V[i-1,j],V[i-1,j-s_i]+v_i\}& i>0 and j\geq s_i \end{matrix}\right.

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]

揹包問題的最優解能夠在\Theta (nC)的時間內和\Theta (C)的空間內得到。算法被認爲對於輸入是指數的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章