假设我们面临一个问题
题目
题目描述
有一个容量为 m 的背包 , 有 n 件物品可以挑选 , 每个物品有 2 个属性 :体积及价值 。
求背包能装下的最大价值是多少?
输入格式
第1行 2 个正整数 , n, m 代表物品个数与背包容量。
接下来 n 行每行 2 个正整数 , w[i]表示第 i 个物品的体积 , v[i]表示第 i 个物品的价值
输出格式
输出1个整数 , 即能装下的最大价值。
输入样例
4 6
1 4
2 6
3 12
2 7
输出样例
23
数据范围
1 <= n,m <= 1000;1 <= w[i],v[i] <= 400
分析
假设有三个物品:吉他(价值15 重量1),笔记本(价值20 重量3),音响(价值30 重量4)
并且背包容量是4
贪心算法
按照贪心的想法,要获得最大价值应该先拿价值最大的物品,也就是音响,价值30,但是如果这样一拿,背包就直接装满了,也就是说最终得到价值30的东西,但我们可以很快构造出价值35的组合:吉他+笔记本。所以,贪心算法是难以解决此问题的。
那有没有一种算法能快速解决此问题呢?
动态规划算法
我们可以创建一个数组F
//f[i][j]表示前i个物品,背包容量不超过j时的最大价值
用图像说明的话就是:
那接下来的任务就是如何填充此表格了
首先看第一行,我们知道吉他的重量是1,价值是15,所以选取吉他并且重量不超过1的最大价值就是吉他的价值15。接下来一想,显然,这一行都是15
再来看第二行,我们知道笔记本的重量是3,价值是20,那么看这一行的第一格,它的意义是前2个物品中,背包容量不超过1的最大价值。因为容量限制在1以内,所以只能是一个吉他,价值15
第二格,表示前2个物品中,背包容量不超过2的最大价值。这时候我们有两个选择:
- 不选择笔记本,最大价值就是前1个物品中背包容量不超过2的最大价值,就是15
- 选择笔记本,最大价值就是前1个物品中(因为已经选了笔记本)背包容量不超过2-笔记本重量的最大价值
但是笔记本的重量是大于2的,所以只能选择方案1,价值还是15
接下来看第三格,同样,两个选择:
- 不选择笔记本,最大价值就是前1个物品中背包容量不超过3的最大价值,就是15
- 选择笔记本,最大价值就是前1个物品中(因为已经选了笔记本)背包容量不超过3-笔记本重量的最大价值
这是方案2就可行,因为笔记本重量=3,所以方案2所得的价值是20+前0个……,也就是20嘛
欸,那这时,20 > 15,所以这时方案2会优于方案1,选择方案2
因此,我们可以得到递推式
f[i][j] = max(f[i - 1][j],f[i - 1][j - w[i]] + v[i]);
其中,f[i - 1][j]为不选第i件物品的最大价值,f[i - 1][j - w[i]] + v[i]为选第i件物品的最大价值
我们就是在这两个值中取较大的
所以表格剩下的也就按照这个递推式来进行即可
最终答案是表格的右下角也就是 f[n][m],最大价值35
但在递推式中,需要注意的是,如果j < w[i]时,就只能是 f[i - 1][j]
我们还需要考虑初始值,其实这很容易,如果这一上来体积就大于背包容量了,那所拿的价值就是0,所以 f 数组中每一项初始值为0
此时,算法原理已经讲完了,而代码实现难度并不高,我们直接来看代码
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int w[1010],v[1010];
//w[i]表示第i件物品的重量,v[i]表示第i件物品的价值
int f[1010][1010];
//f[i][j]表示前i个物品重量不超过j的最大价值
int main()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++)
{
cin >> w[i] >> v[i];
}
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= m;j++)
{
f[i][j] = f[i - 1][j];
if(j - w[i] >= 0)
f[i][j] = max(f[i - 1][j],f[i - 1][j - w[i]] + v[i]);
}
}
cout << f[n][m];
return 0;
}
最后,如果你没有理解这个算法的话,我强烈建议你手动模拟一下整个过程,这样,你的理解程度会更深一些。
有兴趣的话可以看一下下一篇01背包一维优化