01背包问题

原文链接: javacodegeeks 翻译: ImportNew.com hejiani

译文链接: http://www.importnew.com/13072.html

问题

假定背包的最大容量为W,N件物品,每件物品都有自己的价值和重量,将物品放入背包中使得背包内物品的总价值最大。

 

可以想象这样一个场景——小偷在屋子里偷东西,他带着一只背包。屋子里物品数量有限——每件物品都具有一定的重量和价值——珠宝重量轻但价值高,桌子重但价值低。最重要的是小偷背包容量有限。很明显,他不能把桌子分成两份或者带走珠宝的3/4。对于一件物品他只能选择带走或者不带走。

示例:

Knapsack Max weight : W = 10 (units)
Total items         : N = 4
Values of items     : v[] = {10, 40, 30, 50}
Weight of items     : w[] = {5, 4, 6, 3}

解决方法:从示例数据大致估算一下,最大重量为10时背包能容纳的物品最大价值为50+40=90,重量为7。

最佳的解决方法是使用动态规划——先得到该问题的局部解然后扩展到全局问题解。

构建物品X在不同重量时的价值数组V(Value数组):

V[N][W] = 4 rows * 10 columns

初始情况一:对于第0列,它的含义是背包的容量为0。此时物品的价值呢?没有。因此,第一列都填入0。该矩阵中的每个值的求解都代表一个更小的背包问题。

初始情况二:对于第0行,它的含义是屋内没有物品。那么没有任何物品的背包里的价值多少呢?还是没有!所有都是0。

步骤:

1、现在,开始填入数组每一行的值。第1行第1列代表什么含义呢?对于第一个物品,可以把重量为1的该物品放入背包吗?不行。第一个物品的重量是5。因此,填入0。实际上直到第5列(重量5)之前都应该填入0。
2、对于第1行的第5列(重量5),意味着将物品1放入背包。填入10(注意,这是Value数组):

3、继续,对于第6列,我们可以再放入重量为1(重量值-物品的重量)的物品吗。我们现在只考虑物品1。由于我们加入物品1之后就不能再加入额外的重量,可以很直观地看到其余的列都应该还是相同的值。

4、接着,有意思的事情就要出现了。在第3行第4列,此时重量为4。

需要作以下判断:

  1. 可以放入物品2吗——可以。物品2的重量为4。
  2. 不加入物品2的话当前已有物品的重量的Value值是否更大——查看相同重量时的前一行的值。不是。前一行的值为0,重量4时不能放入物品1。
  3. 在这个重量时可以放入两件物品使得价值最大吗?——不能。此时重量减去物品2的重量后为0。

为什么是前一行?

简单来说,重量为4的前一行的值本身就是个更小的背包问题解,它的含义是到该重量时背包内物品的最大价值(通过遍历物品得到)。

举个例子:

  1. 当前物品价值 = 40
  2. 当前物品重量 = 4
  3. 剩余重量 = 4-4 = 0
  4. 查看上面的行(物品1或者其余行的值)。剩余容量为0时,可以再容纳物品1吗?对于该给定的重量值上面的行还有任何值吗?

计算过程如下:

1) 计算不放入该物品时该重量的最大价值:

previous row, same weight = 0
=> V[item-1][weight]

 

2) 计算当前物品的价值 + 可以容纳的剩余重量的价值

Value of current item + value in previous row with weight 4 (total weight until now (4) - weight of the current item (4))
=> val[item-1] + V[item-1][weight-wt[item-1]]

找到二者之中的最大值40(0和40)。

3) 下一次最重要的位置为第2行第9列。意味着此时重量为9,放入两件物品。根据示例数据现在可以放入两件物品。我们作了以下判断:

1. The value of the current item = 40

2. The weight of the current item = 4

3. The weight that is left over = 9 - 4 = 5

4. Check the row above.  At the remaining weight 5, are we able to accommodate Item 1.

计算如下:

  1. 不加入该物品时该重量的最大价值:
previous row, same weight = 10

计算当前物品的价值+可以容纳的剩余重量的价值

Value of current item (40)
+ value in previous row with weight 5 (total weight until now (9) - weight of the current item (4))
= 10

10vs50 = 50。

解决了所有的子问题之后,返回V[N][W]的值——4件物品重量为10时:

复杂度

解法的复杂度非常直观。在N次循环中有W次循环 => O(NW)

实现

/**
 * 0-1背包问题
 * 问题的关键是对于每一个物品,都只有放进去和不放进去两种选择
 * @param weight 物品的重量
 * @param value 物品的价值
 * @param num 物品总数
 * @param capacity 背包容量
 * @return 最大价值
 */
public static int ZeroOneBackpack(int[] weight, int[] value, int num, int capacity)
{
	//mp[i][j]表示可选物品为前i个,容量为j时的最大价值
	int[][] mp = new int[num+1][capacity+1];
	//可选物品为0和容量为0时,最大价值都为0
	for(int i=0; i<capacity+1; i++)
	{
		mp[0][i] = 0;
	}
	for(int i=0; i<num+1; i++)
	{
		mp[i][0] = 0;
	}
	
	for(int i=1; i<num+1; i++)
	{
		for(int j=1; j<capacity+1; j++)
		{
			if(weight[i-1] > j)//物品i的重量大于容量
			{
				//物品i放不进去,最大价值是前i-1个物品的最大价值
				mp[i][j] = mp[i-1][j];
			}
			else
			{
				//i可以放进去
				//比较把i放进去和不放进去的最大价值
				mp[i][j] = Math.max(value[i-1]+mp[i-1][j-weight[i-1]], mp[i-1][j]);
			}
		}
	}
	return mp[num][capacity];
}

2.完全背包问题:

完全背包问题与01背包问题的区别在于每一件物品的数量都有无限个,而01背包每件物品数量只有一个。

问题解法其实和01背包问题一样,只是初始化的值和递推公式需要稍微变化一下。初始化时,当只考虑一件物品时,V[1][j] = j/weight[0]。 递推公式计算时,V[i][y] = max{V[i-1][y], (V[i][y-weight[i]]+value[i])},注意这里当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是V[i][y-weight[i]]+value[i], 而不是V[i-1][y-weight[i]]+value[i]。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值