背包问题
注意事项
你不可以将物品进行切割。
如果有4个物品[2, 3, 5, 7]
如果背包的大小为11,可以选择[2, 3, 5]装入背包,最多可以装满10的空间。
如果背包的大小为12,可以选择[2, 3, 7]装入背包,最多可以装满12的空间。
函数需要返回最多能装满的空间大小。
01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻。
01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
题目描述:
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
name | weight | 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 |
只要你能通过找规律手工填写出上面这张表就算理解了01背包的动态规划算法。
首先要明确这张表是至底向上,从左到右生成的。
为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。
对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。
同理,c2=0,b2=3,a2=6。
对于承重为8的背包,a8=15,是怎么得出的呢?
根据01背包的状态转换方程,需要考察两个值,
一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi;
在这里,
f[i-1,j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值
f[i-1,j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值
f[i-1,j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6
由于f[i-1,j-Wi]+Pi = 9 + 6 = 15 大于f[i-1,j] = 9,所以物品a应该放入承重为8的背包
-------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------------------------
本题由于没有物品价值这个维度,但是可以直接把物品的重量当做物品的价值,最后依然是求解出背包中最大价值的问题。
上面的解释中数组的更新是从底向上不是很直观,我们从上到下其实是一样的道理,二维数组result[A.size()][m+1] 中的result[i][j]表示在背包空间为j,有A中前i个物品可选的情况下,最大的价值是什么。
f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
int backPack(int m, vector<int> A) {
// write your code here
if (A.size() == 0)
return 0;
if (m == 0)
return 0;
vector<vector<int>> result(A.size(), vector<int>(m + 1, 0));
for (int i = 0; i <= m; i++)
{
if (i >= A[0])
result[0][i] = A[0];
}
for (int i = 1; i < A.size(); i++)
{
for (int j = 0; j <= m; j++)
{
if (j - A[i] >= 0)
result[i][j] = max(result[i - 1][j], result[i- 1][j - A[i]] + A[i]);
else
result[i][j] = result[i- 1][j];
}
}
return result[A.size()][m];
}
int backPack(int m, vector<int> A) {
// write your code here
if (A.size() == 0)
return 0;
if (m == 0)
return 0;
vector<vector<int>> result(2, vector<int>(m + 1, 0));
//本来应该用二维数组存储,但是其实每一次计算只会用到上一行的数据,所以可以直接压缩到一个两行的数组
for (int i = 0; i <= m; i++)
{
if (i >= A[0])
result[0][i] = A[0];
}
for (int i = 1; i < A.size(); i++)
{
for (int j = 0; j <= m; j++)
{
if (j - A[i] >= 0)
result[i % 2][j] = max(result[abs(i % 2 - 1)][j], result[abs(i % 2 - 1)][j - A[i]] + A[i]);
else
result[i % 2][j] = result[abs(i % 2 - 1)][j];
}
}
return result[A.size() % 2 == 0 ? 1 : 0][m];
}