什么是0_1背包问题: 0-1背包问题,表示的是每个物品只有一件,每件物品不能分割,在不超过背包容量的同时,如何选取物品,使得背包所装的价值最大(背包可以不装满)。
特点:“步步优"。即最优解是由部分最优解得来。
递归地定义最优解的值:
对于每个物品我们可以有两个选择,放入背包,或者不放入,有n个物品,故而我们需要做出n个选择,于是我们设f[i][v]表示做出第i次选择后,所选物品放入一个容量为v的背包获得的最大价值。对于第i件物品,有两种选择,放或者不放。
1:如果放入第i件物品,则f[i][v] = f[i-1][v-w[i]]+p[i],表示,前i-1次选择后所选物品放入容量为v-w[i]的背包所获得最大价值为f[i-1][v-w[i]],加上当前所选的第i个物品的价值p[i]即为f[i][v]。
2:如果不放入第i件物品,则有f[i][v] = f[i-1][v],表示当不选第i件物品时,f[i][v]就转化为前i-1次选择后所选物品占容量为v时的最大价值f[i-1][v]。则:f[i][v] = max{f[i-1][v], f[i-1][v-w[i]]+p[i] 。
注意这里f[i][v](i指的是前i件物品。v指的是背包剩余容量)。
例如:有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 w[i],价值是 p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这里假设N = 5,V = 10, w[] = {2,2,6,5,4},p[] = {6,3,5,4,6}。
至此可以写出代码来:
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int MAXN = 100;
int main()
{
int n,V;
int f[MAXN][MAXN]; //例如:f[i][j]表示选择第i件物品后,剩余背包容量为j时的价值。
int w[MAXN], p[MAXN]; //w记录物品的重量,p记录物品价值。
while(cin>>n>>V)
{
if(n == 0 && V == 0)
break;
for(int i = 0; i <= n; i++)
{
w[i] = 0, p[i] = 0;
for(int j = 0; j <= n; ++j)
{
f[i][j] = 0;
}
}
for(int i = 1; i <= n; ++i)
cin>>w[i]>>p[i];
/*这里包含对i-n件物品,容量分别为它的w[i]到总容量V区间的选择情况*/
for(int i = 1; i <= n; ++i)
{
for(int t = w[i]; t <= V; t++)
{
f[i][t] = max(f[i-1][t], f[i-1][t-w[i]]+p[i]);
}
}
cout<<f[n][V]<<endl;
}
return 0;
}
优化情况:我们可以知道,我们必须要做出n次选择,所以外层n次循环是必不可少的,对于上面代码的内层循环,表示当第i个商品要放入容量为v(v = w[i]....V)的背包时所获得的价值,即先对子问题求解,这个也是必不可少的,所以时间复杂度为O(nV),这个已不能进一步优化,但是我们可以对空间进行优化。
由于我们用f[n][V]表示最大价值,但是当物品和背包容量比较大时,这种用法会占用大量的空间,那么我们是不是对此可以进一步优化呢?
现在考虑如果我们只用一位数组f[v]来表示f[i][v],是不是同样可以达到效果?我们由上述可知f[i][v]是由f[i-1][v]和f[i-1][v-w[i]]这两个子问题推得,事实上第i次选择时,我们虽用到前i-1次的最优结果,但是前i-1次选择的最优结果,已经保存在做出第i-1次选择后的结果中,即第i次的结果只用到了第i-1次选择后的状态,因此我们可以只用一维数组来维持每次选择的结果,怎么维持?也就是当第i次选择时,我们怎么得到f[i-1][v]和f[i-1][v-w[j]]这两种状态,即第i次求f[v]时,此时f[v]和f[v-w[i]]表示的是不是f[i-1][v]和f[i-1][v-w[j]],事实上我们只需要将内层循环变为从V到w[j]的逆向循环即可满足要求。
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
const int MAXN = 100;
int main()
{
int n, V;
int f[MAXN];
int w[MAXN], p[MAXN];
while(cin>>n>>V)
{
if(n == 0 && V == 0)
break;
memset(f, 0, sizeof(f)); //初始化为0
memset(w, 0, sizeof(w));
memset(p, 0, sizeof(p));
for(int i = 1; i <= n; ++i)
cin>>w[i]>>p[i];
for(int i = 1; i <= n; ++i)
{
for(int v = V; v >= w[i]; --v) //这里要注意,要从后往前推.for(int v = w[i]; v <= V; ++v)不可。
{ //因为假设此时我们用的f[6]和f[3]相当于是f[1][6]和f[1][3],而不是f[0][6]和f[0][3].因为我们在上述代码中隐含对[0-n]的 二维数组情况.
f[v] = max(f[v], f[v-w[i]]+p[i]);
}
}
cout<<f[V]<<endl;
}
return 0;
}
参考博客:
点击打开链接。