参看文章:通过金矿模型介绍动态规划,
原文写的非常通俗易懂,我只是按自己的理解总结一下。
01背包问题
有10件物品,编号为0-9,第i件物品的体积为weight[i],价值为value[i]。如果有一个背包,容量为s。选取哪些物品装进背包里能使背包的装的东西总价值最大?
因为物品不能拆分,要么整个装,要么整个都不装,所以是01问题。
按平均价值是错误的
看到这个问题,可能首先会想到算每个物品的平均价值,先选平均价值最大的。但是这样算是错的。举个粒子:要把下面3个物品(体积,价值,平均价值)装容量为10的背包:
物品A(3, 60,20), 物品B(4, 70,17.5), 物品C(5,80, 16)
按平均价值算:最大平均价值算会选A,B 为 130 ,很显然这是错的。最大价值应该选B,C 150。
如果不是0-1问题,比如物品是一堆糖,一堆盐,是可以拆分,才能用平均价值计算。
暴力遍历组合
最简单粗暴的方法就是遍历所有可能的组合。每个物品都有两种选择:装或者不装。 n件物品总共就有 2n 种组合。
动态规划
再看看另一种思路, 我们把从i件物品中选择若干件放入容量为j的背包后的最大价值记为f(i,j)。假如背包的容量为20,物品为10件(编号从0-9),我们的任务就是要计算出 f(10,20)。
先看最后一个物品(编号9,假如体积为3,价值为5),有两种选择:装 或者 不装。
(1).如果选择装,并且装得下。那么背包现价值为 5, 背包还能在剩余的9个物品中装进体积为17的东西(20-3)。 背包总价值为:5+f(9,20-3)
(2).如果不装,那么背包现价值为0, 背包还能在剩余的9个物品中装进体积为20的东西。 背包总价值为:f(9, 20)
再从这两种选择中选取总价值最大的那一种,f(10, 20)= max{ f(9,20-3)+5, f(9,20) }。
这里还有另外一种情况:如果选择装,但是发现根本装不下,没办法就只能选择不装了,变成了情况(2)。那么最大总价值就是不装的最大总价值 f(10,20)=f(9,20)
这个式子中有两个递归的子问题f(9,17)和f(9,20)。要得到f(10,20)的结果,还得继续算这俩子问题的结果。但是我们发现这俩个子问题和主问题完全是一个套路。比如说f(9,17),不就是在剩下的9个物品中选择一些装进容量为17的背包后的最大价值嘛。也按上面的套路来算好了。
算f(9,17)。看剩下的这9个物品中的最后一个(也就是8号物品,假如体积为2,价值为3),也是两种选择:装 或者 不装。
(1).如果选择装,并且装得下。那么背包现价值为 3, 背包还能在剩余的8个物品中装进体积为15的东西(17-2)。 背包最后的总价值为:3+f(8,17-2)。
(2).如果不装,那么背包现价值为0, 背包还能在剩余的8个物品中装进体积为17的东西。 背包最后的总价值为:f(8, 20)。
再从这两种选择中选取总价值最大的那一种,f(9, 17)= max{ f(8,17-2)+3, f(8,17) }。 又冒出两个更小点的子问题,还得继续往下算。。。
总结一下从i件物品中选择若干件放入容量为j的背包后的最大价值的递归的公式(背包状态转换方程):
f(i,j)=max { f(i-1, j-weight(i))+ value(i), f(i-1, j) } ; 条件 weight(i)<=j
决策装不装第i个物品的时候另一种情况,如果weight(i)>j,也就是这件物品根本就装不进背包,我们就不考虑能装它的情况啦,背包的最大总价值就是不装该物那种情况的最大总价值。
f(i,j)= f(i-1,j) ; 条件 weight(i)>j
递归的子问题在一点点变小,很容易想到,最后一定终结在下面两种情况之一:
(1)把全部物品都检查看了一遍了,已经没有物品了。 自然有 价值f(0, c)=0
(2)还有物品,但是背包已经满了。 f(i, 0)=0。
其实也可以更早一点终结: 到只剩下一件物品(编号0)的时候,看还有多大空间的背包(j)。如果j>=weight(0),那么价值就是value(0), 如果j<weight(0),那么价值就是0。
如果直接这样计算,每个问题都划分成两个更小的问题。时间复杂度也是在O(2n)级别。 但实际上有很多f(i,j)值会反复计算多次,可以进一步优化让每个f(i,j)值只计算一次然后保存,以后就不需要再重复计算。然后,复杂度就变成了总共会有多少个子问题f(i,j)需要计算了。理论上说i可以取值0...i,j可以取值0...j,总共i*j+i+j+1种组合,子问题规模为O(ij)。但是很显然,实际的问题数量会远远小于这个理论值。
一般来说动态规划所需要的时间为 T=O(questingCount * chooseCount)。 questingCount(就是前面所说子问题的数量)= 可选对象的数量 * 资源总量。 chooseCount为每个子问题可以进行的选择数。背包问题中,一个物品要么装要么不装,所有chooseCount=2。
动态规划的特点(满足这些特点的问题就可以使用动态规划的思想):
- 最优子结构:如果一个母问题可以分成几个子问题,确定了最优的子问题那么母问题也就是最优的。
- 子问题重叠:母问题和子问题属于相同的模式,可以用同一个迭代式表示,不同点仅仅是参数。
- 子问题独立:一个母问题的多个子问题只会选择一个作为实施方案,一般是相互独立的,不会互相影响。
- 边界:问题逐渐变小,一定要能到达一个终止条件,也就是边界,否则就成死循环了。
- 缓存中间数据,避免重复计算,可以极大优化性能。
题目一:买书
有一书店引进了一套书,共有3卷,每卷书定价是60元,书店为了搞促销,推出一个活动,活动如下:
如果单独购买其中一卷,那么可以打9.5折。
如果同时购买两卷不同的,那么可以打9折。
如果同时购买三卷不同的,那么可以打8.5折。
如果小明希望购买第1卷x本,第2卷y本,第3卷z本,那么至少需要多少钱呢?(x、y、z为三个已知整数)。