LintCode 背包问题

来源:互联网 发布:传奇一条龙源码 编辑:程序博客网 时间:2024/05/21 17:03

背包问题 

 

注意事项

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

样例

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

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

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

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



首先给一个背包问题通俗易懂的讲解,这里使用的是从杰大佬的一个很形象的讲解,附上他的博客链接:
http://blog.csdn.net/mu399/article/details/7722810

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的背包,如何让背包里装入的物品具有最大的价值总和?

nameweightvalue12345678910a26066991212151515b23033669991011c65000666661011d54000666661010e460006666666

只要你能通过找规律手工填写出上面这张表就算理解了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 hereif (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]);elseresult[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 hereif (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]);elseresult[i % 2][j] = result[abs(i % 2 - 1)][j];}}return result[A.size() % 2 == 0 ? 1 : 0][m];}



0 0
原创粉丝点击