0-1背包问题

背包问题,常见的有三种类型:基本的0-1背包、完全背包和多重背包、二维背包 

        首先是基本的0-1背包问题。因为这里的物品一般指花瓶、玉器什么的,要么拿、要么不拿,只有0和1两种状态,所以也叫0-1背包。0-1背包虽然简单,却很重要,是“万法之源”,是其他几类问题的基础。 

        初学者有时会认为,0-1背包可以这样求解:计算每个物品的Vi/Wi,然后依据Vi/Wi的值,对所有的物品从大到小进行排序。其实这种贪心方法是错误的。如下表,有三件物品,背包的最大负重量是50,求可以取得的最大价值。 

        其实,0-1背包是DP的一个经典实例,可以用动态规划求解。

DP求解过程可以这样理解:对于前i件物品,背包剩余容量为j时,所取得的最大价值(此时称为状态3)只依赖于两个状态。

状态1:前i-1件物品,背包剩余容量为j。在该状态下,只要不选第i个物品,就可以转换到状态3。

状态2:前i-1件物品,背包剩余容量为j-w[i]。在该状态下,选第i个物品,也可以转换到状态3。

因为,这里要求最大价值,所以只要从状态1和状态2中选择最大价值较大的一个即可。 

状态转换方程:

dp( i,j ) = Max( dp( i-1, j ), dp( i-1, j-w[i] ) + v[i] )

dp( i,j )表示前i件物品,背包剩余容量为j时,所取得的最大价值。 

        还是结合上面的例子来说明吧。有三件物品,背包的最大负重量是50,求可以取得的最大价值。下图表示了DP自上而下的求解过程。

编程实现:

       一般来说,有了状态方程,直接编程实现就game over。dp( i,j ),用一个二维数组来实现,然后用一个两层循环就可以了。不过,有时选择的物品很多,背包的容量很大,这时要用二维数组往往是不现实的。这里有一个方法,可以进行空间压缩,然后使用一维数组实现。

       还是结合上面的例子,有三件物品,背包的最大负重量是5,求可以取得的最大价值。为了方面说明,物品weight依次为:1,2,3。二维数组下的求解顺序,物品数1--->n, 背包容量1--->w。如图,要使用一维数组,背包容量要采用倒序,即w--->1, 只有这样对于方程dp( j ) = Max( dp( j ), dp (j-w[i] ) + v[i] ),才能达到等式左边才表示i,而等式右边表示i-1的效果。POJ对于题目:3624。下面附代码。

        完全背包和多重背包。有了基本的0-1背包基础,下面的东西也就好理解了。 完全背包,指每个物品有无限多个。 多重背包,指每个物品的数量是有限的。当然,这时的问题不再是拿与不拿,而是拿多少的问题,当然不能超过背包容量。 

 根据上述分析的最优解的结构递归地定义问题最优解。设c[i,w]表示背包容量为w时,i个物品导致的最优解的总价值,得到下式。显然要求c[n,w]。

编码实现:

如果直接编码,用三层循环,往往会超时。这样有一种很有效的压缩方式:二进制压缩。把原来的物品按照2的n次方进行重新组合。用1、2、4、8…进行组合,可以组合出任意的数字。POJ题目:1276

#include <iostream>
using namespace std;

/***
c[i][w]表示背包容量为w时,i个物品导致的最优解的总价值,大小为(n+1)*(w+1)
v[i]表示第i个物品的价值,大小为n
w[i]表示第i个物品的重量,大小为n
***/

void DP(int n, int W, int c[][18], int *v, int *wei)
{
	memset(*c, 0, (W+1)*sizeof(int));
	for (int i = 1; i <= n; i++)
	{
		c[i][0] = 0;
		for (int w = 1; w <= W; w++)
		{
			if (wei[i-1] > w)	//此处比较是关键
			{
				c[i][w] = c[i-1][w];
			}
			else
			{
				int temp = c[i-1][w-wei[i-1]] + v[i-1];	//注意wei和v数组中的第i个应该为wei[i-1]和v[i-1]
				if (c[i-1][w] > temp)
				{
					c[i][w] = c[i-1][w];
				}
				else 
					c[i][w] = temp;
			}
		}
	}
}

void findPath(int c[][18], int *x, int *wei, int n, int W)
{	
	int w = W;
	for (int i = n; i >= 2; i--)
	{
		if (c[i][w] == c[i-1][w])
		{
			x[i-1] = 0;
		}
		else
		{
			x[i-1] = 1;
			w = w - wei[i-1];
		}
	}
	if (c[1][w] == 0)
		x[0] = 0;
	else
		x[0] = 1;
}

int main()
{
	int n = 5;
	int W = 17;
	int w[] = {3, 4, 7, 8, 9};
	int v[] = {4, 5, 10, 11, 13};
	int c[6][18] = {0};
	DP(n, W, c, v, w);
	cout<<c[5][17]<<endl;
	int x[5];
	findPath(c, x, w, n, W);
	for (int i = 0; i < n; i++)
		cout<<x[i]<<" ";
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值