0-1背包问题练习

题目描述:

编号为a,b,c,d,e的5个物品,体积分别为2,2,6,5,4,价值分别为6,3,5,4,6。现在有一个容量为10的背包,如何往包中装东西,使得包里的物品总价值最大。

这就是0-1背包问题,0、1分别代表是否选在该物品。解决此类问题最常用的方法就是动态规划。

我们用一个数组记录背包容量从1到10的所有最优情况,只要能构造出这个数组,就能求出最优解。

id

capacity

value

1

2

3

4

5

6

7

8

9

10

a

2

6

0

6

6

9

9

12

12

15

15

15

b

2

3

0

3

3

6

6

9

9

9

10

11

c

6

5

0

0

0

6

6

6

6

6

10

11

d

5

4

0

0

0

6

6

6

6

6

10

10

e

4

6

0

0

0

6

6

6

6

6

6

6


需要说明的是,这张表是从下往上,从右往左填写的。对于e那行的第一列,我们用(e,1)表示,很明显(e,1)=0,(e,2)=0, (e,3)=0, (e, 4)=6 等等等等。

(e,1) = 0,表示当前只有e这一个物品,同时背包容量为1,由于e的体积大于背包容量,所以e放不进去,那么此时背包的最大价值是0。同理(e,4) = 6,表示背包容量为4时,e能放进去了,所以背包价值为6。

(d,1) = 0,表示当前物品有d和e,背包的容量为1,由于容量太小d和e都放不进去,所以背包的价值为0。同理(d,5) = 6,表示当背包容量为5时,d和e只能放一个,因为e的价值高,所以放入e,此时背包的价值为6。(d,9) = 10, 表示当背包容量为9时,d和e都能放进去,此时背包的价值是9。


经过上面的论述,可以总结出以下规律:

假设我们有0,1,2,…,i,…n个物体,用m(i, j)表示现在遍历到第 i 个物体,并且当前背包容量为 j 时背包的价值,

  • 如果背包放不下它,那么m(i,j) = m(i-1, j),背包当前的状态和之前一样一样的
  • 如果背包能放下它,那么m(i,j) = max{ m(i-1,j), m(i-1, j-c) + v) },放还是不放这是个问题,如果价值变大了,就放。其中c表示物体 i 的体积,v表示 i 的价值。

这个规律,就是传说中的状态转移方程~,有了它我们的程序就好写了。
注:程序没有考虑特殊输入,和非法输入,仅为了验证动态规划的思想。作者纯小白一枚~

import java.util.Arrays;

/**
* 0-1背包问题,用动态规划解决
* @author

public class Bag

public static int solution(int[] cap, int[] val, int max_cap) {
if(cap.length != val.length) {
return -1;
}
// 首先建立dp矩阵
int num = cap.length;
int[][] dp = new int[num][max_cap + 1];

// 从下往上填写dp矩阵,首先填写倒数第一行
for(int i = 0; i <= max_cap; i++) {
if(i < cap[num-1]) { // 如果容量小于最后一个物品的体积
dp[num-1][i] = 0;
}else {
dp[num-1][i] = val[num-1];
}
}

// 然后从倒数第二行开始,用状态转移方程,依次填写表格
for(int i = num-2; i >= 0; i--) {
for(int j = 0; j <= max_cap; j++) {
if(j < cap[i]) {
dp[i][j] = dp[i+1][j];
} else {
dp[i][j] = Math.max(dp[i+1][j], dp[i+1][j - cap[i]] + val[i]);
}
}
}

// 打印表格
System.out.println("dp数组为:");
for(int i = 0; i < num; i++) {
for(int j = 0; j <= max_cap; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}

int max = dp[0][max_cap]; //背包的最大价值

// 构造出最优解
// 方法,从最后一列开始构造
int[] result = new int[num];
int i = 0;
for(int j = max_cap; j >= 0 && i < num;) {
if(i+1 < num && dp[i][j] == dp[i+1][j]) {
result[i] = 0;
}else{
result[i] = 1;
j -= cap[i];
}
i++;
}

// 打印result
System.out.println("最优解是,0代表没选,1代表选了");
System.out.println(Arrays.toString(result));

return max;

}

public static void main(String[] args) {

int[] cap = {2, 2, 6, 5, 4}; // 每个物品的体积
int[] val = {6, 3, 5, 4, 6}; // 每个物品的价值
int bag_cap = 10; //背包的总容量
int

0-1背包问题练习_i++

总结一下:
理解状态转移方程,就理解了这个类型的问题

  • 如果背包放不下它,那么m(i,j) = m(i-1, j),背包当前的状态和之前一样一样的
  • 如果背包能放下它,那么m(i,j) = max{ m(i-1,j), m(i-1, j-c) + v) },放还是不放这是个问题,如果价值变大了,就放。其中c表示物体 i 的体积,v表示 i 的价值。

加油哦!!