在看了背包九讲以及http://blog.csdn.net/wumuzi520/article/details/7014559这篇博文之后,对于01背包有了更好的理解,下面整理一下以备以后回顾之用。
首先,说明一下什么是01背包问题。
一共有N个物品,每个物品的价值是w[i],重量是c[i],给你一个容量为V的背包,问从这N个物品中选取若干件所能获得的最大价值是多少。
我们考虑一下第i件物品。以数组f[i][j]表示前i件物品,在j的背包容量下可以获得的最大价值。
很明显的是,对于第i件物品,只有取或者不取两种状态,那么f[i][j]就是:
下面就很容易给出代码
for(int i = 1 ; i <= N ; i ++)
{
for(int j = 0 ; j <= V ; j ++)
f[i][j] = max(f[i-1][j],f[i-1][j-c[i]]+w[i]);
}
到这个地方应该是没有问题的。
这段代码的空间复杂度和时间复杂度都是O(VN),我们想优化一下。时间复杂度不可能优化了....(我觉得不可能),背包九讲的作者想到的就是从空间复杂度上优化。即只用一维数组f[j]来取代f[i][j]。
可能我智商不行....原作者讲的我看不太懂。看了一些列的资料以后才慢慢明白一点。
我们想用f[j]来取代f[i][j],那么就必须明确f[j]存储的是什么状态。
定义:f[j]表示的是在背包容量为j的情况下,N件物品所能取得的最大价值。
(f[i][j]表示的是前i件物品在背包容量为j的情况下所能取得的最大i价值)
其实看上边这一段代码,容易看出,f[i][j]的状态只和f[i-1][j]和f[i-1][j-c[i]]有关,即只和i-1时刻的状态有关系,我们用一个一维数组
f[]来储存各个时刻的状态(假设不同的i代表不同的时刻)
(f[j]表示的是把前i件物品放进背包容量为j的背包中,i从1-N循环完毕后就是N件物品选取若干件放入背包容量为j的背包中的价值)
那么给一组测试数据结合例子看看
10,3, 3,4 4,5, 5,6
C[v]从物品i=1开始,循环到物品3,期间,每次逆序得到容量v在前i件物品时可以得到的最大值。
(看着这个图自己写写画画的确明白必须是逆序的...但是自己想还是想不到的...)
所以代码如下:
for(int i = 0 ; i < N ; i++)
{
for(int j = V ; j >= c[i] ; j++)
f[j] = max(f[j],f[j-c[i]]+w[i]);
}
附注:
有的问题可能会让我们把取的物品编号打印出来...
对于f[i][j]这种状态转移方程其实很好判断,只要F[i][j]==F[i-1][j-C[i]]+W[i]说明包里面有第i件物品,然后打印编号即可。
如果是f[i]这种方式,是没有办法用F[j]==F[j-C[i]]+W[i]来判断的...这时候可以找一个二维数组path[i][j]记录下来...代码如下:
for(int i = 0 ; i < N ; i++)
{
for(int j = V ; j >= c[i] ; j++)
if(f[j-c[i]]+w[i] > f[j])
{
f[j] = f[j-c[i]]+w[i];
path[i][j] = 1;
}
}
打印时,逆序寻找(必须是逆序的...)
代码如下:
int i = N, j = V;
while(i > 0 && j > 0)
{
if(Path[i][j] == 1)
{
cout << c[i-1] << " ";
j -= c[i-1];
}
i--;
}