标题0-1背包问题
公式
| dp[i-1][j] , w[i]>val 公式(一)
dp[i][j] = |
| max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) ,w[i]<=val 公式(二)
其中dp标识的是这样的一个二维数组,即前i种物品在背包的存放量为j时所能放置的最大的物品价值。其中w是一个一维数组表示的是一系列的物品的重量,v是一个与w对应的数组里面的是每一个物品的价值。val为当前的背包的剩余容量,如果当前的物品重量比剩余容量大,俺么只能选取之前的。如果当前的容量可以存放下当前物品那么就进行价值比较。
现在来句一个例子,并一步一步的推导出dp数组。
int w[]={2,3,4,5}; //四个物品,重量分别为2,3,4,5
int v[]={3,4,5,6};//四个物品对应的价值,分别为3,4,5,6
int val=8; //背包的总容量为8
int dp[][] = new int[w.length][val+1]; //定义一个dp数组,因为8算是最大值,所以要去到8,那么列数就只能是9。
第一步先求出dp的第一行,也就是只有一个物品时背包如何存放
如例子所示先给出第一行的列表:
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
如表所示,只有当当前的背包的容量大于等于物品0也就是第一个物品的时候才会有具体的价值,所以当背包容量为2的时候此时的价值为3,后续不管背包如何大,因为只能拿一个所以价值一直为3 。
下面来推导一下两个物品时的dp值该如何选取。
- 首先还是那样只有当当前的背包的容量大于当前的物品的重量的时候才可以放入物品。所以背包容量为0,1时不能存放任何的物品,所以此时的价值为0.即dp[1][0]=dp[1][1] = 0.
- 此时到了背包的容量为2了,那么对于物品0来说可以放下,但是对于物品而来说就放不下了,所以按照公式直接可以得出答案就是dp[i][j]=dp[i-1][j]=2 。
- 此时到了背包容量为3了,那么对于物品0来说可以放下且价值为3,对于物品1来说也是可以放下的,且价值为4.根据公式第一条肯定不满足即当前的背包是可以存放下当前物品即物品1的。那么就按照第二个公式来判断,即max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) ,此时的dp[i-1][j] (i=1,j=3),那么就是dp[0][3],对比上表可知为3,此时的w[i]为3,v[i] 为4.那么就是max(3,dp[0][3-3]+4)=max(3.4)=4,所以当前的dp[1][3]=4 。
- 同理计算背包容量为4时,那么对于物品0来说可以放下且价值为3,对于物品1来说也是可以放下的,且价值为4.根据公式第一条肯定不满足即当前的背包是可以存放下当前物品即物品1的。那么就按照第二个公式来判断,即max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) ,此时的dp[i-1] [j] (i=1,j=4),那么就是dp[0][4],对比上表可知为3,此时的w[i]为3,v[i] 为4.那么就是max(3,dp[0][4-3]+4)=max(3.4)=4,所以当前的dp[1][4]=4 。
- 同理计算背包容量为5的情况,那么对于物品0来说可以放下且价值为3,对于物品1来说也是可以放下的,且价值为4.根据公式第一条肯定不满足即当前的背包是可以存放下当前物品即物品1的。那么就按照第二个公式来判断,即max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) ,此时的dp[i-1][j] (i=1,j=5),那么就是dp[0][5],对比上表可知为3,此时的w[i]为3,v[i] 为4.那么就是max(3,dp[0][5-3]+4)=max(3.7)=7,所以当前的dp[1][5]=7 。
- 下面的就基本上不变了,因为在背包容量为5的时候就已经将两个物品都放入背包中那么即使背包再打那么也只能在放入两个物品了,所以后面的值和背包容量为5的值一样。
如下表:
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
1 | 3 | 4 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
下面在说一下剩下的情况
前三个物品的情况,按照上面的步骤一步一步的解析,如在背包容量为8的时候那么如何判断呢?首先8肯定是大于第三个物品的重量4的所以选择第二个公式,那么就来计算一下第二个公式里的两个只把,首先dp[i-1][j] = dp[1][8] =7,dp[i-1][j-w[i]] +v[i]= dp[1][4] +v[2] = 4+5 = 9,所以dp[2][8] = 9.
如表所示:
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
1 | 3 | 4 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
2 | 4 | 5 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
那么最后的前四个物品的情况也是同理,如在背包容量为8的时候那么如何判断呢?首先8肯定是大于第四个物品的重量5的所以选择第二个公式,那么就来计算一下第二个公式里的两个值吧,首先dp[i-1][j] = dp[2][8] =9,dp[i-1][j-w[i]] +v[i]= dp[2][3] +v[3] = 4+6 = 10,所以dp[2][8] = 10.
如表格所示:
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
1 | 3 | 4 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
2 | 4 | 5 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
3 | 5 | 6 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
所以只要按照公式来就可以计算出来所有的情况,最后返回一下dp的最后一个值即可即10.
贴出代码:
public class OneZeroProblem {
int w[]={2,3,4,5};
int v[]={3,4,5,6};
int val=8;
int dp[][] = new int[w.length][val+1];
public OneZeroProblem(){
// 先处理第一行的数据,即要不是0,要不是第一个物品的价值
for (int i = 0; i <= val; i++) {
if (w[0]>i)
dp[0][i] = 0;
else
dp[0][i] = v[0];
}
// 然后按照递推公式也就是状态转移公式来计算每一个数值。
for (int i = 1; i < w.length; i++) {
for (int i1 = 0; i1 <= val; i1++) {
dp[i][i1] = getresult(i,i1,i1);
}
}
for (int i = 0; i < w.length; i++) {
for (int i1 = 0; i1 <= val; i1++) {
System.out.print(dp[i][i1]+" ");
}
System.out.println();
}
}
public int getresult(int i,int j,int cap){
// 1.如过当前的重量大于当前的容量,那么就是前一个数的最大值。
if (w[i]>cap)
return dp[i-1][j];
else{// 将之前的最大值即剩余容量不变或是((将当前的剩余质量-减去当前物品的重量的价值)+当前物品的价值)
//这两个值取至大值。
return Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}
public static void main(String[] args) {
new OneZeroProblem();
}
}
|
完全背包问题
与上面的01背包问题不一样的是完全背包问题不限制拿取的物品的个数,但是这个完全可以由01背包问题推导出来。唯一的区别就是这个物品拿多少的问题。
还是以第一个物品举例,但是背包的容量变大了变为了28(取大一点方便展示效果)
如0,1都是为0,因为他们还没有物品1大。到了背包容量为2时此时可以存放下物品1了,那么价值就是为3,容量为3时,价值为3,因为现在的容量放不下两个物品1.容量为4时价值为6,应为此时可以存放2个物品1了,。。。。。容量为28时价值为42.
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | … | 28 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
好了,第一个被解出来了,那么后面的就好办了,唯一需要理解的就是当前的背包可不可以放下当前序号的物品,如果能的话,那么能放下几个,并且和之前的不放这个物品的价值做比较。所以因为多了一个能放几个物品所以需要额外的一个for循环来找到物品存放的最佳个数。
所以需要将之前的公式改变一下:
| dp[i-1][j] , w[i]>va 公式(一)
dp[i][j] = |
| max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) ,k*w[i]<=val 公式(二)
其中k为选取当前物品的数量。
如前两个物品来举例:
- 0,1都是不满足的,直接为0.
- 对于背包容量为2,那么依据公式二可知此时的价值为3
- 对于背包容量为3,那么依据公式2可知此时的价值为4.
- 对于背包容量为4,那么可知此时的k只能取1或是0,如果取0,那么就是dp[i-1][j]=dp[0][4] = 6,如果选择1的话那么就是max(6,5) = 6,所以最终的结果是6.
- 对于背包容量为5,那么可支持此时的k也只能是0.1,那么最终的结果还是和上面的一样为6.
- 对于背包容量为6.那么k的可选值为0,1,2,那么最终的结果是选0为9,选1为max(9,3+4)=9,选择2的话那么最终的结果是max(9,8)=9.
- 对于背包容量为7,和背包容量为6的那个都是9.
- 对于背包容量为8,和背包容量为6的那个都是9.
- 对于背包容量为28,那么k的可选范围为0,1,2,。。。。,9那么最终的结果就是42.
如表所示:
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | … | 28 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
1 | 3 | 4 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
后面的物品均和之前一样的判断
i | w | v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | … | 28 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
1 | 3 | 4 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
2 | 4 | 5 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
3 | 5 | 6 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 12 | … | 42 |
贴出代码:
public class AbsolutePacketProblem {
int w[]={2,3,4,5};
int v[]={3,4,5,6};
int val=28;
int dp[][] = new int[w.length][val+1];
public AbsolutePacketProblem(){
for (int i = 0; i <= val; i++) {
if (w[0]>i)
dp[0][i]=0;
else{
for(int k=1;k*w[0]<=i;k++){
dp[0][i] = k*v[0];
}
}
}
for (int i = 1; i < w.length; i++) {//物品放置的个数,从两个开始以知道最终的物品的个数
for (int i1 = 0; i1 <= val; i1++) {//背包的容量,从0到val
int max =0;
//在这里判断可以放置当前物品的个数,并求出最佳组合的价值
for(int k=0;k*w[i]<=i1;k++){
int getresult = getresult(i, i1, k);
if (getresult>max)
max = getresult;
}
dp[i][i1] = max;
}
}
System.out.print(" ");
for (int i = 0; i <= val; i++) {
System.out.print(i+" ");
}
System.out.println();
for (int i = 0; i < w.length; i++) {
System.out.print(i+" "+"w: "+w[i]+" v: "+v[i]+" ");
for (int i1 = 0; i1 <= val; i1++) {
System.out.print(dp[i][i1]+" ");
}
System.out.println();
}
}
public int getresult(int i,int i1,int k){
if (w[i]>i1){//依据公式一
return dp[i-1][i1];
}
else{//依据公式二
return Math.max(dp[i-1][i1],dp[i-1][i1-k*w[i]]+v[i]*k);
}
}
public static void main(String[] args) {
new AbsolutePacketProblem();
}
}
多重背包问题
这个问题就跟简单了,背包问题解决了,那么这个问题就很简单了。只需要在完全背包问题的最后一层for循环上添加一个判断即可,先看公式:
| dp[i-1][j] , w[i]>va 公式(一)
dp[i][j] = |
| max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) ,k*w[i]<=val&&k<Knum[i] 公式(二)
其中Knum[i]指的是第i个物品的个数。
代码:
public class MutiPacketProblem {
/**
输入:
第一行两个输入n,m 分别是物品种类和背包的大小
第二行,n个数分贝是每个物品的重量
第三行n个数,分别是每个物品的价值
第四行n个数,分别是每个物品的个数
4 5
1 2 3 4
2 4 4 5
3 1 3 2
*/
int[] W;
int[] V;
int[] Wnums;
int[] packets;
int[][] dp;
public void ScannerInit(){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int cap = sc.nextInt();
W = new int[n];
V = new int[n];
Wnums = new int[n];
packets = new int[cap+1];
for (int i = 0; i < n; i++) {
W[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
V[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
Wnums[i] = sc.nextInt();
}
for (int i = 0; i <= cap; i++) {
packets[i] = i;
}
dp = new int[n][cap+1];
}
public MutiPacketProblem(){
ScannerInit();
for (int j = 0; j < packets.length; j++) {
if (W[0]>j)
dp[0][j] = 0;
else {
for (int k = 1; k * W[0] <= j && k <= Wnums[0]; k++) {
dp[0][j] = k*V[0];
}
}
}
for (int i = 1; i < W.length; i++) {
for (int j = 0; j < packets.length; j++) {
for(int k = 1;k*W[i]<=j&&k<=Wnums[i];k++){
dp[i][j] = getResult(i,j,k);
}
}
}
for (int i = 0; i < dp.length; i++) {
for (int i1 = 0; i1 < dp[0].length; i1++) {
System.out.print(dp[i][i1]+" ");
}
System.out.println();
}
}
public int getResult(int i,int cap,int num){
if (W[i]>cap)
return dp[i-1][cap];
else
return Math.max(dp[i-1][cap],dp[i-1][cap-W[i]*num]+num*V[i]);
}
public static void main(String[] args) {
new MutiPacketProblem();
}
}
效果:
输入
4 5
1 2 3 4
2 4 4 5
3 1 3 2
结果
0 2 4 6 6 6
0 0 4 6 8 10
0 0 0 6 8 10
0 0 0 0 8 10