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


一、基本概念

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

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

    假設問題是由交疊的子問題所構成,我們就能夠用動態規划技術來解決它。一般來說,這種子問題出自對給定問題求解的遞推關系中,這個遞推關系包括了同樣問題的更小子問題的解。動態規划法建議,與其對交疊子問題一次重新的求解,不如把每一個較小子問題僅僅求解一次並把結果記錄在表中(動態規划也是空間換時間的)。這樣就能夠從表中得到原始問題的解。

(3)動態規划經常常使用於解決最優化問題,這些問題多表現為多階段決策。

    關於多階段決策:在實際中,人們經常遇到這樣一類決策問題,即因為過程的特殊性,能夠將決策的全過程根據時間或空間划分若干個聯系的階段。而在各階段中。人們都須要作出方案的選擇。我們稱之為決策。而且當一個階段的決策之后,經常影響到下一個階段的決策,從而影響整個過程的活動。這樣,各個階段所確定的決策就構成一個決策序列,常稱之為策略。因為各個階段可供選擇的決策往往不止一個。因而就可能有很多決策以供選擇,這些可供選擇的策略構成一個集合,我們稱之為同意策略集合(簡稱策略集合)。每一個策略都對應地確定一種活動的效果。我們假定這個效果能夠用數量來衡量。 因為不同的策略經常導致不同的效果,因此,怎樣在同意策略集合中選擇一個策略,使其在預定的標准下達到最好的效果。經常是人們所關心的問題。我們稱這種策略為最優策略,這類問題就稱為多階段決策問題。

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

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

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

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

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

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

二、基本思想與策略


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

       在求解任一子問題時,列出各種可能的局部解,通過決策保留那些有可能達到最優的局部解,丟棄其它局部解。依次解決各子問題,最后一個子問題就是初始問題的解。因為動態規划解決的問題多數有重疊子問題這個特點。為降低反復計算。對每個子問題僅僅解一次,將其不同階段的不同狀態保存在一個二維數組中。

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

三、適用的情況


能采用動態規划求解的問題的一般要具有3個性質:

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

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

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

四、求解的基本步驟

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

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

                 

    (1)划分階段:按照問題的時間或空間特征,把問題分為若干個階段。在划分階段時,注意划分后的階段一定要是有序的或者是可排序的,否則問題就無法求解。

    (2)確定狀態和狀態變量:將問題發展到各個階段時所處於的各種客觀情況用不同的狀態表示出來。當然,狀態的選擇要滿足無后效性。

    (3)確定決策並寫出狀態轉移方程:因為決策和狀態轉移有着天然的聯系,狀態轉移就是根據上一階段的狀態和決策來導出本階段的狀態。所以如果確定了決策,狀態轉移方程也就可寫出。但事實上常常是反過來做,根據相鄰兩個階段的狀態之間的關系來確定決策方法和狀態轉移方程。

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

    一般,只要解決問題的階段、狀態和狀態轉移決策確定了,就可以寫出狀態轉移方程(包括邊界條件)。

實際應用中可以按以下幾個簡化的步驟進行設計:

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

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

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

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

五、常見動態規划問題

 1、找零錢問題   

   有數組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];  
    }   

2、走方格問題


      有一個矩陣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;     
        }  
    }  

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM