0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi w i ,其价值为 vi v i 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
假设一个函数B是求解总价值的函数,有两个因变量n与C ;则我们的优化目标就变为
max.F(n,C,x).x∈0,1 m a x . F ( n , C , x ) . x ∈ 0 , 1
展开公式其实就是:
F(n,C,x)=x1∗v1+x2∗v2+....+xn∗vn F ( n , C , x ) = x 1 ∗ v 1 + x 2 ∗ v 2 + . . . . + x n ∗ v n
std.x1∗w1+x2∗w2+...+xn∗wn≤C s t d . x 1 ∗ w 1 + x 2 ∗ w 2 + . . . + x n ∗ w n ≤ C
xi∈0,1 x i ∈ 0 , 1
x的取值范围为0或者1,代表着这个物品我们可以选择拿或者不拿,我们想要找出这样的01组合如:
(1,1,1,0,0,1) ( 1 , 1 , 1 , 0 , 0 , 1 ) 使得
F(n,C,x) F ( n , C , x ) 最大。
我们假设一个函数
B(n,C)=max.F(n,c,x) B ( n , C ) = m a x . F ( n , c , x )
也就是说B函数是一个能够自动组合x的取值使得
F(n,c,x) F ( n , c , x ) 达到最大。
再次理解这个
B(n,c) B ( n , c ) 这个函数的意义:从n个物品里面选物品,容量为C,能达到的最大价值。如何求解这个函数呢?我们可以细分目标,试想一下,如果想要在5个商品里面选择,得到最大总价值,那么肯定得先求得在4个物品里面选择,得到最大价值后,然后考虑第五个物品要不要放进去?放进去会不会超过容量限制,会不会得到一个最大价值。我们就得到了一个函数。
B(n,c)=B(n−1,c);没有多余的空间去放置最后一个物品 B ( n , c ) = B ( n − 1 , c ) ; 没 有 多 余 的 空 间 去 放 置 最 后 一 个 物 品
B(n,c)=max{B(n−1,c),B(n−1,c−wn)+vn};如果有多余的空间去放置,则考虑是否要放置 B ( n , c ) = m a x { B ( n − 1 , c ) , B ( n − 1 , c − w n ) + v n } ; 如 果 有 多 余 的 空 间 去 放 置 , 则 考 虑 是 否 要 放 置
这里存在一个困扰:如果有多余空间去放置的话,那么放置肯定比不放的价值大啊,因为我多放入一个物品了啊,也就是说选择不放的函数
B(n−1,x) B ( n − 1 , x ) 肯定小与放置的函数
B(n−1,c−w)+vn B ( n − 1 , c − w ) + v n ,说道这里你就犯了一个惯性错误,认为
B(n−1,x) B ( n − 1 , x ) 与
B(n−1,c−w) B ( n − 1 , c − w ) 所对应的
F(n,c,x) F ( n , c , x ) 中的
x={x1,x2,.....xn} x = { x 1 , x 2 , . . . . . x n } 组合相同,在理解这个函数的意义:从n个物品里面选物品,容量为C,能达到的最大价值。这个两个函数组合不一定相同,为容量C约束条件变了。就会变为从n-1个物品里面选,容量为C与从n-1个物品里面选,容量为c-w且加上v,两者哪个大。彻底理解了函数B之后,我相信没有什么能够阻挡你学习这个算法了。
例:
w={1,2},v={1,2},c=2 w = { 1 , 2 } , v = { 1 , 2 } , c = 2
解:B(2,2)为最大价值,如果我们拿最后物品w=2,v=2,因为w=2=c,所以我们可以选择拿或者不拿
1、拿:如果确定拿走最后一个物品,则B(2,2)=B(2-1,2-2)+2=B(1,0)+2
2、不拿:如果确定不拿走最后一个物品,则B(2,2)=B(1,2);因为最后一个物品我选择不拿,所以情景肯定变为从1个物品里面选,容量为2,是否达到最大值,因此等式左右两边相等。
然后比较
B(1,0)+2 B ( 1 , 0 ) + 2 与
B(1,2) B ( 1 , 2 ) 哪个大,很明显,对于B(1,0),已经没有容量去放置下一个物品,就相当于从0个物品里面选
B(1,0)=B(0,0)=0,B(1,0)+2=2 B ( 1 , 0 ) = B ( 0 , 0 ) = 0 , B ( 1 , 0 ) + 2 = 2 则.求解
B(1,2) B ( 1 , 2 ) ,代表着我只能去选择(第一件:2=1,v=1)的我要不要去拿,肯定得拿啊,再不拿就没东西可以拿了,结果为0,拿了话结果价值就为1
B(2,2)=max{B(1,0)+2,B(1,0)+2}=max{2,1}=2 B ( 2 , 2 ) = m a x { B ( 1 , 0 ) + 2 , B ( 1 , 0 ) + 2 } = m a x { 2 , 1 } = 2
显然这是个递归求解的过程,实现递归过程,并测试一下
capicaty=[0,1,2]
value=[0,1,2]
def best_value(i,j):
if i==0:
return 0
if j-capicaty[i]<0:
return best_value(i-1,j)
else:
return max(best_value(i-1,j),best_value(i-1,j-capicaty[i])+value[i])
print(best_value(2,2))
result:2
递归结果正确,但是如果数据量比较大的话,程序肯定会变的缓慢,递归会使得程序效率变差这个原因我就不解释了,但是如果我要求B(2,2)是不是要计算,B(1,2),B(0,2),如果要求B(1,2)是不是得求一下B(0,2),最后才能返回结果,我们是不是重复计算了B(1,2)与B(0,2)。如果要是能把每个计算的结果保存下来,下次就不必重复计算了,可以的,我们把递归转化为递推。
声明一个矩阵maxvalue[N][C],专门用来存放B函数的缓存结果,利用行列进行索引值,行代表有几个物品,列代表容量,这样的话如果要求B(2,2),就去索引第二个物品,容量为2 即maxvalue[2][2],找到这个值就ok了
maxvalue=[[0 for i in range(3)] for j in range(3)]
for k in range(1,3):
for c in range(1,3):
if c-capicaty[k]>=0:
maxvalue[k][c]=max(maxvalue[k-1][c],maxvalue[k-1][c-capicaty[k]]+value[k])
else:
maxvalue[k][c]=maxvalue[k-1][c]
B(2,2)那么我们去索引,maxvalue[2][2]同样得到2的结果,这样的话这个算法你就掌握了,可以去到动态规划的世界玩耍一番,好好感悟一下,晦涩难懂的理论知识,状态,状态转移方程,把目标划分为子目标,分而治之,你会发现你看得懂了,完美。