背包问题总结
首先,什么是背包问题呢?
背包问题的形式如下,给定 n n n个重量分别为 ω 1 , ω 2 , ⋯   , ω n \omega_1,\omega_2,\cdots,\omega_n ω1,ω2,⋯,ωn,价值分别为 v 1 , v 2 , ⋯   , v n v_1,v_2,\cdots,v_n v1,v2,⋯,vn的物品和容量为 C C C的背包,求这个物品中一个最有价值的子集,使得在满足背包的容量的前提下,包内的总价值最大。
其中,背包问题还有很多分类,咱们先从最基础的开始,0-1背包问题。该问题的要求是每个物品只能用一次。
刚才的叙述还是太抽象了。咱们再具体一下:
给定3个( n = 3 n=3 n=3)物品,每个物品的重量和价值分别如下:
物品序号 | 重量 | 价值 |
---|---|---|
0 | 1 | 6 |
1 | 2 | 10 |
2 | 3 | 12 |
同时,背包容量为5( C = 5 C=5 C=5),求在重量之和小于背包容量的前提下,所选物品的价值最大。
0-1背包问题介绍完了,用什么方法解呢?
回溯法和动态规划法都可以,这里先用动态规划法来解一下。
什么是动态规划法呢?
简单来说就是,将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。这就是动态规划法。
还是有点抽象。咱们来具体一下。
对该问题,大问题就是,满足背包容量为5时,物品的最大价值。
子问题就是,背包容量为4时,物品的最大价值;背包容量为3时,物品的最大价值;一直到背包容量为0时,物品的最大价值。同时,还需要考虑题目的3个物品。所以,来建一个矩阵吧。
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | ||||||
1 | ||||||
2 |
这里需要对矩阵做一个说明:
第一行,代表背包的容量(假设的,不要产生这样的想法,题目中背包容量已经是5了,为什么背包容量还可以从0变到5呢,这里只是假设,或者说,背包中允许利用的容量),从0到5,
第一列,物品的三个编号,物品0,物品1,物品2
其余空间,在当前条件下,所选物品的最大价值。
顺便说一句,这个表格填完了,问题就解决完了。
如果要使用动态规划求解0-1背包问题,咱们需要先把矩阵的第一行填写完毕。第一行的含义是,对所有的背包容量,只放物品0,该物品的重量是1,价值是6,放的具体过程如下:
- 容量=0,小于物品0 的重量1,放不下,最大价值为0
- 容量=1,等于物品0的重量1,放的下,最大价值为6
- 容量=2,小于物品0 的重量1,放的下,最大价值为6
- 容量=3,等于物品0的重量1,放的下,最大价值为6
- 容量=4,小于物品0 的重量1,放的下,最大价值为6
- 容量=5,等于物品0的重量1,放的下,最大价值为6
所以,放完矩阵第一行后,矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | ||||||
2 |
咱们先来看第一个小问题, C = 0 C=0 C=0,即背包所允许利用的最大容量为0,很容易理解,最大价值为0,所以,表格更新如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | |||||
2 | 0 |
接下来是第二个小问题, C = 1 C=1 C=1,即背包所允许利用的最大容量为1,这里再次给出物品的相关信息,希望读者将物品的相关信息写在纸上
物品序号 | 重量 | 价值 |
---|---|---|
0 | 1 | 6 |
1 | 2 | 10 |
2 | 3 | 12 |
三个物品的重量分别是1,2,3,而背包容量是1,很容易分辨出,只能放物品0,最大价值是6,但是,计算机怎么分辨呢?动态规划最终还是要用编程语言来实现额啊。下面就是动态规划的核心的,状态转移方程
d
p
(
i
,
j
)
=
m
a
x
(
d
p
(
i
−
1
,
j
)
,
d
p
(
i
−
1
,
j
−
w
[
i
]
)
+
v
[
i
]
)
,
j
≥
w
[
i
]
,
i
>
0
dp(i,j)=max(dp(i-1,j),dp(i-1,j-w[i])+v[i]),j\ge w[i],i>0
dp(i,j)=max(dp(i−1,j),dp(i−1,j−w[i])+v[i]),j≥w[i],i>0
很多公式啊,咱们来理解一下:
- dp代表咱们要填写的矩阵,为什么是dp呢,因为这两个字母是动态规划英文表达的缩写
- d p ( i , j ) dp(i,j) dp(i,j)代表待求取位置的最大价值,对于咱们所处的这一步来说,i=1,j=1,因为填到物品1,容量=1了嘛,
- max是求取最大值的操作
- w [ i ] w[i] w[i]代表第i个物品的重量,此处,i=1, w [ i ] = 2 w[i]=2 w[i]=2
- v [ i ] v[i] v[i]代表第i个物品的价值,此处,i=1, v [ i ] v[i] v[i]=10
- 很容易添加限制条件, j ≥ w [ i ] j\ge w[i] j≥w[i],否则矩阵就越界了
公式介绍完了,那么对于咱们要填的这个空,怎么处理呢
- i=1,j=1时,dp(i-1,j)=dp(0,1)=6,w[i]=w[1]=2>j=1,所以,dp(i,j)=dp(1,1)=max(dp(i-1,j))=dp(0,1)=6
- i=2,j=1时,dp(i-1,j)=dp(1,1)=6,w[i]=w[2]=3>j=1,所以,dp(i,j)=dp(2,1)=max(dp(i-1,j))=dp(1,1)=6
此时,如果用一句话描述上面的公式就是,如果当前背包容量j小于物品的重量w[i],那么最大价值就等于上面那一格的值。填写后的矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | 6 | ||||
2 | 0 | 6 |
接下来是第三个小问题, C = 2 C=2 C=2,即背包所允许利用的最大容量是2,
- i=1,j=2时,dp(i-1,j)=dp(0,2)=6,w[i]=w[1]=2=j=2,dp(i-1,j-w[i])=dp(1-1,2-2)=dp(0,0)=0,v[i]=v[1]=10,所以,dp(1,2)=max(dp(0,2)+dp(0,0)+v[1])=max(6,0+10)=10
- i=2,j=2时,dp(i-1,j)=dp(1,2)=10,w[i]=w[2]=3>j=2,所以,dp(2,2)=dp(1,2)=10
什么规律呢,物品1的重量是2,那么dp(1,2)=max( dp(0,2) ,dp(0,0)+v[1]),即上面的那个格的值和上面一行,往左移动2个格(w[1]=2)的值的最大值
填写完后的矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | 6 | 10 | |||
2 | 0 | 6 | 10 |
接下来是第三个小问题, C = 3 C=3 C=3,即背包所允许利用的最大容量是3,
-
i=1,j=3时,dp(i-1,j)=dp(0,3)=6,w[i]=w[1]=2<j=3,dp(i-1,j-w[i])=dp(1-1,3-2)=dp(0,1)=6,v[i]=v[1]=10,所以,dp(1,3)=max(dp(0,3)+dp(0,1)+v[1])=max(6,6+10)=16
-
i=2,j=3时,dp(i-1,j)=dp(1,3)=16,w[i]=w[2]=3=j=3,dp(i-1,j-w[i])=dp(2-1,3-3)=dp(1,0)=0,v[i]=v[2]=12,所以,
dp(2,3)=max(dp(1,3)+dp(1,0)+v[2])=max(16,0+12)=16
填写完后的矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | 6 | 10 | 16 | ||
2 | 0 | 6 | 10 | 16 |
接下来是第三个小问题, C = 4 C=4 C=4,即背包所允许利用的最大容量是4,
-
i=1,j=4时,dp(i-1,j)=dp(0,4)=6,w[i]=w[1]=2<j=4,dp(i-1,j-w[i])=dp(1-1,4-2)=dp(0,2)=6,v[i]=v[1]=10,所以,dp(1,3)=max(dp(0,4)+dp(0,2)+v[1])=max(6,6+10)=16
-
i=2,j=4时,dp(i-1,j)=dp(1,4)=16,w[i]=w[2]=3<j=4,dp(i-1,j-w[i])=dp(2-1,4-3)=dp(1,1)=6,v[i]=v[2]=12,所以,
dp(2,3)=max(dp(1,4)+dp(1,1)+v[2])=max(16,6+12)=18
填写完后的矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | 6 | 10 | 16 | 16 | |
2 | 0 | 6 | 10 | 16 | 18 |
接下来是第三个小问题, C = 5 C=5 C=5,即背包所允许利用的最大容量是5,
-
i=1,j=5时,dp(i-1,j)=dp(0,5)=6,w[i]=w[1]=2<j=5,dp(i-1,j-w[i])=dp(1-1,5-2)=dp(0,3)=6,v[i]=v[1]=10,所以,dp(1,5)=max(dp(0,5)+dp(0,3)+v[1])=max(6,6+10)=16
-
i=2,j=5时,dp(i-1,j)=dp(1,5)=16,w[i]=w[2]=3<j=5,dp(i-1,j-w[i])=dp(2-1,5-3)=dp(1,2)=10,v[i]=v[2]=12,所以,
dp(2,5)=max(dp(1,5)+dp(1,2)+v[2])=max(16,10+12)=22
填写完后的矩阵如下:
物品 | 容量=0 | 容量=1 | 容量=2 | 容量=3 | 容量=4 | 容量=5 |
---|---|---|---|---|---|---|
0 | 0 | 6 | 6 | 6 | 6 | 6 |
1 | 0 | 6 | 10 | 16 | 16 | 16 |
2 | 0 | 6 | 10 | 16 | 18 | 22 |
终于写完了,总结一下公式的含义
上面的一个表格,上面那个表格往左数w[i]和表格,这两个表格的最大值就是要求的值。
解决代码如下:
public class KnapSack01 {
public static int knapSack(int[] w, int[] v, int C) {
int size = w.length;
if (size == 0) {
return 0;
}
int[][] dp = new int[size][C + 1];
//初始化第一行
//仅考虑容量为C的背包放第0个物品的情况
for (int i = 0; i <= C; i++) {
dp[0][i] = w[0] <= i ? v[0] : 0;
}
//填充其他行和列
for (int i = 1; i < size; i++) {
for (int j = 0; j <= C; j++) {
dp[i][j] = dp[i - 1][j];
if (w[i] <= j) {
dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
}
}
}
return dp[size - 1][C];
}
public static void main(String[] args) {
int[] w = {2, 1, 3, 2};
int[] v = {12, 10, 20, 15};
System.out.println(knapSack(w, v, 5));
}
}
最后总结一下0-1背包问题的解题思路:
- 建立矩阵dp,行数是物品的个数,列数是背包容量加1
- 填写第一行,如果背包容量j小于物品重量w[0],最大价值是0,否则是第一个物品的价值v[0]
- 填写接下来的每一行,首先判断背包容量j和第i个物品的重量w[i]的关系
- 如果j<w[i],那么dp(i,j)=dp(i-1,j)
- 如果j>=w[i],那么dp(i,j)=max( dp(i-1,j) ,dp(i-1,j-w[i])+v[i])
- 返回矩阵右下角元素就是最大价值。