動態規劃算法詳解及經典例題

1、基本概念

(1)一種使用多階段決策過程最優的通用方法。java

(2)動態規劃過程是:每次決策依賴於當前狀態,又隨即引發狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,因此,這種多階段最優化決策解決問題的過程就稱爲動態規劃。算法

    假設問題是由交疊的子問題所構成,咱們就可以用動態規劃技術來解決它。通常來講,這種子問題出自對給定問題求解的遞推關係中,這個遞推關係包括了一樣問題的更小子問題的解。動態規劃法建議,與其對交疊子問題一次從新的求解,不如把每個較小子問題僅僅求解一次並把結果記錄在表中(動態規劃也是空間換時間的)。這樣就可以從表中獲得原始問題的解。數組

(3)動態規劃常常常使用於解決最優化問題,這些問題多表現爲多階段決策。測試

    關於多階段決策:在實際中,人們常常遇到這樣一類決策問題,即由於過程的特殊性,可以將決策的全過程根據時間或空間劃分若干個聯繫的階段。而在各階段中。人們都需要做出方案的選擇。咱們稱之爲決策。並且當一個階段的決策以後,常常影響到下一個階段的決策,從而影響整個過程的活動。這樣,各個階段所肯定的決策就構成一個決策序列,常稱之爲策略。由於各個階段可供選擇的決策每每不止一個。於是就可能有不少決策以供選擇,這些可供選擇的策略構成一個集合,咱們稱之爲贊成策略集合(簡稱策略集合)。每個策略都對應地肯定一種活動的效果。咱們假定這個效果可以用數量來衡量。 由於不一樣的策略常常致使不一樣的效果,所以,怎樣在贊成策略集合中選擇一個策略,使其在預約的標準下達到最好的效果。常常是人們所關心的問題。咱們稱這種策略爲最優策略,這類問題就稱爲多階段決策問題。優化

(4)多階段決策問題舉例:機器負荷分配問題spa

    某種機器可以在高低兩種不一樣的負荷下進行生產。在高負荷下生產時。產品的年產量g和投入生產的機器數量x的關係爲g=g(x),這時的年完善率爲a,即假設年初完善機器數爲x,到年終時完善的機器數爲a*x(0<a<1);在低負荷下生產時,產品的年產量h和投入生產的機器數量y的關係爲h=h(y)。對應的完善率爲b(0<b<0)。且a<b。設計

    假定開始生產時完善的機器熟練度爲s1。code

    要制定一個五年計劃,肯定每一年投入高、低兩種負荷生產的完善機器數量,使5年內產品的總產量達到最大。blog

    這是一個多階段決策問題。排序

    顯然可以將全過程劃分爲5個階段(一年一個階段),每個階段開始時要肯定投入高、低兩種負荷下生產的完善機器數,而且上一個階段的決策一定影響到下一個階段的生產狀態。決策的目標是使產品的總產量達到最大。這個問題經常使用數學方法建模,結合線性規劃等知識來進行解決。

2、基本思想與策略


  基本思想與分治法相似,也是將待求解的問題分解爲若干個子問題(階段),按順序求解子階段,前一子問題的解,爲後一子問題的求解提供了實用的信息。

       在求解任一子問題時,列出各類可能的局部解,經過決策保留那些有可能達到最優的局部解,丟棄其它局部解。依次解決各子問題,最後一個子問題就是初始問題的解。由於動態規劃解決的問題多數有重疊子問題這個特色。爲下降反覆計算。對每一個子問題僅僅解一次,將其不一樣階段的不一樣狀態保存在一個二維數組中。

       與分治法最大的區別是:適合於用動態規劃法求解的問題,經分解後獲得的子問題每每不是互相獨立的(即下一個子階段的求解是創建在上一個子階段的解的基礎上,進行進一步的求解)。

3、適用的狀況


能採用動態規劃求解的問題的通常要具備3個性質:

(1)最優化原理:假設問題的最優解所包括的子問題的解也是最優的,就稱該問題具備最優子結構,即知足最優化原理。

(2)無後效性:即某階段狀態一旦肯定。就不受這個狀態之後決策的影響。也就是說,某狀態之後的過程不會影響曾經的狀態。僅僅與當前狀態有關;

