01揹包 學習筆記

學習揹包問題之前首先要了解動態規劃的思想

動態規劃

作者:王勐
鏈接:https://www.zhihu.com/question/23995189/answer/35429905
來源:知乎
著作權歸作者所有,轉載請聯繫作者獲得授權。


動態規劃是通過拆分問題(不管問題有沒有時間軸的概念,抽象成時間先後關係)

  • 狀態的定義
  • 狀態轉移方程的定義
  • 階段是狀態的容器

問題的分類由階段中狀態的轉移方式決定的:

  • 每個階段只有一個狀態->遞推;
  • 每個階段的最優狀態都是由上一個階段的最優狀態得到的->貪心;
  • 每個階段的最優狀態是由之前所有階段的狀態的組合得到的->搜索;
  • 每個階段的最優狀態可以從之前某個(不一定是上一個)階段的某個或某些狀態直接得到而不管之前這個狀態是如何得到的->動態規劃。
    每個階段的最優狀態可以從之前某個階段的某個或某些狀態直接得到,這個性質叫做最優子結構
    而不管之前這個狀態是如何得到的,這個性質叫做無後效性

01揹包問題:有的問題往往需要對每個階段的所有狀態都算出一個最優值,然後根據這些最優值再來找最優狀態。


01揹包問題

揹包問題——“01揹包”詳解及實現(包含揹包中具體物品的求解)


狀態轉移方程F[i][j]=max{F[i-1][j] , F[i-1][j-W[i]]+V[i]}
不管前i個物品的狀態 xxxxx ↑ xxxxxxx xxxxxx ↑ xxxxxxx
xxxxx xxxxxxx xxxx 第i個物品未選 xxx 第i個物品已選

揹包問題數據表:

編號i 1 2 3 4 5 6
體積W 2 3 1 4 6 5
價值V 5 6 5 1 19 7

揹包中得到的最大價值表F[i][j]:(i標號,C承重,W體積,V價值)

W V i\C 0 1 2 3 4 5 6 7 8 9 10
0 0 0 0 0 0 0 0 0 0 0 0
2 5 1 0 0 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆ 5☆
3 6 2√ 0 0 5 6☆ 6☆ 11☆ 11☆ 11☆ 11☆ 11☆ 11☆
1 5 3√ 0 5☆ 5 10☆ 11☆ 11☆ 16☆ 16☆ 16☆ 16☆ 16☆
4 1 4 0 5 5 10 11 11 16 16 16 16 17☆
6 19 5√ 0 5 5 10 11 11 19☆ 24☆ 24☆ 29☆ 30☆
5 7 6 0 5 5 10 11 11 19 24 24 29 30
  • →從左向右一行一行填表。
  • 每一列相當於一個階段。有兩種狀態:選中,未選中。
  • 單元中狀態的maxValue取決於:max{對應上一行F[i-1][j](未選中),上一行中F[i-1][j-W(i)] (選中)}
  • ☆代表Path[i][j]=1,即存儲節點是否更新(即不管前i個物品存放關係,第i物品是否選中存放。選中是爲1,不是爲0)
    注:不是表示最終第i個是否被選中即可,只是其中各個階段中是否被選中

代碼實現(java):

public class package01 {
    //cow代表物品編號i;column代表承重量j
    int[][] Table;//存儲各節點價值
    int[][] Path;//存儲節點是否更新(即不管前i個物品存放關係,第i物品是否選中存放。選中是爲1,不是爲0)
    int Weight[], Value[];//物品重量,物品價值
    int nLen, nCapacity;//物品數量,揹包最大承重量
    //構造函數,初始化
    public package01(int W[], int V[], int N, int C){
        Weight = W;
        Value = V;
        nLen = N;
        nCapacity = C;
        Table = new int[N+1][C+1];
        Path = new int [N+1][C+1];
    }
    /*
     * 狀態轉移方程F[i][j]=max{F[i-1][j] , F[i-1][j-C[i]]+W[i]}
     */
    int progressing(boolean isPrint){
         for(int i = 1; i <= nLen; i++)  
            {  
                for(int j = 1; j <= nCapacity; j++)  
                {  
                    Table[i][j] = Table[i-1][j];  
                    Path[i][j] = 0;  
                    if(j >= Weight[i-1] && Table[i][j] < Table[i-1][j-Weight[i-1]]+Value[i-1])  
                    {  
                        Table[i][j] = Table[i-1][j-Weight[i-1]]+Value[i-1];  
                        Path[i][j] = 1;  
                    }  
                }  
            } 
         if(isPrint){       
             System.out.print("i:V:F\n");
             Print(nLen, nCapacity);             
         }

        return Table[nLen][nCapacity];

    }
    //打印選中節點(遞歸)
    void Print(int cow, int column){
        if(cow>0 && column>0){
            if(Path[cow][column] == 1){
                Print(cow-1, column-Weight[cow-1]);//不管有沒有選中cow都要減1
                System.out.print(cow +":" + Value[cow-1] + ":" + Table[cow][column] + "\n");
            }
            else {
                Print(cow-1, column);
            }
        }   
    }

