LintCode 背包问题

背包问题 
 

注意事项

你不可以将物品进行切割。

样例

如果有4个物品[2, 3, 5, 7]

如果背包的大小为11,可以选择[2, 3, 5]装入背包,最多可以装满10的空间。

如果背包的大小为12,可以选择[2, 3, 7]装入背包,最多可以装满12的空间。

函数需要返回最多能装满的空间大小。



首先给一个背包问题通俗易懂的讲解,这里使用的是从杰大佬的一个很形象的讲解,附上他的博客链接:

01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻。

01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] }

f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值。
Pi表示第i件物品的价值。
决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ?

题目描述:

有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?

nameweightvalue12345678910
a26066991212151515
b23033669991011
c65000666661011
d54000666661010
e460006666666

只要你能通过找规律手工填写出上面这张表就算理解了01背包的动态规划算法。

首先要明确这张表是至底向上,从左到右生成的。

为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。

对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。

同理,c2=0,b2=3,a2=6。

对于承重为8的背包,a8=15,是怎么得出的呢?

根据01背包的状态转换方程,需要考察两个值,

一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi;

在这里,

 f[i-1,j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值

f[i-1,j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值

f[i-1,j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6

由于f[i-1,j-Wi]+Pi = 9 + 6 = 15 大于f[i-1,j] = 9,所以物品a应该放入承重为8的背包



-------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------------------------

本题由于没有物品价值这个维度,但是可以直接把物品的重量当做物品的价值,最后依然是求解出背包中最大价值的问题。

上面的解释中数组的更新是从底向上不是很直观,我们从上到下其实是一样的道理,二维数组result[A.size()][m+1] 中的result[i][j]表示在背包空间为j,有A中前i个物品可选的情况下,最大的价值是什么。

f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] }


其中   value1= f[i-1,j]  意味着第i个物品不放到背包中,所以背包中的最大价值还是和i-1个候选物品时一样
而 value2=   f[i-1,j-Wi]+Pi( j >= Wi) 表示我的背包如果一定要放入第i个物品的话,那么我先给i号物品预留wi的空间,所以背包的总价值是j-wi的空间情况下i-1个候选物品的最大价值+第i个物品的价值。

通过比较value1和value2的值,我就可以做出决策来让在空间为j,候选物品为前i个,的情况下的最大价值。

所以我们可以得到我们的程序如下:
但是这个程序在大数据量的时候会爆内存,所以我们需要继续优化我们的代码↓
int backPack(int m, vector<int> A) {
	// write your code here
	if (A.size() == 0)
		return 0;
	if (m == 0)
		return 0;

	vector<vector<int>> result(A.size(), vector<int>(m + 1, 0));

	for (int i = 0; i <= m; i++)
	{
		if (i >= A[0])
			result[0][i] = A[0];
	}

	for (int i = 1; i < A.size(); i++)
	{
		for (int j = 0; j <= m; j++)
		{
			if (j - A[i] >= 0)
				result[i][j] = max(result[i - 1][j], result[i- 1][j - A[i]] + A[i]);
			else
				result[i][j] = result[i- 1][j];
		}
	}
	return result[A.size()][m];
}


同时通过观察状态转移方程我们可以看到,每一行的计算其实只会用到上一行的数据,所以其实我们只需要一个2行,m+1列的数组就能够完成计算了,所以这里需要一些小的技巧,利用i的奇偶特性来确定当前使用哪一行来保存数据,具体的代码如下:

int backPack(int m, vector<int> A) {
	// write your code here
	if (A.size() == 0)
		return 0;
	if (m == 0)
		return 0;

	vector<vector<int>> result(2, vector<int>(m + 1, 0)); 
	//本来应该用二维数组存储,但是其实每一次计算只会用到上一行的数据,所以可以直接压缩到一个两行的数组

	for (int i = 0; i <= m; i++)
	{
		if (i >= A[0])
			result[0][i] = A[0];
	}

	for (int i = 1; i < A.size(); i++)
	{
		for (int j = 0; j <= m; j++)
		{
			if (j - A[i] >= 0)
				result[i % 2][j] = max(result[abs(i % 2 - 1)][j], result[abs(i % 2 - 1)][j - A[i]] + A[i]);
			else
				result[i % 2][j] = result[abs(i % 2 - 1)][j];
		}
	}
	return result[A.size() % 2 == 0 ? 1 : 0][m];
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值