01背包基础详解(附求解选择的物品)

在看了背包九讲以及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]就是:



下面就很容易给出代码

<span style="font-family:Microsoft YaHei;font-size:14px;">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]);
}</span>

到这个地方应该是没有问题的。

这段代码的空间复杂度和时间复杂度都是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

01pack-1

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--;  
}  




posted @ 2014-08-04 08:08  SixDayCoder  阅读(1340)  评论(1编辑  收藏  举报