算法之動態規劃

版權聲明:本文爲博主原創文章,允許轉載,不過請標明出處。 https://blog.csdn.net/zyh2525246/article/details/78976823

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

動態規劃的思想與策略:
將待求解的問題分解爲若干個子問題(階段),按順序求解子階段,前一子問題的解,爲後一子問題的求解提供了有用的信息。在求解任一子問題時,列出各種可能的局部解,通過決策保留那些有可能達到最優的局部解,丟棄其他局部解。依次解決各子問題,最後一個子問題就是初始問題的解。

由於動態規劃解決的問題多數有重疊子問題這個特點,爲減少重複計算,對每一個子問題只解一次,將其不同階段的不同狀態保存在一個二維數組中。

在上代碼之前說個例子:
假設你有1,2,3(元)三種種類的零錢,數量不限,你需要找零6元,請問有多少種方法?

很顯然,是7種。我們就來說一說這個思想。

第一類:只用1元或2元或3元的零錢去找零,均爲1種,共3種
第二類:只用1元,2元二種找零,會出現2種,
第三類:只用1元,3元二種找零,會出現1種,
第四類:用1元,2元,3元三種找零,會出現1種,

所以答案是7種辦法。我們換一個角度進

引用塊內容

行思考,將其分爲3類:

第一類:只能用1元去找零,共1種
第二類:可以用1元,2元找零,會出現4種,
第三類:可以用1元,2元,3元找零,會出現7種,

在這裏就會有疑問,第三類不是直接和問題一樣了嗎?但是不難發現的是:在第二類的結果中包含了第一類的結果,第三類的結果中也包含了第二類的結果。
第一類與第二類,第二類與第三類存在的差異就是所用的零錢種類數。
換一個角度看,可以將第三類分爲2種情況:
(1)使用3元零錢:3,1,1,1—–3,2,1—–3,3(三種方法)
(2)不使用3元零錢:與第二類相同
同理將第二類分爲同樣的2種情況:
(1)使用2元零錢:2,2,2—–2,2,1,1—–2,1,1,1,1(三種方法)
(2)不使用2元零錢:與第一類相同
第一類其實同樣可以分爲2種情況:
(1)使用1元零錢:1,1,1,1,1,1(1種情況)
(2)不使用1元零錢:0種

如果將問題依次解決,那麼最後一個問題的解就是問題初始的解,這也正是動態規劃的魅力所在。

上面這一段文字或許你沒讀的太懂,所以通過代碼示例進行進一步的解讀。

public class 動態規劃_找零問題 {

    public static void main(String[] args) {

        Exchange exchange = new Exchange();
        int a[] = {1,2,3};//零錢面值
        int count = exchange.countWays(a, 3, 6);//使用前3種零錢找零6元
        System.out.println(count);
    }

}

//penny存放零錢的數組  n所用數組零錢種類的數量  aim所需找錢數
class Exchange {  
    public int countWays(int[] penny, int n, int aim) { 
        //數據出錯 
        if(n == 0 || penny == null || aim < 0){  
            return 0;     
        }  
        /**
        *二維數組的大小設定爲零錢的種類數*(找零數+1)
        所加的1是找零0元
        */
        int[][] pd = new int[n][aim+1]; 
        //將找零0元的列初始化爲1(無論是多少零錢去找零0元,都不存在,所以都是一種情況)
        for(int i=0;i<n;i++){  
            pd[i][0] = 1;     
        }  
        //由於最小找零是1,所以第0行的每一列都初始化爲1
         /**就像這樣
         *        0     1     2     3     4     5    6
         *(1元)【1】 【1】 【1】 【1】 【1】 【1】 【1】
         */
        for(int i = 1; penny[0] * i <= aim; i++){  
            pd[0][penny[0] * i] = 1;     
        }
       /**
         *現在假設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。(就好比你需要找零2元,假設你有一種3元的零錢,你就使用不到)
         *這樣就可以求出當m>=penney[n]時 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]]
         *否則,dp[n][m] = dp[n-1][m]
         */
        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];  
    }

或許還是會有一丁點的疑惑(那我就再仔仔細細的分析一波):
第二類(只用1元,2元):

  • 如果需要找一元錢,則不需要用到2元錢。那麼和只用一元錢是一樣的,即dp[n][m] = dp[n-1][m]。是1種。
  • 如果需要找兩元錢,不用到2塊錢,即一種。
    用到2元錢。然後再用(1元,2元)去找0元錢。1種。
    相加共2種。
  • 如果需要找三元錢,不用到2塊錢,即一種。
    用到2元錢。然後再用(1元,2元)去找1元錢。1種。
    相加共2種。
  • 如果需要找四元錢,不用到2塊錢,即一種。
    用到2元錢。然後再用(1元,2元)去找2元錢(見上)。2種。
    相加共3種。
  • 如果需要找五元錢,不用到2塊錢,即一種。
    用到2元錢。然後再用(1元,2元)去找3元錢(見上)。2種。
    相加共3種。
  • 如果需要找六元錢,不用到2塊錢,即一種。
    用到2元錢。然後再用(1元,2元)去找4元錢(見上)。即dp[n][m-peney[n]],3種。
    相加共4種。

第三類(用1,2,3):

  • 如果需要找一元錢,則不需要用到3元錢。即dp[n][m] = dp[n-1][m]。是1種。
  • 如果需要找兩元錢,不用到3塊錢,即2種。
  • 如果需要找三元錢,不用到3塊錢,即2種。
    用到3元錢。然後再用(前三種)去找0元錢。1種。
    相加共3種。
  • 如果需要找四元錢,不用到3塊錢,即3種。
    用到3元錢。然後再用(前三種)去找1元錢。1種。
    相加共4種。
  • 如果需要找五元錢,不用到3塊錢,即3種。
    用到3元錢。然後再用(1元,2元)去找2元錢(見上)。2種。
    相加共5種。
  • 如果需要找六元錢,不用到3塊錢,即4種。
    用到3元錢。然後再用(前三種)去找3元錢(見上)。3種。
    相加共7種。

那麼算到最後呢,數組是這樣子的:
0 1 2 3 4 5 6
(1元) a0 【1】 【1】 【1】 【1】 【1】 【1】【1】
(2元) a1 【1】 【1】 【2】 【2】 【3】 【3】【4】
(3元) a2 【1】 【1】 【2】 【3】 【4】 【5】【7】

如果還有疑問,對不起,我已經把能講的講完了。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章