動態規劃算法-揹包問題

動態規劃定義

任何數學遞推公式均可以直接轉換成遞推算法,可是編譯器經常不能正確對待遞歸算法。將遞歸從新寫成非遞歸算法,讓後者把些子問題的答案系統地記錄在一個表內。利用這種方法的一種技巧叫作動態規劃java

注:由已知推未知就是遞推,由未知推未知就是遞歸,這裏說的數學遞推公式有別與遞推算法。具體解釋以下: 若是數列{an}的第n項與它前一項或幾項的關係能夠用一個式子來表示,那麼這個公式叫作這個數列的遞推公式。git

爲何編譯器經常不能正確對待遞歸?

遞歸4條基本法則

  1. 基準情形。必須有某些基準情形,它無需遞歸就能解出。
  2. 不斷推動。對於那些遞歸求解的步驟,每一次遞歸調用都必需要使狀況朝一種
  3. 設計法則。假設全部的遞歸調用都能運行。
  4. 合成效益法則(compound interest rule)。在求解一個問題的實例時,切勿在不一樣的遞歸調用中作重複的操做。    遞歸的4條法則中,效率低下的遞歸實現常常觸犯第4條法則,即合成效益法則,也是編譯器一般沒法正確對待遞歸的緣由。下面舉例說明。

以求斐波那契數爲例說明

問題說明

有通項公式 f(n)=f(n-1)+f(n-2); f(0)=f(1)=1;求任意n對應的f(n)github

注意:目前有的編譯器能夠優化尾遞歸算法

遞歸解法及存在的問題

    /**
     * 遞歸實現違反合成效益法則
     * */
    public static int fib(int n){
        if(n<=1){
            return 1;
        }else{
            return fib(n-1)+fib(n-2);
        }
    }
複製代碼

以求f6爲例,計算f6須要計算f5和f4,而算f5是有須要計算f4+f3,則一定有重複計算的部分。具體詳細見下圖,(下圖紫色部分都是多餘計算)數組

image

分析

因爲計算F(N)只須要知道F(N-1)和F(N-2),所以咱們只須要保留最近算出的兩個斐波那契數,並從f(2)開始一直計算的f(n)便可。bash

代碼實現

/**
     * 動態規劃版本,保證沒有多餘的計算,
     * 以last 保存f(i-1)的值,nextToLast保存f(i-2)
     * answer 保存f(i)的值
     * */
    public static int fibonacci(int n){
        if(n<=1){
            return 1;
        }
        int last=1;
        int nextToLast=1;
        int answer=1;
        for(int i=2;i<=n;i++){
            answer=last+nextToLast;
            nextToLast=last;
            last=answer;
        }
        return answer;
    }
複製代碼

小試牛刀解揹包問題

問題說明

假定揹包的最大容量爲W,N件物品,每件物品都有本身的價值val和重量wt,將物品放入揹包中使得揹包內物品的總價值最大(val的和最大)。數據結構

分析

臨時揹包總價值=Max{選取當前項揹包總價值,不選取當前項揹包總價值},轉換爲數學公式爲:優化

選取當前項時, 臨時揹包總價值=val[item-1]+V[item-1][weight-wt[item-1]]ui

不選取當前項,臨時揹包總價值= V[item-1][weight]spa

 V[item][weight]=Math.max (val[item-1]+V[item-1][weight-wt[item-1]], V[item-1][weight]);
複製代碼

進過上步驟分析,咱們僅需保留以item爲行,以權重weight爲列的二維數組便可。具體實現以下:

代碼實現(非自實現)

 /**
     * @param val 權重數組
     * @param wt  重量數組
     * @param W   總權重
     * @return    揹包中使得揹包內物品的總價值最大時的重量
     */
    public static int knapsack(int val[], int wt[], int W) {
        //物品數量總和
        int N = wt.length; 
 
        //建立一個二維數組
        //行最多存儲N個物品,列最多爲總權重W,下邊N+1和W+1是保證從1開始
        int[][] V = new int[N + 1][W + 1]; 
 
        
        //將行爲 0或者列爲0的值,都設置爲0
        for (int col = 0; col <= W; col++) {
            V[0][col] = 0;
        }
        for (int row = 0; row <= N; row++) {
            V[row][0] = 0;
        }
        //從1開始遍歷N個物品
        for (int item=1;item<=N;item++){
            //一行一行的填充數據
            for (int weight=1;weight<=W;weight++){
              
                if (wt[item-1]<=weight){
                    //選取(當前項值+以前項去掉當前項權重的值)與不取當前項的值得最大者
                    V[item][weight]=Math.max (val[item-1]+V[item-1][weight-wt[item-1]], V[item-1][weight]);
                }else {//不選取當前項,以以前項代替
                    V[item][weight]=V[item-1][weight];
                }
            }
 
        }
 
        //打印最終矩陣
        for (int[] rows : V) {
            for (int col : rows) {
                System.out.format("%5d", col);
            }
            System.out.println();
        }
        //返回結果
        return V[N][W];
    }
複製代碼

總結

  1. 編譯器通常不能很好的處理遞歸,尤爲是違反合成效益法則的遞歸
  2. 動態規劃須要分析,其重點在於以適用的數據結構保持遞歸步驟中的中間值。
  3. 是否須要將遞歸轉換爲非遞歸須要以實際項目的狀況,酌情考慮。

代碼地址

github地址

求Fibonacci數

動態規劃算法解揹包

碼雲地址

求Fibonacci數    動態規劃算法解揹包

相關文章
相關標籤/搜索