    /*
    //打印選中節點(逆序)
    void Print(int cow, int column){
        int i = cow;
        int j = column;
        while(i > 0 && j > 0)  
        {  
            if(Path[i][j] == 1)  
            {  
                System.out.print(i +":" + Value[i-1] + ":" + Table[i][j] + "\n");
                j -= Weight[i-1];  
            }  

            i--; 
        }
    }  
    */

}

時間及空間複雜度均爲O(VN)


壓縮空間,以降低空間複雜度
時間複雜度爲O(VN),空間複雜度將爲O(V)
F[i][j]只與F[i-1][j]和F[i-1][j-C[i]]有關,即只和i-1時刻狀態有關,
只需要用一維數組F[]來保存i-1時的狀態F[]。
狀態轉移方程F[j]=max{F[j] , F[j-W[i]]+V[i]}

代碼實現:

public class package01_compress {
    //cow代表物品編號i;column代表承重量j
    int[] Table;//存儲各節點價值,一維數組壓縮空間複雜度
    int[][] Path;//存儲節點是否更新(即不管前i個物品存放關係,第i物品是否選中存放。選中是爲1,不是爲0)
                  //注:不是表示最終第i個是否被選中即可,只是其中各個階段中是否被選中
    int Weight[], Value[];//物品重量,物品價值
    int nLen, nCapacity;//物品數量,揹包最大承重量
    //構造函數,初始化
    public package01_compress(int W[], int V[], int N, int C){
        Weight = W;
        Value = V;
        nLen = N;
        nCapacity = C;
        Table = new int[C+1];
        Path = new int [N+1][C+1];
    }
    /* 
     * F[i][j]只與F[i-1][j]和F[i-1][j-C[i]]有關,即只和i-1時刻狀態有關,
     * 只需要用一維數組F[]來保存i-1時的狀態F[]
     */
    int progressing(boolean isPrint){
        for(int i = 0; i < nLen; i++)  
        {  
            //逆序遍歷(正序遍歷時,會收集前面某個階段節點的信息,但是可能已經被更新了,導致錯誤)
            for(int j = nCapacity; j >= Weight[i]; j--)  
            {  
                Path[i+1][j] = 0;  
                if(Table[j] < Table[j-Weight[i]]+Value[i])  
                {  
                    Table[j] = Table[j-Weight[i]]+Value[i];  
                    Path[i+1][j] = 1;  
                }  
            }     
        } 
         if(isPrint){       
             System.out.print("i:V:F\n");
             Print(nLen, nCapacity);             
         }

        return Table[nCapacity];
    }
    //打印選中節點(遞歸)
        void Print(int cow, int column){
            if(cow>0 && column>0){
                if(Path[cow][column] == 1){
                    Print(cow-1, column-Weight[cow-1]);//不管有沒有選中cow都要減1
                    System.out.print(cow +":" + Value[cow-1] + ":" + Table[column] + "\n");
                }
                else {
                    Print(cow-1, column);
                }
            }   
        }
}

測試代碼:

public class test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int W[] = {2,3,1,4,6,5};  
        int V[] =  {5,6,5,1,19,7};  
        int C = 10;  
       // package01 pa = new package01(W, V, W.length, C);
        package01_compress pa =new package01_compress(W, V, W.length, C);
        System.out.println("maxValue:" + pa.progressing(true));
    }

}

結果:

i:V:F
2:6:10
3:5:11
5:19:30
maxValue:30
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章