關於01背包問題的優化


一、01背包問題介紹

  背包問題是經典的動態規划問題之一;

  常見的01背包問題就是說有一堆物品,現在要裝入一個容器中,這些物品的重量和價值各不一致,而容器的重量又是有限的,沒種物品只能裝1個或者不裝(0個),求當重量限定為w時,這些物品能裝進去組合成的最高價值是多少?

 

分析:我們首先將物品排成一排(隨機),依次標記為1號,2號。。。。然后從一號開始依次往里放,放的時候判斷當前物品是不是應該放進去:

      如果當前物品放進去之后的最高總價值不放進去的最高總價值 大 ,那么就是要放入,然后總價值取放入之后的

                                  反之不放入,總價值依然取之前的值。

  並且,在對同一個物品判斷時,從0依次增加容器的容量,直到上限w

     當前物品放進去之后的最高總價值 = 上一號物品判斷時(容量 = 當前容量 - 當前物品重量 時)總價值 + 當前物品的價值

     不放進去的最高總價值 = 上一號物品判斷的時候(容量 = 當前容量 時)總價值

  所以有偽代碼如下:

        if (當前物品重量 > 當前容量) {
            此時最高總價值 = 當前物品不放進去的最高總價值;  // 此時物品重量比容量大,放不進去,只能取放不進去的情況
        } else {
            此時最高總價值 = max(當前物品不放進去的最高總價值, 當前物品放進去的最高總價值);
        }

因此,我們可以用一個狀態表來對整個過程進行描述。

假設現在是有三物品 (不是三種)一號、二號、三號,重量分別為3,2,5; 價值分別為7,4,8; 當前容器容量為8,求最大價值。

首先,第一行,為沒有任何物品的時候,全部置0;

第二行,判斷一號物品能不能放:

  當容量擴充為3的時候,一號物品終於能放,價值為7,由於當前也只有一號,所以后面都是7;

第三行,判斷二號物品能不能放:

  當容量擴充為2的時候,二號物品終於能放,價值為4,與不放進去的最高價值(一號物品在此容量時的值:0)進行比較,所以成功放進去;

  當容量擴充為3的時候,二號物品一定放時,價值為4,與不放進去的最高價值(一號物品在此容量時的值:7)進行比較,決定不放進去了;

  。。。

  當容量擴充為5的時候,二號物品一定放時,最高價值為(一號物品在(當前容量 - 二號物品的重量)時的價值+二號物品的價值

                                          =(一號物品在容量為(5-2)的時候 + 二號物品的價值)= 7 + 4 =11

  。。。【如下表】

容量 0 1 2 3 4 5 6 7 8
0 0 0 0 0 0 0 0 0
一號 0 0 0 7 7 7 7 7 7
二號 0 0 4 7 7 11 11 11 11
三號 0 0 4 7 7 11 11 12 15

所以有代碼如下:

    public static int getMaxValue(int[] weight, int[] value, int w) {
        int n = weight.length;
        int[][] table = new int[n + 1][w + 1];
        for (int i = 1; i <= n; i++) {     // 物品遍歷(第0行肯定全是0,所以不用遍歷)
            for (int j = 0; j <= w; j++) { // 背包大小遞增
                if (weight[i-1] > j) {     // 當前物品i的重量比背包容量j大,裝不下,只能是不裝
                    table[i][j] = table[i - 1][j];
                } else {                   // 裝得下,Max{不裝物品i, 裝物品i}
                    table[i][j] = Math.max(table[i - 1][j], table[i - 1][j - weight[i-1]] + value[i-1]);
                }
            }
        }
        return table[n][w];
    }

提問:為什么要【n+1】行?

 ——因為之后每一個數都需要與上一行做比較,一號物品放入的時候也需要與沒放入的時候做比較,所以空出一行作為“沒有物品的時候”;

  為什么要【w+1】列?

 ——因為在容量遞增時,第一個物品能放的時候,需要與容量減去當前物品的重量,也就是容量為0的時候做比較,所以,也是需要一列作為“0容量的時候”;

  為什么是weight[i - 1] 和 value[i - 1]?

    ——因為傳入的重量和價值必然是從一號物品開始,而我們的表是從“沒有物品”和“0容量”開始,多了一行和一列,所以 i 是當前物品的標號,而當前物品下標為【標號-1】;

 

二、空間復雜度優化

  很顯然,上面算法的空間復雜度為矩陣大小【n+1】*【w+1】。

  n可能不大,但是實際應用傷的w可能會很大,這樣造成了比較大的空間占用。

  而仔細觀察發現,矩陣中的值只與當前值的左上角的矩陣里的值有關,如圖二號物品容量為4時的價值7,只與綠色標注值有關。

容量 0 1 2 3 4 5 6 7 8
0 0 0 0 0 0 0 0 0
一號 0 0 0 7 7 7 7 7 7
二號 0 0 4 7 7 11 11 11 11
三號 0 0 4 7 7 11 11 12 15

  並且我們是按行進行更新的,所以我們用一個一維數據就能進行狀態的更替,不過要注意更新的方向。

  所以依賴關系為:下面依賴上面,右邊依賴左邊

  如果正常地從左到右邊,那么右邊面等待更新的值需要的依賴(左邊)就會被覆蓋掉,所以應該每行從右邊開始更新。代碼如下:

    public static int getMaxValueByOne(int[] weight, int[] value, int w) {
        int n = weight.length;
        int[] table = new int[w + 1];

        for (int i = 1; i <= n; i++) {
            for (int j = w; j >= 0; j--) {
                if (weight[i-1] <= j) {
                    table[j] = Math.max(table[j], table[j - weight[i-1]] + value[i-1]);
                }
            }
        }
        return table[w];
    }

  如此一來,01背包的空間復雜度就降為了O(w)

 


免責聲明!

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



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