1. 程式人生 > >動態規劃初探及什麽是無後效性? (轉)

動態規劃初探及什麽是無後效性? (轉)

電腦 整理 tar aux 小白 zax ima 個數 研究

轉自:http://blog.csdn.net/qq_30137611/article/details/77655707

初探動態規化

剛學動態規劃,或多或少都有一些困惑。今天我們來看看什麽是動態規劃,以及他的應用。
學過分治方法的人都知道,分治方法是通過組合子問題來求解原問題,而動態規劃與分治方法相似,都是通過組合子問題的解來求解原問題,只不過動態規劃可以解決子問題重疊的情況,即不同的子問題有公共的子子問題。
說了這麽多的比較苦澀的話,只是為了回頭再看,我們通過一個例子來具體說明一下:

鋼條切割問題

小王剛來到一家公司,他的頂頭boss買了一條長度為10的鋼條,boss讓小王將其切割為短鋼條,使得這條鋼條的價值最大,小王應該如何做?我們假設切割工序本身沒有成本支出。
已知鋼條的價格表如下:

長度 i 1 2 3 4 5 6 7 8 9 10
價格P(i) 1 5 8 9 10 17 17 20 24 30

小王是一個非常聰明的人,立刻拿了張紙畫了一下當這根鋼條長度為4的所有切割方案(將問題的規模縮小)
技術分享
小王很快看出了可以獲得的最大收益為5+5=10,他想了想,自己畫圖以及計算最大值的整個流程,整理了一下:
1>先畫出切一刀所有的切割方案:(1+8)、(5+5)、(8+1)一共三種可能,也就是 4 - 1中可能,把這個 4 換成 n(將具體情況換成一般情況),就變成了長度為 n 的鋼條切一刀會有 n -1 中可能,然後在將一刀切過的鋼條再進行切割。
同上,(1+8)這個組合會有 2 中切法,(1+1+5)和(1+5+1)【看圖】,同理,(5+5)會有兩種切法(1+1+5)和(5+1+1),由於(1+1+5)和上面的(1+1+5)重合,所以算一種切法,依次類推。
由於我們對 n-1個切點總是可以選擇切割或不切割,所以長度為 n 的鋼條共有 2^(n-1)中不同的切割方案

不懂的點我

技術分享

2>從這2^(n-1)中方案中選出可以獲得最大收益的一種方案

學過遞歸的小王很快就把上述過程抽象成了一個函數,寫出了以下的數學表達式:
設鋼條長度為n的鋼條可以獲得的最大收益為 r(n) (n>=1)
技術分享
第一個參數P(n)表示不切割對應的方案,其他 n-1個參數對應著另外 n-1中方案(對應上面的一刀切)
為了求解規模為 n 的原問題,我們先求解形式完全一樣,但規模更小的子問題。即當完成首次切割後,我們將兩段鋼條看成兩個獨立的鋼條切割問題來對待。我們通過組合兩個相關子問題的最優解,並在所有可能的兩段切割方案中選取組合收益最大者,構成原問題的最優解我們成鋼條問題滿足最優子結構

性質
編程能力很強的小王拿出筆記本
很快的在電腦上寫下了如下代碼

#include <stdio.h>

int CUT_ROD(int * p ,int n);
int max(int q, int a);

int main(void){
    int i = 0;
    int p[10] = {1,5,8,9,10,17,17,20,24,30};
    printf("請輸入鋼條的長度(正整數):\n");
    scanf("%d",&i);

    int maxEarning = CUT_ROD(p,i); // 切割鋼條
    printf("鋼條長度為 %d 的鋼條所能獲得的最大收益為:%d\n",i,maxEarning);

    return 0;
}
// 切割鋼條
int CUT_ROD(int * p,int n){
    int i;
    if(n < 0){
        printf("您輸入的數據不合法!\n");
        return -1;
    }else if(n == 0){
        return 0;
    }else if(n > 10){
        printf("您輸入的值過大!\n");
        return -1;
    }
    int q = -1;
    for(i = 0; i < n;i++){
        q = max(q,p[i] + CUT_ROD(p,n-1-i));
    }
    return q;
}

int max(int q, int a){
    if(q > a)
        return q;
    return a;
}

沾沾自喜的小王拿著自己的代碼到boss面前,說已經搞定了。boss看了看他的代碼,微微一笑,說,你學過指數爆炸沒,你算算你的程序的時間復雜度是多少,看看還能不能進行優化?小王一聽蒙了,自己這些還沒有想過,自己拿著筆和紙算了好大一會,得出了復雜度為T(n) = 2^n,沒想到自己寫的代碼這麽爛,規模稍微變大就不行了。boss看了看小王,說:你想想你的代碼效率為什麽差?小王想了想,說道:“我的函數CUT-ROD反復地利用相同的參數值對自身進行遞歸調用,它反復求解了相同的子問題了”boss說:“還不錯嘛?知道問題出在哪裏了,那你怎樣解決呢?”小王搖了搖頭,boss說:“你應該聽過動態規劃吧,你可以用數組把你對子問題求解的值存起來,後面如果要求解相同的子問題,直接用之前的值就可以了,動態規劃方法是付出額外的內存空間來節省計算時間,是典型的時空權衡”,聽到這裏小王暗自佩服眼前的boss,姜還是老的辣呀。
boss說完拿起小王的筆記本,寫下了如下代碼:

