上一篇博客中写道“0-1背包问题”,同时也给出了状态转移方程,但是空间复杂度还是比较高的。
但是上一篇博客中还没有给出相应的代码,所以今天这篇博客就将其完善一下,并且优化一下空间复杂度。
附上一篇博客链接:彻底搞懂0-1背包问题(动态规划)
0-1背包问题的优化
public class test3 {
public static void main(String[] args) {
//这边需要假设第0个物品的重量和价值为0,因为我们是从第1个物品开始算的,这一点很重要
int[] weight = {0,4,3,1};
int[] value = {0,30,20,15};
int nums = weight.length - 1; //物品的数量
int bag_weight = 4; //背包的重量
int[][] dp = new int[nums + 1][bag_weight+1];
//先遍历物品的的数量
for(int i=1;i<=nums;i++){
//遍历背包的重量
for(int j=1;j<=bag_weight;j++){
if(j >= weight[i]){
//决策:要么拿,要么不拿,取两者最大值
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]] + value[i]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
System.out.println(dp[nums][bag_weight]);//输出35
}
}
- 我们可以看到更新后的最大价值 dp[i][j] 基于两个决策,一个是在当前行,我不拿,那么最大价值就是由前一行的最大值直接传递过来;如果拿,那么需要减去这个物品的重量的背包所能装的最大价值+这个物品的价值;然后两者取最大值。
- 很明显我们当前的这个最大值dp[i][j]是根据当前状态的上一行的左边更新过来的(如果当前状态是上图黄色部分,那就是根据上图中的绿色部分更新最大值)。并且很重要的一点就是这个最大值是根据旧值更新过来的,所以我们在代码中必须让 j 以递减的形式更新,以保证能够取到上一行的前面的旧值。
public class test3 {
public static void main(String[] args) {
int[] weight = {0,4,3,1};
int[] value = {0,30,20,15};
int nums = weight.length - 1; //物品的数量
int bag_weight = 4; //背包的重量
int[] dp = new int[bag_weight+1];
for(int i=1;i<=nums;i++){
//这边从右往左更新,j需要递减
for(int j=bag_weight;j>=weight[i];j--){
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
}
}
System.out.println(dp[bag_weight]);
}
}
完全背包问题的优化
- 完全背包和 0-1背包不同的地方在于物品的数量是无限个,你可以重复拿。但是思路也是一样的,只有两种大的决策:要么拿这个物品,要么不拿这个物品。如果拿这个物品,判断拿几个这个物品所产生的的价值是最大的。 所以我们很容易得到以下的代码。
public class wanquan_bag {
public static void main(String[] args) {
int[] weight = {0,4,3,1};
int[] value = {0,30,20,15};
int nums = weight.length - 1; //物品的数量
int bag_weight = 4; //背包的重量
int[][] dp = new int[nums + 1][bag_weight+1];
for(int i=1;i<=nums;i++){
for(int j=1;j<=bag_weight;j++){
//k表示我们可以拿几个这个物品,但是要保证所拿物品的重量小于当前背包的重量
for(int k=0;k*weight[i]<=j;k++){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-k*weight[i]]+k*value[i]);
}
}
}
System.out.println(dp[nums][bag_weight]);//输出60
}
}
//************************************//
//或者使用以下代码
public class wanquan_bag {
public static void main(String[] args) {
int[] weight = {0,4,3,1};
int[] value = {0,30,20,15};
int nums = weight.length - 1; //物品的数量
int bag_weight = 4; //背包的重量
int[][] dp = new int[nums + 1][bag_weight+1];
for(int i=1;i<=nums;i++){
for(int j=1;j<=bag_weight;j++){
if(j >= weight[i]){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-weight[i]]+value[i]);
}
}
}
System.out.println(dp[nums][bag_weight]);
}
}
- 在这里我们也能很容易发现当前的dp[i][j]最大值就取决于上图的绿色部分。但是这里和 0-1 背包不同的优化方式,我们要的是新值,而不在是旧值。所以我们在优化的时候需要从左往右更新,j 是递增的。
public class wanquan_bag {
public static void main(String[] args) {
int[] weight = {0,4,3,1};
int[] value = {0,30,20,15};
int nums = weight.length - 1; //物品的数量
int bag_weight = 4; //背包的重量
int[] dp = new int[bag_weight+1];
for(int i=1;i<=nums;i++){
//这边从左往右更新,j需要递增
for(int j=1;j<=bag_weight;j++){
if(j >= weight[i]){
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
System.out.println(dp[bag_weight]);
}
}