问题描述:
现有一个容量为VVV的背包,以及nnn件物品,其分别占据的容量为cic_ici ,其分别带来的价值为wiw_iwi,(i=1,2,...,ni={1,2,...,n}i=1,2,...,n)
要求:这nnn件物品只能放入背包0次或1次(即,不放入或者放入)
问:背包的最大价值为多少?
使用动态规划进行求解:
子问题定义状态:
dp[i][j]dp[i][j]dp[i][j]表示 前iii件物品,放入容量为jjj的背包中,可以获得的最大价值。
状态转移方程:
dp[i][j]=max(dp[i−1][j],dp[i−1][j−ci]+wi)dp[i][j] = max(dp[i-1][j],dp[i-1][j-c_i]+w_i)dp[i][j]=max(dp[i−1][j],dp[i−1][j−ci]+wi)
其中 dp[i−1][j]dp[i-1][j]dp[i−1][j] 表示:第iii个物品不放入背包时的最大价值(即,将前 i−1i-1i−1 个物品放入容量为jjj的背包中的最大价值)。
dp[i−1][j−ci]+widp[i-1][j-c_i]+w_idp[i−1][j−ci]+wi 表示:第iii个物品放入背包时的最大价值(即,将前 i−1i-1i−1 个物品放入容量为 j−cij-c_ij−ci的背包中的最大价值 + wiw_iwi)。
最终需要的答案为dp[n][V]dp[n][V]dp[n][V]
代码如下:
public static int knapsackProblem(int v, int[] c, int[] w){
int n = c.length;
int[][] dp = new int[n+1][v+1];
// 背包容量为0时,价值为0
for(int i = 0; i <= n; i++)
dp[i][0] = 0;
// 背包里不放任何物品时,其价值也为0
for(int j = 0; j <= v; j++)
dp[0][j] = 0;
// 计算剩余每个dp[i][j]
for(int i = 1; i <= n; i++){
for(int j = c[i-1]; j <= v; j++){ // c[i-1]是因为c是从0开始的,i-1表示的为第i个,w[i-1]同理
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-c[i-1]]+w[i-1]);
}
}
return dp[n][v];
}
优化方案:
上述解决方案使用的是一个二维数组dp[][]dp[][]dp[][],每一行的值与其上一行数据相关,(即,前 iii 个物品产生的最大价值,与前 i−1i-1i−1 个物品产生的最大值相关),我们完全可以只用一行数据,来表示每个不同容量带来的最大价值,(即使用dp[j]dp[j]dp[j]来表示背包容量为 jjj 时带来的最大价值。)而省去表示前 iii 件物品的空间。通过不断更新dp[j]来得到最终的结果。
代码如下:
public static int knapsackProblemOptimized(int v, int[] c, int[] w){
int n = c.length;
int[] dp = new int[v+1];
// 初始化dp,其表示前0个物品放入不同容量的背包,产生的最大价值均为0
for(int i = 0; i <= v; i++)
dp[i] = 0;
// 更新dp值
for(int i = 1; i <= n; i++)
for(int j = v; j >= c[i-1]; j--) // (1)
dp[j] = Math.max(dp[j], dp[j-c[i-1]]+w[i-1]); // (2)
return dp[v];
}
注意点:
在代码 (1) 处,其是一个从 vvv -> c[i−1]c[i-1]c[i−1] 的过程,原因在于:更新值时,依赖的总是 i−1i-1i−1 时的数据。例如,如果现在循环中,i=3i=3i=3,此时 第二重循环还未开始,那 dp[]dp[]dp[] 数组中的数据保存的是 :i=2i=2i=2 时,不同容量(从000 -> vvv)背包的最大价值。而当第二重循环开始后,dp[]dp[]dp[] 中的值开始更新,从(2)中的代码可以看出,dp[j]dp[j]dp[j] 依赖于 dp[j]dp[j]dp[j] 和 dp[j−c[i−1]]dp[j-c[i-1]]dp[j−c[i−1]] 处的值,而这两个值指的均是 i=2i = 2i=2 时的值,若 jjj 值从 c[i−1]c[i-1]c[i−1] -> vvv 更新,dp[j−c[i−1]]dp[j-c[i-1]]dp[j−c[i−1]] 必然会在 dp[j]dp[j]dp[j] 之前被更新,那么当计算 dp[j]dp[j]dp[j] 时, dp[j−c[i−1]]dp[j-c[i-1]]dp[j−c[i−1]] 表示的是 i=3i=3i=3 时的值,而非 i=2i = 2i=2 ,这样的话,就会出现错误。因此 jjj 不能从 c[i−1]c[i-1]c[i−1] -> vvv 更新,而应该从大到小更新。
实质上,使用一维数组来优化的过程,就是将二维数组按行拆分,每次更新保存一行数据,以此来降低存储开销。