01背包問題是動態規划中的經典問題。
本篇文章主題:分析與優化最基本的01背包問題,對此類問題解題有一個基本的解題模板。
問題概述:
有一個背包,他的容量為C(Capacity)。現在有n種不同的物品編號分別為0、1....n-1。其中每一件物品的重量為w(i),價值為v(i)。問可以向這個背包中放入哪些物品,使得在不超過背包容量的基礎上,背包內物品價值最大。
思路:
1.暴力法。
每一件物品都可以放進背包,也可以不放進背包。找出所有可能組合一共2^n種組合
時間復雜度:O((2^n)*n)
2.動態規划法。
我們首先使用遞歸函數自上而下進行思考。
明確兩點:
第一、遞歸函數的定義
第二、數據結構
函數定義:
F(n,C)遞歸函數定義:將n個物品放入容量為C的背包,使得價值最大。
這里要注意一下,第二個參數一定是剩余容量。我們通過使用剩余容量來控制價值。
F(i,c) = F(i-1,c)
= v(i) + F(i-1 , c-w(i))
狀態轉移方程:
F(i,c) = max( F(i-1 , c) , v(i) + F(i-1 , c-w(i) ) )
即,當前價值的最大值為,不放入第i個物品(對應剩余容量為c)和放入第i個物品(對應剩余容量為C-w(i))兩種情況的最大值。
數據結構:
借某盜版視頻中的一個例子:
我們這里選擇一個二維數組,來迭代記錄處理的結果。
這個二維數組dp[n][C] 其中n為物品數量,C為最大容量。
儲存的值dp[i][j]含義為:考慮放入0~i 這些物品, 背包容量為j
我們考慮放入第一個物品。
由於第一個物品,編號為0,重量為1,價值為2。
對於容量為0的背包,放不下該物品,所以該背包價值為0.
其余容量1~5,均可放下該物品。所以只考慮物品0,不同背包大小對應的最大可能價值如圖。
第一行處理為初始化,從第二行開始進行迭代。
第二行開始,就需要單獨處理。
考慮dp[1][0],背包容量為0,理所應當為0
考慮dp[1][1],此處我們依舊無法放入物品1,所以我們使用上一層的結果,即0~0物品在容量為1背包情況的最大價值。
考慮dp[1][2],此處我們終於可以放下物品1了,所以我們考慮如果要放下物品1,剩余背包最大的可能價值,即dp[0][0]
我們對比上一層的情況,以及掏空背包放入物品2的情況。發現最大值為后者,所以dp[1][2]為10
同上,我們掏出可以放下物品1的空間,考慮此時最大價值,即dp[0][1]。對比他和上一層dp[0][3]的大小,發現前者大。
故此時dp[1][3]為dp[0][1]+v[1] = 16.
以此類推,我們每次清空對應物品大小的背包,然后放入對應物品,對比不放入物品的上一行。求出最大值
依次填入dp[][]得出最終的二維數組。
代碼如下
class Knapasack01{ public : int knapsack01(int[] w,int[] v,int C){ //w為0-~n-1物品對應價值 //v為0~n-1物品對應重量。 //C為背包容量 int n = w.length(); if(n == 0) return 0; //動態規划記憶數組。 int[][] dp = new int[n][C]; //初始化第一行。 for(int j=0 ; i<= C ; j++) dp[0][j] = (j>=w[0]?v[0]:0); for(int i=1 ; i<n ; i++) for(int j=0 ; j<C ; j++){ dp[i][j] = dp[i-1][j]; if(j>=w[i]) memo[i][j] = (int)Math.max(dp[i][j] , v[i]+dp[i][j-w[i]]); } return dp[n-1][C]; } }