背包问题与硬币问题的一个总结

本文参考了以下链接:
http://blog.csdn.net/binling/article/details/39735445
http://blog.csdn.net/wangbaochu/article/details/53099953

背包问题最基本的有4种(还有更多):

  1. 部分背包 – 贪心
  2. 01背包 – DP //优化后2层循环
  3. 完全背包 – DP //优化后2层循环
  4. 多重背包 – DP //优化后3层循环

只有部分背包是个贪心问题,其他的都是以01背包为基础的动归问题。为什么部分背包是贪心问题呢?因为部分背包问题一定能把包塞满,而其他背包问题则不一定,只能通过DP来解。当然其他背包问题也可以通过搜索来解,但非最优。

部分背包问题
定义: 有N个物体,第i个物体的重量为W[i],价值为V[i],在总重量不超过C的情况下让总价值尽量高。每一个物体都可以只取走一部分,价值和重量按比例计算。

解法: 把物品按价值密度从大到小排序(W[i]/V[i]),然后从第一种物品开始,尽可能多拿当前物品,如果当前物品的量小于背包剩余容量,拿完,指针推到下一种物品;否则拿背包剩余容量的量,结束。

01背包问题
定义: 有N个物体,第i个物体的重量为W[i],价值为V[i],每一个物体要么取,要么不取,在总重量不超过C的情况下让总价值尽量高。

解法: 递推式:f(i,j )= max ( f(i-1, j), f(i-1, j-V[i])+W[i]),i定义为前i个物品,j为当前背包剩余容量。当前状态只跟上一行相同列和一个前面的列有关,滚动数组逆序求解。滚动数组初始值代表拿0个物品,不管背包容量是多少价值都是0,所以初始化全是0,行循环下界从1开始。列的计算也是从1开始,因为第0列代表容量为零,一个物品都放不下,价值必然是0,进一步的优化是从V[i]开始,因为背包容量小于V[i]的时候,当前物品i必然没办法拿,只能是f(i-1, j) 即保持上一行的状态。

复杂度分析:二维动归,状态是二维的,遍历所有状态是O(NC),计算每个状态的开销是O(1),总的复杂度是O(NC)。

完全背包问题
定义: 有N种物体,第i个物体的重量为W[i],价值为V[i],每个物品不是取还是不取,而是可以取任意次数,在总重量不超过C的情况下让总价值尽量高。

解法1:虽说可以取任意次,实际上每个物品可取的次数有个上限,即C/W[i]。一个物品可拿C/W[i]次可以分割为C/W[i]个只能拿一次的相同物品,转换为01背包。时间复杂度O(N’*C),这里N’=NC/W[i],所以总复杂度为O(C*∑i=1…N(C/W[i]))。

解法2:直接扩展01背包的思路,从2种选择(0,1)扩展到 C/V[i]种选择(0,1,2,…C/V[i])。递推式:f(i,j) = max{ f(i-1, j - k* V[i]) + k * W[i] } k=0,1,…, C/V[i]。滚动数组依然是逆序求解。
复杂度分析:遍历二维状态 是O(NC),计算每个状态是O(C/V[i]),总的复杂度是O(C∑i=1…N(C/W[i]))。

解法3:递推式:f(i, j) = max ( f(i-1, j), f(i, j - V[i]) + W[i]) 这个递推式和01背包的递推式很像,只是max里的第二项是本行的状态。递推式的含义不是按当前物品拿几次划分,而是另一种划分和解释:用前i-1个物品装容量j的最大价值,和用前i个物品,先装最大 j- V[i]的容量,然后再装一个物品i的价值,二者的大者。划分的角度是用不用到前i个物品。注意这里f的定义和01背包就不同:f(i,j)的定义是,用前i个物品,每个物品不限次数,装到容量为j的背包的最大价值。滚动数组求解是正序的。正序和逆序的区别就是,对于正序,j以前的状态是当前行的状态;对于逆序,j以前的状态是上一个行的状态。
复杂度分析:遍历二维状态O(NC),计算状态O(1),总的复杂度是O(NC)。

解法4:直接递归的思路。考察在装任意一个物品之后面临的问题,仍然可以从n种物品中任意选择,这个条件没有变化,只是背包容量变小了,也就是说装了任意一个物品之后的面临的问题是和原问题相同的问题,只是规模变小了,这就是递归。而且仅仅是背包容量这一个维度变小了,是一维递归。**反观01背包,在装一个物品之后面临的问题,不仅仅是原问题背包容量变小之后的子问题,因为问题其他条件也变了,有一个物品不能选了,必须把这个变量也作为递归变量,是二维递归。**直接递归法下的完全背包问题递推式:f(j) = max { f(j- V[i]) + W[i] }, i= 1,…, n and V[i]<=C 。解释是,先取一个,然后解决剩下的问题,经典的递归、分治思想,先让规模减小一步,然后解决规模变小了的相同问题。
小结:01背包–>二维DP, 完全背包–>一维DP

多重背包问题
定义: 有N种物体,第i个物体的重量为W[i],价值为V[i],每个物品取的次数有限制,所以输入会多一个数组num[n],在总重量不超过C的情况下让总价值尽量高。
解法1:次数转化成物品数,变成01背包问题。

解法2:扩展01背包问题,第i个物品分别拿0到num[i]次,还要满足j-num[i] * V[i]>0

硬币问题:背包问题的变种

其实01背包问题的模型是相对复杂的,输入是一两个数组,一个cost数组,一个价值数组,还有一个总cost上限,求价值最大化的值。

变种1:最少硬币问题。给定一个硬币面额集合和一个金额,求怎样用最少(或最多)的硬币凑成这个金额?输入比背包问题少一个数组,问题不如背包问题复杂。用背包问题的元素翻译一下:给定一组物体的体积,求装满一个给定容量背包所用物品的最少个数,每个物品可以任意取。
注意:因为每种硬币都有无穷多,所以这是完全背包的变种。

解法1:递推式:f( j) = min { 1+ f(j-V[i-1]) } i= 1,…, n and V[i-1]<= j,先分别取一个每一个可以取的硬币(面额小于给定的金额),然后递归解决金额变小了的子问题,得到分别先取每一个硬币的取法的最小硬币数,然后取最小的。

解法2:完全背包,f(i, j) = min( f(i-1, j), f(i, j - v[i-1]))

变种2:方案数问题,给定一个硬币面额集合和一个金额,求用这面额的硬币凑成这个金额的方案数。

递归思路:完全背包:不取第i种面额的方案数+ 取第i种面额的方案数。递推式:f(i, j) = f(i-1, j) + f(i, j - v[i - 1])

变种3:给定n种硬币,从中取k个,求方案数。

思路:和上一题是一样的,只不过背包体积换成背包可以装的个数,每一个物品的体积cost变为数目cost为1,完全背包问题: f[i][j] = f[i][j-1] + f[i-1][j]

你可能感兴趣的:(背包问题与硬币问题的一个总结)