首页 > 代码库 > 0/1背包问题(动态规划)

0/1背包问题(动态规划)

0/1背包问题:

现有n种物品,对1<=i<=n,已知第i种物品的重量为正整数Wi,价值为正整数Vi,背包能承受的最大载重量为正整数W,现要求找出这n种物品的一个子集,使得子集中物品的总重量不超过W且总价值尽量大。(注意:这里对每种物品或者全取或者一点都不取,不允许只取一部分)

根据问题描述,可以将其转化为如下的约束条件和目标函数:

技术分享

于是,问题就归结为寻找一个满足约束条件(1),并使目标函数式(2)达到最大的解向量。

0-1背包问题可以看作是寻找一个序列,对任一个变量 的判断是决定=1还是=0.在判断完之后,已经确定了,在判断时,会有两种情况:

(1)          背包容量不足以装入物品i,则=0,背包的价值不增加;

(2)          背包的容量可以装下物品i,则=1,背包的价值增加。

这两种情况下背包的总价值的最大者应该是对判断后的价值。令表示在前i个物品中能够装入容量为j的背包的物品的总价值,则可以得到如下的动态规划函数:

技术分享      

     式(1)说明:把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0.式(2)第一个式子说明:如果第i个物品的重量大于背包的容量,则装入第i个物品得到的最大价值和装入第i-1个物品得到的最大价值是相同的,即物品i不能装入背包中;第二个式子说明:如果第i个物品的重量小于背包的容量,则会有两种情况:(1)如果把第i个物品装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为的背包中的价值加上第i个物品的价值;(2)如果第i个物品没有装入背包,则背包中物品的价值就是等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。

     我们可以一步一步的解出我们所需要的解。第一步,只装入第一个物品,确定在各种情况下背包能得到的最大价值;第二步,只装入前两个物品,确定在各种情况下的背包能够得到的最大价值;依次类推,到了第n步就得到我们所需要的最优解了。最后,便是在容量为W的背包中装入n个物品时取得的最大价值。为了确定装入背包的具体物品,从的值向前寻找,如果>,说明第n个物品被装入了背包中,前n-1个物品被装入容量为的背包中;否则,第n个物品没有装入背包中,前n-1个物品被装入容量为W的背包中。依此类推,直到确定第一个物品是否被装入背包为止。由此,我们可以得到如下的函数:.

技术分享

     根据动态规划函数,用一个的二维数组C存放中间变量,表示把前i个物品装入容量为j的背包中获得的最大价值。

以下为一个例子

     假设最大容量M=10,物品个数N=3,物品大小w{3,4,5},物品价值p{4,5,6}。

技术分享

从背包容量为0开始,1号物品先试,0,1,2,的容量都不能放.所以置0,背包容量为3则里面放4.这样,这一排背包容量为4,5,6,....10的时候,最佳方案都是放4.假如1号物品放入背包.则再看2号物品.当背包容量为3的时候,最佳方案还是上一排的最价方案c4.而背包容量为5的时候,则最佳方案为自己的重量5.背包容量为7的时候,很显然是5加上一个值了。加谁??很显然是7-4=3的时候.上一排 c3的最佳方案是4.所以。总的最佳方案是5+49.这样.一排一排推下去。最右下放的数据就是最大的价值了。(注意第3排的背包容量为7的时候,最佳方案不是本身的6.而是上一排的9.说明这时候3号物品没有被选.选的是1,2号物品.所以得9.)

代码:

#include<iostream>
#include<algorithm>
#include <vector>
#include<string.h>
#include<ctype.h>
#include<math.h>
using namespace std;
const int n=3;   //物品数量
const int W=10;  //背包容量
int c[n+1][W+1];
void DP(int v[], int w[])
{
	int i,j;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= W; j++)
		{
			if (w[i] > j)
				c[i][j] = c[i-1][j];
			else
				c[i][j]=max(c[i-1][j-w[i]]+ v[i],c[i-1][j]);
		}
	}
}
int main()
{
	int w[4]={0,3,4,5};
	int v[4]={0,4,5,6};
	memset(c,0,sizeof(c));
	DP(v,w);
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=W;j++)
			printf("%-3d ",c[i][j]);
		puts("");
	}
}

运行结果如图:

技术分享

这里是用二位数组存储的,可以把空间优化,用一维数组存储。


用c[0..j]表示,c[j]表示把前i件物品放入容量为j的背包里得到的价值。把i从1~n(n件)循环后,最后f[j]表示所求最大值。

*这里c[j]就相当于二位数组的c[i][j]。那么,如何得到c[i-1][j]和c[i-1][j-w[i]]+w[j]?(重点!思考)
首先要知道,我们是通过i从1到n的循环来依次表示前i件物品存入的状态。即:for i=1..N
现在思考如何能在是c[j]表示当前状态是容量为j的背包所得价值,而又使c[j]和c[j-w[i]]+w[i]表示前一状态的价值?

逆序!

#include<iostream>
#include<algorithm>
#include <vector>
#include<string.h>
#include<ctype.h>
#include<math.h>
using namespace std;
const int n=3;   //物品数量
const int W=10;  //背包容量
int c[W+1];
void DP(int v[], int w[])
{
	int i,j;
	for (int i = 1; i <= n; i++)
	{
		for (int j = W; j >=0; j--)
		{
			if(j>=w[i])
				c[j]=max(c[j-w[i]]+ v[i],c[j]);
			printf("%-3d ",c[j]);
		}
		cout<<endl;
	}
}
int main()
{
	int w[4]={0,3,4,5};
	int v[4]={0,4,5,6};
	memset(c,0,sizeof(c));
	DP(v,w);
}

技术分享











0/1背包问题(动态规划)