已知一个背包最多能容纳体积之和为v的物品,现有 n 个物品,第 i 个物品的体积为 vi , 价值为 wi,求如何装入物品,使背包中物品价值之和最大?
01背包问题使用动态规划来求解,那么需要知道dp数组的含义。dp[i][j]表示的含义就是背包容量为j,从0到i个物品中选取部分物品放入背包所对应的最大价值。
对于物品i,有两种选择,放入和不放入,不放入dp[i][j] = dp[i - 1][j]
,背包容量为j,从0到i个物品中选取其中物品放入背包所对应的最大价值与背包容量为j,从0到i - 1个物品中选取其中物品放入背包所对应的最大价值是相同的,因为没有放入物品i。放入则dp[i][j] = dp[i - 1][j - 物品i的体积] + 物品i的价值
,容量j需要减去放入物品i的体积,同时加上物品i的价值。dp[i - 1][j - 物品i的体积]表示剩余背包容量,从物品0到i-1个物品中选取其中物品放入背包得到的最大价值。因此递推公式为dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 物品i的体积] + 物品i的价值)
。
设背包容量为5,物品0的大小为2,价值为10;物品1的大小为3,价值为15;物品2的大小为4,价值为20,使用二维数组vw保存物品的大小和价值。vw[0][0] = 2,vw[0][1] = 10;vw[1][0] = 3,vw[1][1] = 15;vw[2][0] = 4,vw[2][1] = 20。
上述例子中背包容量为5,物品数量为3,因此定义dp[5][3],根据递归公式,dp[i][j]依赖于dp[i - 1]和dp[i - 1][j - 物品体积],就是依赖于其上一行的两个数据,因此需要对dp[0]行的数组进行初始化。根据dp数组的含义,dp[0][j]就是背包容量为j,仅有一个物品0,对应的最大价值。显然如果容量j大于等于物品0的体积,背包的最大价值就是物品0的价值,否则,最大价值为0。因此dp数组初始化代码如下:
for(int j = 0;j <= 5;j++){
if(j >= vw[0][0]){
dp[0][j] = vw[0][1];
}
}
此时dp数组如下表所示,背包容量大于等于物品0的体积时,最大价值就是10,因为背包中物品只能放入一次,且仅有物品0。
接着根据递推公式遍历剩余物品。
for(int i = 1;i < 3;i++){
for(int j = 0;j <= 5;j++){
if(j >= vw[i][0]){
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - vw[i][0]] + vw[i][1]);
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
遍历第二个物品,第二个物品的大小为3,价值为15,j等于0,1,2,即背包容量为0,1,2时都无法放入大小为3的物品,不会进入if,进入else,不考虑放入物品1的情况,当背包容量为3,最大价值为不放入物品1的价值,和放入物品1的价值取最大值,不放入物品1,最大价值为dp[0][3],即仅放入一个物品0的价值为10,放入物品1,则背包容量为j - vw[i][0],就是3 - 3等于0,已无法放入其他物品,dp[0][0],剩余容量为0,仅有物品0,背包最大价值,查上图表,可以得dp[0][0]等于0,因此价值为15,最后取最大价值15。当背包容量为4时,最大价值也是15,因为不放入物品1,则仅有物品0,价值为10,放入物品1,则背包容量为j - vw[i][0],4 - 3 = 1,无法放入物品0。当背包容量为5时,背包容量为j - vw[i][0],5 - 3 = 2,dp[0][2],表示剩余容量2,仅有物品0的最大价值,查上述表得10,即刚好放入物品0和物品1,最大价值为25。遍历完物品2,dp数组如下:
遍历第三个物品,当容量j等于0,1,2,3时,背包容量都小于物品2的大小,因此不考虑放入物品2的情况。当背包容量为4时,放入物品2,背包容量为0,dp[1][0],剩余容量为0,有物品0和1,背包最大价值为0,因此价值为20,不放入物品2,价值为15,取最大价值为20,当背包容量为5时,放入物品2,背包剩余容量为1,无法放入其他物品,价值为20,和不放入物品2,价值为dp[1][5],25,取最大价值为25。遍历完最后一个物品,dp数组如下:
最后给出01背包的完整代码。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 计算01背包问题的结果
* @param V int整型 背包的体积
* @param n int整型 物品的个数
* @param vw int整型二维数组 第一维度为n,第二维度为2的二维数组,vw[i][0],vw[i][1]分别描述i+1个物品的vi,wi
* @return int整型
*/
public int knapsack (int V, int n, int[][] vw) {
int[][] dp = new int[n][V + 1];
for(int j = 0;j <= V;j++){
if(j >= vw[0][0]){
dp[0][j] = vw[0][1];
}
}
for(int i = 1;i < n;i++){
for(int j = 0;j <= V;j++){
if(j >= vw[i][0]){
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - vw[i][0]] + vw[i][1]);
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][V];
}
}