1.完全背包和0/1背包的区别?
完全背包的物体有无限个,可以多次放入
0/1背包的物体只有一个,只能放入一次
2.关于物品遍历顺序
在0/1背包中为了防止物品被重复放入,所以选择倒序遍历背包
而完全背包中,可以重复放入,所以选择正序遍历背包
具体来说,如题目
重量 价值 物品1 1 15 物品2 3 20 物品3 5 35 求大小为4的背包可获得的最大价值。
(1)对于0/1背包来说:各个物品只能放入一次
package other;import java.util.Arrays;
import java.util.Objects;public class Bag {public static void main(String[] args) {int[] weight=new int[]{1,3,4};int[] values=new int[]{15,20,30};Bag obj=new Bag();System.out.println("result: "+ obj.simpleBag2(weight,values,4));}public int simpleBag2(int[] weight,int[] values,int bag){int dp[]=new int[bag+1];//初始化Arrays.fill(dp,0);//双层for循环for(int i=0;i<weight.length;i++){//遍历物品for(int j=bag;j>=0;j--){//遍历背包System.out.println("拿起物品i="+i+" ,\t重量:"+weight[i]+"\t背包容量:"+j);if(weight[i]<=j){dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);System.out.println("放入物品i="+i+" ,\t价值:"+values[i]);} else {dp[j]=dp[j];System.out.println("不放入物品i="+i);}printArr(j,dp);}}printArr(null,dp);return dp[dp.length-1];}public void printArr(Integer j,int []arr){if(!Objects.isNull(j)){System.out.print("j = "+j+" :\t\t");}for(int i=0;i<arr.length;i++){System.out.print(arr[i]+"\t");}System.out.println();}
}
拿起物品i=0 , 重量:1 背包容量:4
放入物品i=0 , 价值:15
j = 4 : 0 0 0 0 15
拿起物品i=0 , 重量:1 背包容量:3
放入物品i=0 , 价值:15
j = 3 : 0 0 0 15 15
拿起物品i=0 , 重量:1 背包容量:2
放入物品i=0 , 价值:15
j = 2 : 0 0 15 15 15
拿起物品i=0 , 重量:1 背包容量:1
放入物品i=0 , 价值:15
j = 1 : 0 15 15 15 15
拿起物品i=0 , 重量:1 背包容量:0
不放入物品i=0
j = 0 : 0 15 15 15 15
拿起物品i=1 , 重量:3 背包容量:4
放入物品i=1 , 价值:20
j = 4 : 0 15 15 15 35
拿起物品i=1 , 重量:3 背包容量:3
放入物品i=1 , 价值:20
j = 3 : 0 15 15 20 35
拿起物品i=1 , 重量:3 背包容量:2
不放入物品i=1
j = 2 : 0 15 15 20 35
拿起物品i=1 , 重量:3 背包容量:1
不放入物品i=1
j = 1 : 0 15 15 20 35
拿起物品i=1 , 重量:3 背包容量:0
不放入物品i=1
j = 0 : 0 15 15 20 35
拿起物品i=2 , 重量:4 背包容量:4
放入物品i=2 , 价值:30
j = 4 : 0 15 15 20 35
拿起物品i=2 , 重量:4 背包容量:3
不放入物品i=2
j = 3 : 0 15 15 20 35
拿起物品i=2 , 重量:4 背包容量:2
不放入物品i=2
j = 2 : 0 15 15 20 35
拿起物品i=2 , 重量:4 背包容量:1
不放入物品i=2
j = 1 : 0 15 15 20 35
拿起物品i=2 , 重量:4 背包容量:0
不放入物品i=2
j = 0 : 0 15 15 20 35
0 15 15 20 35
result: 35
可以观察到,由于dp[i]总是由前面的dp[i-1]推导来的,但是每次更新时,dp[i-1]没有物品的累积。
(2)对于完全背包来说:物品可以重复放入
package other;import java.util.Arrays;
import java.util.Objects;public class Bag {public static void main(String[] args) {int[] weight=new int[]{1,3,4};int[] values=new int[]{15,20,30};Bag obj=new Bag();System.out.println("result: "+ obj.simpleBag(weight,values,4));}public int simpleBag(int[] weight,int[] values,int bag){int dp[]=new int[bag+1];//初始化Arrays.fill(dp,0);//双层for循环for(int i=0;i<weight.length;i++){//遍历物品for(int j=1;j<=bag;j++){//遍历背包System.out.println("拿起物品i="+i+" ,\t重量:"+weight[i]+"\t背包容量:"+j);if(weight[i]<=j){dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);System.out.println("放入物品i="+i+" ,\t价值:"+values[i]);} else {dp[j]=dp[j];System.out.println("不放入物品i="+i);}printArr(j,dp);}}printArr(null,dp);return dp[dp.length-1];}public void printArr(Integer j,int []arr){if(!Objects.isNull(j)){System.out.print("j = "+j+" :\t\t");}for(int i=0;i<arr.length;i++){System.out.print(arr[i]+"\t");}System.out.println();}
}
拿起物品i=0 , 重量:1 背包容量:1
放入物品i=0 , 价值:15
j = 1 : 0 15 0 0 0
拿起物品i=0 , 重量:1 背包容量:2
放入物品i=0 , 价值:15
j = 2 : 0 15 30 0 0
拿起物品i=0 , 重量:1 背包容量:3
放入物品i=0 , 价值:15
j = 3 : 0 15 30 45 0
拿起物品i=0 , 重量:1 背包容量:4
放入物品i=0 , 价值:15
j = 4 : 0 15 30 45 60
拿起物品i=1 , 重量:3 背包容量:1
不放入物品i=1
j = 1 : 0 15 30 45 60
拿起物品i=1 , 重量:3 背包容量:2
不放入物品i=1
j = 2 : 0 15 30 45 60
拿起物品i=1 , 重量:3 背包容量:3
放入物品i=1 , 价值:20
j = 3 : 0 15 30 45 60
拿起物品i=1 , 重量:3 背包容量:4
放入物品i=1 , 价值:20
j = 4 : 0 15 30 45 60
拿起物品i=2 , 重量:4 背包容量:1
不放入物品i=2
j = 1 : 0 15 30 45 60
拿起物品i=2 , 重量:4 背包容量:2
不放入物品i=2
j = 2 : 0 15 30 45 60
拿起物品i=2 , 重量:4 背包容量:3
不放入物品i=2
j = 3 : 0 15 30 45 60
拿起物品i=2 , 重量:4 背包容量:4
放入物品i=2 , 价值:30
j = 4 : 0 15 30 45 60
0 15 30 45 60
result: 60
可以观察到物品被重复放入
(3)对于0/1背包问题,双for循环顺序能否颠倒
尝试先背包后物品,是否会对结果有影响
public int simpleBag3(int[] weight,int[] values,int bag){int dp[]=new int[bag+1];//初始化Arrays.fill(dp,0);//双层for循环for(int j=bag;j>=0;j--){//遍历背包for(int i=0;i<weight.length;i++){//遍历物品System.out.println("拿起物品i="+i+" ,\t重量:"+weight[i]+"\t背包容量:"+j);if(weight[i]<=j){dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);System.out.println("放入物品i="+i+" ,\t价值:"+values[i]);} else {dp[j]=dp[j];System.out.println("不放入物品i="+i);}printArr(j,dp);}}printArr(null,dp);return dp[dp.length-1];}
拿起物品i=0 , 重量:1 背包容量:4
放入物品i=0 , 价值:15
j = 4 : 0 0 0 0 15
拿起物品i=1 , 重量:3 背包容量:4
放入物品i=1 , 价值:20
j = 4 : 0 0 0 0 20
拿起物品i=2 , 重量:4 背包容量:4
放入物品i=2 , 价值:30
j = 4 : 0 0 0 0 30
拿起物品i=0 , 重量:1 背包容量:3
放入物品i=0 , 价值:15
j = 3 : 0 0 0 15 30
拿起物品i=1 , 重量:3 背包容量:3
放入物品i=1 , 价值:20
j = 3 : 0 0 0 20 30
拿起物品i=2 , 重量:4 背包容量:3
不放入物品i=2
j = 3 : 0 0 0 20 30
拿起物品i=0 , 重量:1 背包容量:2
放入物品i=0 , 价值:15
j = 2 : 0 0 15 20 30
拿起物品i=1 , 重量:3 背包容量:2
不放入物品i=1
j = 2 : 0 0 15 20 30
拿起物品i=2 , 重量:4 背包容量:2
不放入物品i=2
j = 2 : 0 0 15 20 30
拿起物品i=0 , 重量:1 背包容量:1
放入物品i=0 , 价值:15
j = 1 : 0 15 15 20 30
拿起物品i=1 , 重量:3 背包容量:1
不放入物品i=1
j = 1 : 0 15 15 20 30
拿起物品i=2 , 重量:4 背包容量:1
不放入物品i=2
j = 1 : 0 15 15 20 30
拿起物品i=0 , 重量:1 背包容量:0
不放入物品i=0
j = 0 : 0 15 15 20 30
拿起物品i=1 , 重量:3 背包容量:0
不放入物品i=1
j = 0 : 0 15 15 20 30
拿起物品i=2 , 重量:4 背包容量:0
不放入物品i=2
j = 0 : 0 15 15 20 30
0 15 15 20 30
result: 30
为了保证物品只用一次,所以背包的遍历一定是倒序遍历,颠倒双for的遍历顺序是先背包后物品
保证了防止最合适的一个物品,由于左边的背包没有计算,所以无法累计物品的重量
最后只是选择了能放入了一个最合适的物品
所以这种方式是不可以的。
(4)对于完全背包问题,双for循环顺序能否颠倒
尝试先背包后物品,是否会对结果有影响
public int simpleBag4(int[] weight,int[] values,int bag){int dp[]=new int[bag+1];//初始化Arrays.fill(dp,0);//双层for循环for(int j=1;j<=bag;j++){//遍历背包for(int i=0;i<weight.length;i++){//遍历物品System.out.println("拿起物品i="+i+" ,\t重量:"+weight[i]+"\t背包容量:"+j);if(weight[i]<=j){dp[j]=Math.max(dp[j],dp[j-weight[i]]+values[i]);System.out.println("放入物品i="+i+" ,\t价值:"+values[i]);} else {dp[j]=dp[j];System.out.println("不放入物品i="+i);}printArr(j,dp);}}printArr(null,dp);return dp[dp.length-1];}
拿起物品i=0 , 重量:1 背包容量:1
放入物品i=0 , 价值:15
j = 1 : 0 15 0 0 0
拿起物品i=1 , 重量:3 背包容量:1
不放入物品i=1
j = 1 : 0 15 0 0 0
拿起物品i=2 , 重量:4 背包容量:1
不放入物品i=2
j = 1 : 0 15 0 0 0
拿起物品i=0 , 重量:1 背包容量:2
放入物品i=0 , 价值:15
j = 2 : 0 15 30 0 0
拿起物品i=1 , 重量:3 背包容量:2
不放入物品i=1
j = 2 : 0 15 30 0 0
拿起物品i=2 , 重量:4 背包容量:2
不放入物品i=2
j = 2 : 0 15 30 0 0
拿起物品i=0 , 重量:1 背包容量:3
放入物品i=0 , 价值:15
j = 3 : 0 15 30 45 0
拿起物品i=1 , 重量:3 背包容量:3
放入物品i=1 , 价值:20
j = 3 : 0 15 30 45 0
拿起物品i=2 , 重量:4 背包容量:3
不放入物品i=2
j = 3 : 0 15 30 45 0
拿起物品i=0 , 重量:1 背包容量:4
放入物品i=0 , 价值:15
j = 4 : 0 15 30 45 60
拿起物品i=1 , 重量:3 背包容量:4
放入物品i=1 , 价值:20
j = 4 : 0 15 30 45 60
拿起物品i=2 , 重量:4 背包容量:4
放入物品i=2 , 价值:30
j = 4 : 0 15 30 45 60
0 15 30 45 60
result: 60Process finished with exit code 0
所以,对于完全背包来说,只是更新的方式不一样,结果是一样的。
先物品后背包<=>行更新
先背包后物品<=>列更新
3.总结
0/1背包问题:
双for循环的的顺序不可以颠倒
背包倒序遍历才能保证物品取用一次
完全背包问题:
双for循环顺序可以颠倒,只是更新是行更新还是列更新的问题
背包正序,每个物品都可以取用无限次。