// 帶備忘的自頂向下法 求解最優鋼條切割問題
#include <stdio.h>

int MEMOIZED_CUT_ROD(int * p,int n);
int MEMOIZED_CUT_ROD_AUX(int * p, int n, int * r);
int max(int q,int s);

int main(void){
    int n;
    int p[11]={-1,1,5,8,9,10,17,17,20,24,30};
    printf("請輸入鋼條的長度(正整數 < 10):\n");
    scanf("%d",&n);
    if(n < 0 || n >10){
        printf("您輸入的值有誤!");
    }else{
        int r = MEMOIZED_CUT_ROD(p,n);
        printf("長度為%d的鋼條所能獲得的最大收益為:%d\n",n,r);
    }
    return 0;
}

int MEMOIZED_CUT_ROD(int * p, int n){
    int r[20];
    for(int i = 0; i <= n; i++){
        r[i] = -1; 
    }
    return MEMOIZED_CUT_ROD_AUX(p,n,r); 
}

int MEMOIZED_CUT_ROD_AUX(int * p, int n, int * r){
    if(r[n] >= 0){
        return r[n];
    }
    if(n == 0){
        return 0;
    }else{
        int q = -1;
        for(int i = 1; i <= n; i++){// 切割鋼條, 大綱有 n 中方案
            q = max(q,p[i] + MEMOIZED_CUT_ROD_AUX(p,n-i,r));  
        }
        r[n] = q;// 備忘
        return q;
    }
}

int max(int q, int s){
    if(q > s){
        return q;
    }
    return s;
}

小王兩眼瞪的直直的。boss好厲害,我這剛入職的小白還得好好修煉呀。
寫完boss說:這中方法被稱為帶備忘的自頂向下法。這個方法按自然的遞歸形式編寫過程,但過程會保存每個子問題的解(通常保存在一個數組或散列表中),當需要一個子問題的解時,過程首先檢查是否已經保存過此解,如果是,則直接返回保存的值。還有一種自底向上法。這種方法一般需要恰當定義子問題的規模,使得任何子問題的求解都只依賴於“更小的”子問題的求解。因而我們可以將子問題按規模排序,按由小至大的順序進行求解,當求解某個子問題是,它所依賴的那些更小的子問題都已經求解完畢,結果已經保存,每個子問題只需求解一次。如果你有興趣回去好好看看書自己下去好好研究下吧。
最後再考考你,我寫的這個帶備忘的自頂向下法的時間復雜度是多少?小王看了看代碼,又是循環,又是遞歸的,腦子都轉暈了。boss接著說:“你可以這樣想,我這個函數是不是對規模為0,1,…,n的問題進行了求解,那麽你看,當我求解規模為n的子問題時,for循環是不是叠代了n次,因為在我整個n規模的體系中,每個子問題只求解一次,也就是說我for循環裏的遞歸直接返回的是之前已經計算了的值,比如說 求解 n =3的時候,for(int i = 1,i <=3;i++),循環體執行三次,n=4時,循環體執行四次,所以說,我這個函數MEMOIZED_CUT_ROD進行的所有遞歸調用執行此for循環的叠代次數是一個等差數列,其和是O(n^2)”,是不是效率高了許多。小王嗯嗯直點頭,想著回去得買本《算法導論》好好看看。

無後效性是一個問題可以用動態規劃求解的標誌之一,理解無後效性對求解動態規劃類題目非常重要

轉自:http://blog.csdn.net/qq_30137611/article/details/77655707


某階段的狀態一旦確定,則此後過程的演變不再受此前各種狀態及決策的影響


百度百科是這樣定義的,是不是很苦澀,難懂。並且網上對這個名詞的解釋大多都是理論性的,不好理解,今天我們通過一個例子來看看什麽是無後效性

現在有一個四乘四的網格,左上角有一個棋子,棋子每次只能往下走或者往右走,現在要讓棋子走到右下角


假設棋子走到了第二行第三列,記為s(2,3),如下圖,畫了兩條路線和一條不符合題意的路線,那麽當前的棋子[s(2,3)位置]怎麽走到右下角和之前棋子是如何走到s(2,3)這個位置無關[不管是黑色尖頭的路線還是藍色箭頭的路線]

換句話說,當位於s(2,3)的棋子要進行決策(向右或者向下走)的時候,之前棋子是如何走到s(2,3)這個位置的是不會影響我做這個決策的。之前的決策不會影響了未來的決策(之前和未來相對於現在棋子位於s(2,3)的時刻),這就是無後效性,也就是所謂的“未來與過去無關”

技術分享


看完了無後效性,那我們再來看看有後效性,還是剛才的例子,只不過現在題目的條件變了,現在棋子可以上下左右走但是不能走重復的格子

那麽現在紅色箭頭就是一個合法的路線了,當我的棋子走到了s(2,3)這個位置的時候,要進行下一步的決策的時候,這時候的決策是受之前棋子是如何走到s(2,3)的決策的影響的,比如說紅色箭頭的路線,如果是紅色箭頭決策而形成的路線,那麽我下一步決策就不能往下走了[因為題意要求不能走重復的格子],之前的決策影響了未來的決策,”之前影響了未來”,這就叫做有後效性

在此感謝騰訊大神的指導,學習離不開自己的努力和名師的指導。

動態規劃初探及什麽是無後效性? (轉)