0-1背包问题

0-1背包问题:有一个贼在偷窃一家商店时,发现有n件物品,第i件物品价值vi元,重wi磅,此处vi与wi都是整数。他希望带走的东西越值钱越好,但他的背包中至多只能装下W磅的东西,W为一整数。应该带走哪几样东西?这个问题之所以称为0-1背包,是因为每件物品或被带走;或被留下;小偷不能只带走某个物品的一部分或带走同一物品两次。

  在分数(部分)背包问题(fractional knapsack problem)中,场景与上面问题一样,但是窃贼可以带走物品的一部分,而不必做出0-1的二分选择。可以把0-1背包问题的一件物品想象成一个金锭,而部分问题中的一件物品则更像金沙。

  两种背包问题都具有最优子结构性质。对0-1背包问题,考虑重量不超过W而价值最高的装包方案。如果我们将商品j从此方案中删除,则剩余商品必须是重量不超过W-wj的价值最高的方案(小偷只能从不包括商品j的n-1个商品中选择拿走哪些)。

  虽然两个问题相似,但我们用贪心策略可以求解背包问题,而不能求解0-1背包问题,为了求解部分数背包问题,我们首先计算每个商品的每磅价值vi/wi。遵循贪心策略,小偷首先尽量多地拿走每磅价值最高的商品,如果该商品已全部拿走而背包未装满,他继续尽量多地拿走每磅价值第二高的商品,依次类推,直到达到重量上限W。因此,通过将商品按每磅价值排序,贪心算法的时间运行时间是O(nlgn)。

  为了说明贪心这一贪心策略对0-1背包问题无效,考虑下图所示的问题实例。此例包含3个商品和一个能容纳50磅重量的背包。商品1重10磅,价值60美元。商品2重20磅,价值100美元。商品3重30磅,价值120美元。因此,商品1的每磅价值为6美元,高于商品2的每磅价值5美元和商品3的每磅价值4美元。因此,上述贪心策略会首先拿走商品1。但是,最优解应该是商品2和商品3,而留下商品1。拿走商品1的两种方案都是次优的。

  但是,对于分数背包问题,上述贪心策略首先拿走商品1,是可以生成最优解的。拿走商品1的策略对0-1背包问题无效是因为小偷无法装满背包,空闲空间降低了方案的有效每磅价值。在0-1背包问题中,当我们考虑是否将一个商品装入背包时,必须比较包含此商品的子问题的解与不包含它的子问题的解,然后才能做出选择。这会导致大量的重叠子问题——动态规划的标识。

 

 

例子:0-1背包问题。总共有三件物品,背包可容纳5磅的东西,物品1重1磅,价值60元。物品2重2磅,价值100元,物品3重3磅,价值120元。怎么才能最大化背包所装物品的价值。

解答:我们可以得出物品一每磅价值60元,大于物品二的每磅50元和物品3的每磅40元。如果按照贪心算法的话就要取物品1。然而最优解应该取的是物品2和3,留下了1.

   在0-1背包问题中不应取物品1的原因在于这样无法将背包填满,空余的空间就降低了货物的有效每磅价值。

   我们可以利用动态规划来解0-1背包问题。

   假设c[i]表示第i件物品的重量,w[i]表示第i件物品的价值,f[i][j]表示背包容量为j,可选物品为物品1~i时,背包能获得的最大价值。

     用动态规划求解即先求出背包容量较小时能获得的最大价值,然后根据背包容量较小时的结果求出背包容量较大时的结果,也就是一个递推的填表过程。

   当没有可选物品时,背包能获得的最大价值为0。即表格可初始化为

   

   填表过程(即状态转移方程)是:

   

  "j<c[i]"表示第i件物品的重量大于当前背包的容量j,此时显然不放第i件物品。下面解释上述方程在“其它“情况下的意义:”将前i件物品放入容量为j背包中“这个问题,如果只考虑第i件物品放或者不放,那么就可以转化为只涉及前i-1件物品的问题,即:

    1. 如果不放第i件物品,则问题转化为只涉及”前i-1件物品放入容量为j的背包中“

    2. 如果放第i件物品,则问题转化为”前i-1件物品放入剩下的容量为j-c[i]的背包中“,此时能获得的最大价值就是f[i-1][j-c[i]]再加上通过放入第i件物品获得的价值获得的价值w[i]。

    则在”其他“情况下,f[i][j]就是1、2中最大的那个值。

    显然,可以从左下角利用状态转移方程依次逐行填表,得到f[3][5](表示可选物品为1、2、3,且背包容量为5时,能获得的最大价值),可见动态规划的确即为一个递推的过程。填表后如下表所示:

    

背包问题初始化

  求最优解的背包问题中,事实上有两种不太相同的问法。有的题目要求”恰好装满背包“时的最优解,有的题目则没有要求必须把背包装满。这两种问法的区别是求解时的初始化不同。

  如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0][0]为0,其他f[0][1~V]均设为-∞ ,这样就可以包装最终得到的解释一种恰好装满背包的最优解。

  如果并没有要求必须是把背包装满,而是只希望价格尽量大,初始化时应该将f[0][1~V]全部设为0.

  这是为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可以在什么也不装且价值为0的情况下被”恰好装满“,其他容量的背包均没有合法的解,属于未定义的状态,应该被赋值为-∞。如果背包并非被必须装满,那么任何容量的背包都有一个合法解”什么都不装“,这个解的价值为0,所以初始状态的值也就全部为0了。

/**0-1 knapsack d(i, j)表示前i个物品装到剩余容量为j的背包中的最大重量**/
#include<cstdio>
using namespace std;
#define MAXN 1000
#define MAXC 100000

int V[MAXN], W[MAXN], x[MAXN];
int d[MAXN][MAXC];

int main(){
	freopen("data.in", "r", stdin);
	freopen("data.out", "w", stdout);
	int n, C;
	while(scanf("%d %d", &n, &C) != EOF){
		for(int i=0; i<n; ++i)	scanf("%d %d", &V[i], &W[i]);
		for(int i=0; i<n; ++i)	x[i] = 0; //初始化打印方案
		
		for(int i=0; i<=n; ++i){
			for(int j=0; j<=C; ++j){
				d[i][j] = i==0 ? 0 : d[i-1][j];
				if(i>0 && j>=V[i-1])	d[i][j] >?= d[i-1][j-V[i-1]]+W[i-1]; 有问题吧?V[i] W[i]
			}
		}
		printf("%d\n", d[n][C]);
		
		//输出打印方案
		int j = C;
		for(int i=n; i>0; --i){
			if(d[i][j] > d[i-1][j]){
				x[i-1] = 1;
				j = j - V[i-1];
			}
		}
		for(int i=0; i<n; ++i)	printf("%d ", x[i]);
		printf("\n");
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值