【算法】01背包
先看题目
物品不能分隔,必须全部取走或者留下,因此称为01背包
(只有不取和取两种状态)
看第一个样例
我们需要把4个物品装入一个容量为10的背包
我们可以简化问题,从小到大入手分析
weight value
2 1
3 3
4 5
7 9
先考虑物品数量为1的情况:
把前1件物品放入容量为1的背包,此时得到的最大价值是:0
把前1件物品放入容量为2的背包,此时得到的最大价值是:1
把前1件物品放入容量为3的背包,此时得到的最大价值是:1
......
把前1件物品放入容量为10的背包,此时得到的最大价值是:1
把前2件物品放入容量为1的背包,此时得到的最大价值是:0
把前2件物品放入容量为2的背包,此时得到的最大价值是:1
把前2件物品放入容量为3的背包,此时得到的最大价值是:3
把前2件物品放入容量为4的背包,此时得到的最大价值是:3
把前2件物品放入容量为5的背包,此时得到的最大价值是:4
把前2件物品放入容量为6的背包,此时得到的最大价值是:4
......
把前2件物品放入容量为10的背包,此时得到的最大价值是:4
把前3件物品放入容量为1的背包,此时得到的最大价值是:0
把前3件物品放入容量为2的背包,此时得到的最大价值是:1
把前3件物品放入容量为3的背包,此时得到的最大价值是:3
把前3件物品放入容量为4的背包,此时得到的最大价值是:5
把前3件物品放入容量为5的背包,此时得到的最大价值是:5
把前3件物品放入容量为6的背包,此时得到的最大价值是:6
......
把前3件物品放入容量为10的背包,此时得到的最大价值是:9
即:先从物品的数量开始枚举,对i件物品枚举所对应的背包容量j,考虑它的最大价值f[i][j]
for(int i=1;i<=n;i++){//总共有n件物品
for(int j=1;j<=m;j++){//背包的最大容量是m
}
}
对于一个f[i][j]:
(1)假设我们不选择第i件物品,则f[i][j] = f[i-1][j]
,相当于把前i-1件物品放入这个容量为j的背包,得到的最大值;
(2)假设我们选择第i件物品,则这个物品剩余的空间就是j-w[i]
,前一次循环的时候必定会算出这个值来,因此可以让:f[i][j] = f[i-1][j-w[i]] + v[i]
两种选择中,我们保留最优解,避免下标出现负数(即j-w[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])f[i][j]=max(f[i][j],f[i-1][j-w[i]]+v[i]);
}
}
printf("%d\n",f[n][m]);
其实这道题还有更多的做法,例如递归,从大到小的做法
上面的做法其实是递推,时间复杂度是O(n*m),相比递归好一点,使用这种算法只要保证n*m在107以内即可。空间复杂度则是O(n*m)
其实上,f数组是可以压缩成一维数组的
对于一个数f[i][j],实际上是只用到了第i-1行
因此,经过简单的优化,只需要一行即可,把上一行的信息移动下来,删掉所有相关第一维度的东西,看看会变成什么样:
f[j]=f[j-w[i]]; //直接存到下一行中
f[j]=f[j];
f[j]=max(f[j],f[j-w[i]]);
对于一维数组的问题所在:循环的顺序会发生变化,如果继续按照从左到右的顺序进行循环的话,我们会发现,每一个数字都会被后面的数据所引用,因此需要从后往前来递推。这样,后面的值更新的时候不会被第二次使用,更新前面的值都不会被影响到。
反正记住是从后往前就行了
完整版的代码
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
//此处写j>=w[i]避免下标产生负数
//相当于if(j>=w[i]) f[j]=max(f[j],f[j-w[i]]+v[i]);
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}