1. 程式人生 > >如何從動態規劃的角度去看問題

如何從動態規劃的角度去看問題

演算法導論(MIT 6.006 第19講)

動態規劃的核心處理流程是什麼?

1: 定義子問題

計運算元問題的數量

2:猜測(嘗試所有可能的方式,獲取最好的)

計算選擇的數量

3: 關聯所有的子問題

計算單個子問題所需要處理的時間

4: 重用子問題結果並記下新的結果,或者使用DP的bottom-up方式(需要注意沒有環)

計算總耗時

5: 解決原有的問題

對結果進行組合等等會劃掉部分額外的時間

總的來說就是:嘗試所有可能的子問題的結果,將最好的可能子結果儲存下來,然後重複利用已經解決的子問題,遞迴去解決所有的問題(猜測+記憶+遞迴)

它消耗的時間為: 子問題的數量 * 每個子問題處理所需要時間

例1:斐波那契數列

使用遞迴的方式求斐波那契數列

fib(n):
    if n<=2:f=1;
    else: f= fib(n-1)+fib(n-2);
    return f;

這種方式的執行時間為 θ ( 2

n / 2 ),空間為O(1)

T(n)=T(n-1)+T(n-2)+O(1) 2T(n-2)=

θ ( 2 n / 2 )

Memoized DP 演算法求斐波那契數列

fib(n):
    memo={}
    if n in memo:return memo[n];
    if n<=2:f=1;
    else f=fib(n-1)+fib(n-2);
    memo[n]=f;
    return f;

這種方式的執行時間為 θ ( n ) ,空間為O(n)

memo的存在使得實際產生呼叫的只有 fib(1) …. fib(n),共n次,區域的直接從memo中獲取,使用常量的時間

Bottom-up DP演算法求斐波那契數列

fib(n):
    fib={}
    for i in range(1,n+1):
        if i<=2: f=1
        else: f=fib[i-1]+fib[i-2]
        fib[i]=f
    return fib[n]

這種方式的執行時間為 θ ( n ) ,空間可以只用O(1)

它可以看做是一種拓撲排序(針對DAG),對於使用空間其實只需要記住前兩個即可
這裡寫圖片描述

例2:最短路徑

要求s到t的最短路徑,那麼必定會經過與t相鄰的一條邊,如圖示的u,那麼最短路徑 δ ( s , t ) = m i n ( u , t ) E ( δ ( s , u ) + w ( u , t ) )

δ ( s , u ) 就是需要遞迴呼叫處理的部分

對於DAG: δ ( s , t ) 每個子問題的處理時間為 indegree(t)+O(1)

indegree(t):入度數也就是類似(u,t)邊的數量,需要去遍歷所有t的入邊

O(1):判斷是不是有入邊

總共的執行時間為

v V ( i n d e g r e e ( v ) + O ( 1 ) ) = O ( E + V )
這裡寫圖片描述

當圖中有環的時候求最短路徑產生的問題

要求s到v的最短路徑 δ ( s , v ) ,首選需要去求 δ ( s , a ) ,然後是 δ ( s , b ) ,到b節點有兩條路徑: δ ( s , s ) δ ( s , v ) ,此時去memo中查 δ ( s , v ) 是不存在的,又會這回查詢,導致了一個死迴圈
這裡寫圖片描述

解決圖中有環的時候求最短路徑的問題

方式是去環,將原來的圖一層一層的展開。
假設從s到v需要的路徑為k步,那麼可以得到 δ k ( s , v ) = m i n ( b , v ) E ( δ k 1 ( s , b ) + w ( b , v ) ) ,當k遞減到0的時候,其實也就是從s到s本身
這裡寫圖片描述
所需要的展開層數為:|V|-1

對於求最短路徑來講,最長不能超過|V|-1,否則就是成環,會造成迴圈的情況(從0開始的計數),這就是為什麼Bellman-Ford的外層迴圈是 |V|-1

每層的節點數為所有的節點。那麼總共的節點數為|V’|=|V|(|V|-1)+1=O( V 2 ),邊數是|E’|=|E|(|V|-2)+1=O(VE)。轉換後的圖是DAG圖,那麼實際上的時間為O(V’+E’)=O(VE)。這也就是從動態規劃的角度去看Bellman-Ford演算法

節點的數目是1個源點,邊的數目是每多一層實際上就多了加了一遍所有的邊。

斐波那契數列與最短路徑使用動態規劃處理步驟的對比

例子 斐波那契數列 最短路徑
1:定義子問題 F k 其中 1 k n δ k ( s , v ) 其中 v V , 0 k < V
子問題數量 n V 2
2:猜測 什麼都沒做,完全是定義 節點v的入邊(如果存在的話)
選擇的數量 1 v的入邊數+1
3:關聯所有的子問題 F k = F k 1 + F k 2