作为动态规划的经典题目,网上的很多解释有点模糊,写个博客记录一下吧。
1、问题描述
你有一个承重能力为 b e a r i n g bearing bearing 的背包,现在有一些物品,其重量按顺序存放在数组 w e i g h t ( w e i g h t [ i ] > 0 ) weight (weight[i]>0) weight(weight[i]>0) 中,每个物品的价值按顺序存放在数组 v a l u e value value 中。问题:在背包的承重范围内,可以装的物品的最大价值是多少?
2、状态转移方程的建立
2.1 状态转移方程
我们用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示访问到
i
i
i 个物品时(包括第
i
i
i 个物品)承重能力为
j
j
j 的背包所装物品的最大价值。 【注意:有的文章将
j
j
j 解释为背包的剩余承重量,这是错误的!这里的
j
j
j 就是背包的承重能力,至于为什么,看到后面就懂了。】
当访问到第
i
i
i 个物品的时候,对于承重能力为
j
j
j 的背包,分为以下两种情况:
-
j
<
w
e
i
g
h
t
[
i
]
j<weight[i]
j<weight[i],即背包的承重能力
j
j
j 小于第
i
i
i 个物品的重量
w
e
i
g
h
t
[
i
]
weight[i]
weight[i],此时第
i
i
i 个物品是无法装入背包中的,因此访问到第
i
i
i 个物品的时候背包所装物品的最大价值就是访问到第
i
−
1
i-1
i−1 个物品时背包所装物品的最大价值。即:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j] -
j
>
=
w
e
i
g
h
t
[
i
]
j>=weight[i]
j>=weight[i],即背包的承重能力
j
j
j 不小于(大于等于)第
i
i
i 个物品的重量
w
e
i
g
h
t
[
i
]
weight[i]
weight[i],此时第
i
i
i 个物品可以装入背包中【注意:这里说第
i
i
i 个物品可以装入背包指的是背包之前怎么装的我不管,由于背包的承重能力
j
>
w
e
i
g
h
t
[
i
]
j>weight[i]
j>weight[i],因此第
i
i
i 物品是可以装入背包中,至于装入第
i
i
i 个物品之前怎么装最好,那是
d
p
[
i
]
[
j
−
w
e
i
g
h
t
[
i
]
]
dp[i][j-weight[i]]
dp[i][j−weight[i]] 需要回答的问题。】。对于第
i
i
i 个物品,有两种选择——装或者不装。
选择装的时候的最大价值为:
d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] dp[i-1][j-weight[i]]+value[i] dp[i−1][j−weight[i]]+value[i]
【注意:这里不是 d p [ i − 1 ] [ j − 1 ] + v a l u e [ i ] dp[i-1][j-1]+value[i] dp[i−1][j−1]+value[i],此处的理解是关键。】
选择不装的时候的最大价值则为:
d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]
我们选择两者的最大值,即为访问到 i i i 个物品时(包括第 i i i 个物品)承重能力为 j j j 的背包所装物品能获得的最大价值,即 d p [ i ] [ j ] dp[i][j] dp[i][j],也就是:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] , d p [ i − 1 ] [ j ] ) dp[i][j]=max(dp[i-1][j-weight[i]]+value[i],dp[i-1][j]) dp[i][j]=max(dp[i−1][j−weight[i]]+value[i],dp[i−1][j])
2.2 初始条件
对于 d p [ i ] [ j ] dp[i][j] dp[i][j],由于下标是从0开始的,因此01背包问题的初始条件是:
- 由于物品的重量不为0,因此当背包的承重 j j j 为 0 时,访问到第 i i i 个物品时所能装的物品的最大价值为0,即 d p [ i ] [ 0 ] = 0 dp[i][0]=0 dp[i][0]=0.
- 第 i i i 个物品处的最大价值与第 i − 1 i-1 i−1 个物品的最大价值是相关的,如果首个物品的编号为 i i i,则第 i − 1 i-1 i−1 个物品就代表没有物品,由于数组的下标从0开始,因此我们将物品从1开始编号,则第0个物品代表没有物品,因此有 d p [ 0 ] [ j ] = 0 dp[0][j]=0 dp[0][j]=0.
可以验证,上面的初始条件和状态转移方程是吻合的。
3、代码实现
对于如下问题。
物品的重量为:
w
e
i
g
h
t
=
[
4
,
6
,
2
,
2
,
5
,
1
]
weight=[4,6,2,2,5,1]
weight=[4,6,2,2,5,1]
每个物品的价值为 :
v
a
l
u
e
=
[
8
,
10
,
6
,
3
,
7
,
2
]
value=[8,10,6,3,7,2]
value=[8,10,6,3,7,2]
背包的承重为:
b
e
a
r
i
n
g
=
12
bearing=12
bearing=12
为了适应状态转移方程,我们对物品从1开始编号,而不是从0开始编号,先对
w
e
i
g
h
t
weight
weight 和
v
a
l
u
e
value
value 补零,即:
w
e
i
g
h
t
=
[
0
,
4
,
6
,
2
,
2
,
5
,
1
]
weight=[0,4,6,2,2,5,1]
weight=[0,4,6,2,2,5,1]
v
a
l
u
e
=
[
0
,
8
,
10
,
6
,
3
,
7
,
2
]
value=[0,8,10,6,3,7,2]
value=[0,8,10,6,3,7,2]
从而可以得到
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 的边界条件。即:
d
p
[
i
]
[
0
]
=
0
(
i
=
0
,
1
,
2...
,
6
)
dp[i][0]=0 \quad (i=0,1,2...,6)
dp[i][0]=0(i=0,1,2...,6)
d
p
[
0
]
[
j
]
=
0
(
j
=
0
,
1
,
2...
,
12
)
dp[0][j]=0 \quad (j=0,1,2...,12)
dp[0][j]=0(j=0,1,2...,12)
代码实现如下:
// 0 1 背包问题
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
vector<int> weight = { 0,4,6,2,2,5,1 }; //物品的重量(补零后)
vector<int> value = { 0,8,10,6,3,7,2 }; //物品的价值(补零后)
int bearing = 12; //背包的承重
int n = weight.size(); //物品数量(补零后)
vector<vector<int>> dp(n, vector<int>(bearing + 1)); //状态转移数组
//核心代码
for (int i = 1; i < n; i++)
{
for (int j = 1; j < = bearing; j++)
{
if (j < weight[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j - weight[i]] + value[i], dp[i - 1][j]);
}
}
cout << "所装物品最大价值:" << dp[n - 1][bearing];
return 0;
}
结果为24.
如果将
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 全部打印出来,则如下表所示: