1. 程式人生 > >動態規劃演算法學習總結(帶案例)

動態規劃演算法學習總結(帶案例)

【動規演算法學習總結】

首先,遇到動態規劃問題要找到三個重要元素: 1.最優子結構 2.邊界 3.狀態轉移方程

【最優子結構】 通俗來說,就是具有規律性的結果的獲取方式。 如上樓梯問題中, 上第10層的情況種類 = 上第8、9層的情況種類之和。第9層的結果又為第7、8層結果之和。 又如擊鼓傳花問題中。 傳m次傳給1的情況種類 = 傳m-1次傳給n、2的情況種類之和。 傳m-1次傳給2的情況又為傳m-2次傳給1、3情況之和。 (詳細內容見另外兩篇部落格)

【邊界】 任何問題都要有邊界,否則我們就要無休止的迴圈下去了。 在得出最優子結構後,在將結果遞迴至某一處時(一般在資料集邊緣),可以直接得出其結果。而這個就是邊界。 如上樓梯問題(初始位置為第0階樓梯),其邊界為:上第1階時,結果為1。 上第二階時,結果為2({1,1},{2}})。 又如擊鼓傳花問題,傳1次傳給2的結果為1,傳1次傳給n的結果為1。

【狀態轉移方程】 在得出最優子結構後,就可以對應歸納出狀態轉移方程。 如上樓梯問題,F(n) = F(n-1) + F(n-2) 但是我個人覺得,如果時間緊迫(如筆試題),也可以放棄得到其最優子結構和最優的狀態轉移方程。 而是採用判斷的形式,得出對應的結果。 如在擊鼓傳花問題當中,我將其分為三中情況:

 * dp[m][1]等於第m-1次傳遞到小賽左右兩邊的人的情況之和,即 dp[m][1] = dp[m-1][n] + dp[m-1][2]
 * i代表傳遞i次,j代表傳遞給第j個人,則
 * j == 1時:
 *   dp[i][j] = dp[i-1][n] + dp[i-1
][j+1]; * j == n時: * dp[i][j] = dp[i-1][j-1] + dp[i-1][1]; * 正常情況: * dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1];

而對應的程式碼,同樣是判斷

//dynamic planing
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(j == 1){
                    dp[i][j] = dp[i-1
][n] + dp[i-1][j+1]; }else if(j == n){ dp[i][j] = dp[i-1][j-1] + dp[i-1][1]; }else{ dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]; } } } System.out.println(dp[m][1]);

其次,在得到三個元素後,需要將思路逆轉過來 為什麼說逆轉過來呢? 因為剛才我們得到的最優子結構,是採用自頂向下的思路推理分析的。而我們所採用的動態規劃解法,則自底而上,從我們找到的邊界值開始,從底層,採用for迴圈,一部一部得到上方的值,最終得到我們最頂端的值。 舉例說明: 上樓梯問題,知道1階和2階的結果,我們就可以得到3階的結果。 知道2階和3階的結果,我們就可以得到4階的結果。 …….最終,我們得到了10階的結果。 具體程式碼如下:

static int step(int n){
        if (n == 1){return 0; }
        if (n == 2){return 1; }
        if (n == 3){return 2; }
        int a = 1, b = 2, step = 0;
        for (int i = 4; i <= n; i++){
            step = a + b;
            a = b;
            b = step;
        }
        return step;
    }

擊鼓傳花問題,我們知道dp[1][2]=1,dp[1][n]=1,其餘dp[1][i]=0。 我們可以根據dp[1][2]=1,dp[1][n] = 1 ,得到dp[2][1] = 2; 根據dp[1][1]=0,dp[1][3] = 0,得到dp[2][2] = 0; 根據dp[1][2] = 1, dp[1][4] = 0,得到 dp[2][3] = 1; …… 最終得到dp[m][1]的結果。

這就是自己學習動態規劃的一些心得。

【補充內容】 其實,在得到【最優子結構】,【邊界】,【狀態轉移方程】後最直接的解法是: 採用遞迴演算法(時間複雜度高,空間複雜度高) 以及備忘錄演算法(時間複雜度低,空間複雜度較高) 最優的才是動態規劃演算法

簡要介紹一下這裡提到的兩種演算法: 【遞迴演算法】 以上樓梯問題為例:

int step(int n){
    if(n == 0)
        return 0;
    if(n == 1)
        return 1;
    if(n == 2)
        return 2;
    return step(n-1) + step(n-2);
}

遞迴最為簡單,直接把邊界值,狀態轉移方程帶入求解即可。 但是效率極低,因為其它重複計算了很多內容。(一旦n值極大,程式馬上崩潰)。

【備忘錄演算法】 思路:採用遞迴演算法,但是增加一個緩衝池,每次取值時, 判斷緩衝池中是否有?取出:計算得出並放入緩衝池 虛擬碼如下:

Map<Integer,Integer> cache = new HashMap<>();
int step(int n){
    if(n == 0)
        return 0;
    if(n == 1)
        return 1;
    if(n == 2)
        return 2;
    if(cache.contains(n){
        return map.get(n)
    }else{
        int res = step(n-1) + step(n-2);
        cache.put(n,res);
        return res;
    }
}