(3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被屢次使用到(該性質並不是動態規劃適用的必要條件,但是假設沒有這條性質。動態規劃算法同其它算法相比就不具有優點)。

4、求解的基本步驟

     動態規劃所處理的問題是一個多階段決策問題,通常由初始狀態開始,經過對中間階段決策的選擇,達到結束狀態。這些決策造成了一個決策序列,同時肯定了完成整個過程的一條活動路線(一般是求最優的活動路線)。如圖所示。動態規劃的設計都有着必定的模式,通常要經歷如下幾個步驟。

    初始狀態→│決策1│→│決策2│→…→│決策n│→結束狀態

                 

    (1)劃分階段:按照問題的時間或空間特徵,把問題分爲若干個階段。在劃分階段時,注意劃分後的階段必定要是有序的或者是可排序的,不然問題就沒法求解。

    (2)肯定狀態和狀態變量:將問題發展到各個階段時所處於的各類客觀狀況用不一樣的狀態表示出來。固然,狀態的選擇要知足無後效性。

    (3)肯定決策並寫出狀態轉移方程:由於決策和狀態轉移有着自然的聯繫,狀態轉移就是根據上一階段的狀態和決策來導出本階段的狀態。因此若是肯定了決策,狀態轉移方程也就可寫出。但事實上經常是反過來作,根據相鄰兩個階段的狀態之間的關係來肯定決策方法和狀態轉移方程。

    (4)尋找邊界條件:給出的狀態轉移方程是一個遞推式,須要一個遞推的終止條件或邊界條件。

    通常,只要解決問題的階段、狀態和狀態轉移決策肯定了,就能夠寫出狀態轉移方程(包括邊界條件)。

實際應用中能夠按如下幾個簡化的步驟進行設計:

    (1)分析最優解的性質,並刻畫其結構特徵。

    (2)遞歸的定義最優解。

    (3)以自底向上或自頂向下的記憶化方式(備忘錄法)計算出最優值。

    (4)根據計算優值時獲得的信息,構造問題的最優解。

5、常見動態規劃問題

 一、找零錢問題   

   有數組penny,penny中全部的值都爲正數且不重複。每一個值表明一種面值的貨幣,每種面值的貨幣可使用任意張,再給定一個整數aim(小於等於1000)表明要找的錢數,求換錢有多少種方法。給定數組penny及它的大小(小於等於50),同時給定一個整數aim,請返回有多少種方法能夠湊成aim。

測試樣例:
penny=[1,2,4]

penny_size=3

aim = 3
返回:2
即:方案爲{1,1,1}和{1,2}兩種

分析:

  設dp[n][m]爲使用前n中貨幣湊成的m的種數,那麼就會有兩種狀況:

              使用第n種貨幣:dp[n-1][m]+dp[n-1][m-peney[n]]

              不用第n種貨幣:dp[n-1][m],爲何不使用第n種貨幣呢,由於penney[n]>m。

       這樣就能夠求出當m>=penney[n]時 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]],

  不然,dp[n][m] = dp[n-1][m]。

import java.util.*;  
public class Exchange {  
    public int countWays(int[] penny, int n, int aim) {  
        // write code here  
        if(n==0||penny==null||aim<0){  
         return 0;     
        }  
        int[][] pd = new int[n][aim+1];  
        for(int i=0;i<n;i++){  
         pd[i][0] = 1;     
        }  
        for(int i=1;penny[0]*i<=aim;i++){  
         pd[0][penny[0]*i] = 1;     
        }  
        for(int i=1;i<n;i++){  
            for(int j=0;j<=aim;j++){  
                if(j>=penny[i]){  
                    pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];  
                }else{  
                    pd[i][j] = pd[i-1][j];  
                }  
            }  
        }  
        return pd[n-1][aim];  
    }   

二、走方格問題


      有一個矩陣map,它每一個格子有一個權值。從左上角的格子開始每次只能向右或者向下走,最後到達右下角的位置,路徑上全部的數字累加起來就是路徑和,返回全部的路徑中最小的路徑和。
給定一個矩陣map及它的行數n和列數m,請返回最小路徑和。保證行列數均小於等於100.
測試樣例:
[[1,2,3],[1,1,1]],2,3
返回:4

解析:設dp[n][m]爲走到n*m位置的路徑長度,那麼顯而易見dp[n][m] = min(dp[n-1][m],dp[n][m-1]);

import java.util.*;    
public class MinimumPath {  
    public int getMin(int[][] map, int n, int m) {  
        // write code here  
       int[][] dp = new int[n][m];  
        for(int i=0;i<n;i++){  
            for(int j=0;j<=i;j++){  
             dp[i][0]+=map[j][0];      
            }  
        }  
        for(int i=0;i<m;i++){  
            for(int j=0;j<=i;j++){  
             dp[0][i]+=map[0][j];      
            }  
        }  
        for(int i=1;i<n;i++){  
            for(int j=1;j<m;j++){  
             dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);     
            }  
        }  
        return dp[n-1][m-1];  
    }  
    public int min(int a,int b){  
        if(a>b){  
         return b;     
        }else{  
         return a;     
        }  
    }  
相關文章
相關標籤/搜索