01背包问题及空间优化

一、题目描述:


有n件物品,每件物品占用的空间为w[i], 价值为p[i]。

有容量为 V 的背包。求在容量允许的范围下,背包装入物品的最大价值。


用dp[i][v] 表示 用剩余容量为v的背包,来装前i件物品,可以达到的最大价值。

那么 dp[i][0] = 0;

在当前为i,v的情况下,考察第i件物品。有两种情况。

1、如果i物品的体积大于v,根本装不下了。没得选,只能放弃。则最大价值不变。相当于,用前i-1件物品来填满v。dp[i][v] = dp[i-1][v];

2、如果i物品的体积不大于v,可以选择装入i或者放弃i。

如果装入i,则剩余的容量变为v-w[i]。整个问题变为,先把i装入,再用v-w[i]的空间去装前i-1件物品。

dp[i][v] = dp[i-1][v-w[i]] + p[i]

如果放弃i,则dp[i][v] 依然是dp[i-1][v] 和情况1相同。


状态转移方程就出来了:

if( v >= w[i] )  dp[i][v] = max { dp[i-1][v] , dp[i-1][ v-w[i] ] + p[i] }

elsedp[i][v] = dp[i-1][v]

代码如下:

int main()
{
	int n = 5;
	int sum = 12;

	int w[5] = {5, 4, 7 ,2 ,6};
	int p[5] = {12,3,10, 3, 6};
/*
	dp[i][V] 表示前i个,放入体积为V的背包内,获得的最大收益。

	dp[0][0] = {0};		dp[i][V] = max{ dp[i-1][V], dp[i][V-w[i]] + p[i] }
*/
	int dp[6][13];

	int i,v;

	memset(dp,0,sizeof(dp));

	for(i = 1; i <= n ; i++)
	{
		//for(v = w[i-1];v<=sum;v++)	这是错的!因为如果v < w[i]的话,dp[i][v]等于dp[i-1][V]。
		for(v = 1;v <= sum; v++)
		{
			if(v >= w[i-1])	dp[i][v] = max_2( dp[i-1][v-w[i-1]] + p[i-1],dp[i-1][v] );
			else			dp[i][v] = dp[i-1][v];
		}
	}

	 for(i = 1; i <= n ; i++)
	{
		for(v = 0;v<=sum;v++)
		{
			printf("%2d ",dp[i][v]);
		}
		printf("\n");
	}
	return 0;
}


可以看到,当V=0 和 1时,任何物品都不能放下,所以 前两列的数都是0.

当v=2时,只能放下第四件物品,所以前三件的时候,依然是0,第四件以后,开始变化。


二、空间优化

注意到  dp[i][v]只用到了上一行的值。dp[i-1][v]  dp[i-1][ v-w[i] ]


考虑用dp[v]来表示之前同样的状态。

那么在第i次循环开始之前,dp[j] = dp[i-1][j]         0<= j <= V

第i次循环之后,dp[j]才代表dp[i][j]。

那么在计算第i次循环的dp值时,两种选择变为下面的情况。

如果放弃物品i,即dp[i][v] = dp[i-1][v],则dp[v]保持不变即可。

如果放入物品i,即dp[i][v] = dp[i-1][ v-w[i] ], 很幸运,当前的dp[v-w[i]]就是dp[i-1][v-w[i]]。


OK,代码出来了。

for (i=0;i<n;i++)
{
	for(v = 0;v<=sum;v++)
	{
		if( v < w[i])dp[v] = dp[v] ;//  第i-1轮的值直接更新到了第i轮
		elsedp[v] = max{ dp[v],dp[ v-w[i] ] };
	}
}




有个问题出现了,我们必须保证,在计算dp[v]的时候,dp[ v -w[i] ]依然等于dp[i-1][ v-w[i] ]。

但是看代码中,明显不是这样。因为v从小到大计算,dp[ v-w[i]] 一定在 dp[v]之前改变了。

我们的代码相当于在执行:dp[i][v] = max{ dp[i-1][v],dp[i][ v-w[i] ] };(注意是dp[i][ v-w[i] ])


int main()
{
	int w[5] = {5, 4, 7 ,2 ,6};
	int p[5] = {12,3,10, 3, 6};

	int i,v,n = 5;
	int sum = 12;

	int dp[13];

	memset(dp,0,sizeof(dp));

	for(i=0;i<n;i++)
	{
		for(v=sum;v>=w[i];v--)
		{
			dp[v] = max_2( dp[v], dp[v-w[i]]+p[i] );
		}
	}
	
	for(i = 0;i<=sum;i++)
	{
		printf("%d ",dp[i]);
	}
	return 0;
}


第一次循环之前,i=0,dp[v] 全体为0 . 


每一次循环得到的值,和上个表格中的第i行是一致的。



只是记录自己的一些理解和思考。不能保证代码完全正确,因为没有经过大量数据测试。


欢迎指正!


PS:CSDN的博客简直是垃圾。。。写作体验真的很烂。。。准备换地方。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值