背包问题
现有不同类型的物品,质量分别为w[i], 价值为v[i],我们把他们放到背包里,背包本身有一定的容量c,我们想要放入背包的东西:
- 总质量小于或者等于背包的容量;
- 总价值能达到最大。
有两种情况:
- 每种物品数量不限,可以用无限制使用;
- 每种物品数量只有1个,就是标准的0-1背包问题。
首先我们来看看无限制的情况。
每种物品可以无限制使用
我们假设背包容量r所能达到的最大价值为m®, r中每一个值都代表了一个子问题。那么我们的递归分解过程将围绕着背包容量中最后一个单元是否有用来进行。
就相当于说把背包按从0-c的顺序依次放大,然后依次判断每个物品能否放进去。并且确保背包在此时的容量里,我们存储下来的是最大价值。——始终存储最优解。
如果该单元没有用,那么
m
(
r
)
=
m
(
r
−
1
)
m(r) = m(r-1)
m(r)=m(r−1);
如果该单元有用,我们就会在其中选择合适的对象来用。如果我们选择了对象i,那么
m
(
r
)
=
v
[
i
]
+
m
(
r
−
w
[
i
]
)
m(r) = v[i] + m(r-w[i])
m(r)=v[i]+m(r−w[i]) 。因为我们加入了i的价值,但同时也用掉了剩余容量中某一位置的质量。
算法逻辑:
将其看作一个决策过程,我们可以自行选择是否要用背包容量中的最后一个单元。
用一个list,记忆化存储在这个容量下的最大价值。
记忆化存储可以帮我们消除掉其中指数级别的递归冗余。
代码:
def unbounded_knapsack(w, v, c):
m = [0] #背包容量为0的时候的最大价值也是0
for r in range(1, c+1): #背包容量为1,到c的时候
val = m[r-1]
for i, wi in enumerate(w): #挨个试用每个物品
if wi > r:#物品重量比背包容量还大,直接放弃
continue
val = max(val, v[i] + m[r-wi]) #物品重量小于背包容量,则比较不放这个物品,和放入这个物品的最大价值。这时候可能会疑惑,万一这个物品放进去超出背包容量怎么办。注意我们这里添加的是m[r-wi],也就是其他物品的重量不超过r-wi的最大价值,所以这个物品是一定放的进去的
m.append(val)
return m[c]
if __name__ == "__main__":
w = [2, 3, 4, 5]
v = [3, 4, 5, 6]
c = 8
print("无限制的背包问题")
x = rec_unbounded_knapsack(w, v, c)
print(x)
输出结果:
无限制的背包问题
第 0 个物品的重量, 2
第 1 个物品的重量, 3
第 2 个物品的重量, 4
第 3 个物品的重量, 5
随着容量增大背包的最大价值: [0, 0]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 0 3
第 1 个物品的重量, 3
第 2 个物品的重量, 4
第 3 个物品的重量, 5
随着容量增大背包的最大价值: [0, 0, 3]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 3 3
第 1 个物品的重量, 3
max of : 3 4
第 2 个物品的重量, 4
第 3 个物品的重量, 5
随着容量增大背包的最大价值: [0, 0, 3, 4]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 4 6
第 1 个物品的重量, 3
max of : 6 4
第 2 个物品的重量, 4
max of : 6 5
第 3 个物品的重量, 5
随着容量增大背包的最大价值: [0, 0, 3, 4, 6]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 6 7
第 1 个物品的重量, 3
max of : 7 7
第 2 个物品的重量, 4
max of : 7 5
第 3 个物品的重量, 5
max of : 7 6
随着容量增大背包的最大价值: [0, 0, 3, 4, 6, 7]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 7 9
第 1 个物品的重量, 3
max of : 9 8
第 2 个物品的重量, 4
max of : 9 8
第 3 个物品的重量, 5
max of : 9 6
随着容量增大背包的最大价值: [0, 0, 3, 4, 6, 7, 9]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 9 10
第 1 个物品的重量, 3
max of : 10 10
第 2 个物品的重量, 4
max of : 10 9
第 3 个物品的重量, 5
max of : 10 9
随着容量增大背包的最大价值: [0, 0, 3, 4, 6, 7, 9, 10]
xxxxxxxxxxxxxxxxxxxxxx
第 0 个物品的重量, 2
max of : 10 12
第 1 个物品的重量, 3
max of : 12 11
第 2 个物品的重量, 4
max of : 12 11
第 3 个物品的重量, 5
max of : 12 10
随着容量增大背包的最大价值: [0, 0, 3, 4, 6, 7, 9, 10, 12]
xxxxxxxxxxxxxxxxxxxxxx
12
每种物品只可以使用一次
下面,我们来看一个知名度更大的版本——01背包问题。在这里,每个对象最多使用一次(要想使其扩展成超过一次也很简单,可以对算法本身进行微调,也可以在问题实例中引入多个对象)。区分于无限制的情况,除了背包容量的限制之外,我们这里还引入了“进/出”对象的思维,以此来限制可用对象的数量。或者,我们可以按照顺序指定“目前正在考虑”的对象,并用强归纳法假设在所有子问题中,我们既会选择先考虑较靠前的对象,还是较低的容量,也会亮着一并考虑,这些都可以递归来解决。
假设前k个对象的最大价值为m(k,r), 其中r为剩余背包的容量。然后,显然,如果k=0或者r=0,就一定有m(k,r)=0。对于其他情况,我们可以再将其视为某种决策。我们只需要考虑是否纳入最后一个对象,i=k-1,如果选择否,那么
m
(
k
,
r
)
=
m
(
k
−
1
,
r
)
m(k,r) = m(k-1, r)
m(k,r)=m(k−1,r)。如果这里
w
[
i
]
>
r
w[i]>r
w[i]>r,那么只能删除这个对象。
如果对象合适,我们可以放入背包中,也就是
m
(
k
,
r
)
=
v
[
i
]
+
m
(
k
−
1
,
r
−
w
[
i
]
)
m(k, r) = v[i] + m(k-1, r-w[i])
m(k,r)=v[i]+m(k−1,r−w[i]) 。与无限制的情况十分类似,只不过这里多了一个额外的参数k。限制对象不能重复使用。
def knapsack(w, v, c):
n = len(w)
m = [[0]*(c+1) for i in range(n+1)]
P = [[False] * (c+1) for i in range(n+1)]
for k in range(1, n+1): #依次讨论物品
print("当我们具备前",k,"个物品的时候:")
i = k-1
for r in range(1, c+1): #对于每一个容量,存储该情况下的最大价值
m[k][r] = drop = m[k-1][r] #抛弃
if w[i] > r: continue #放不了
keep = v[i] + m[k-1][r-w[i]] #保留
m[k][r] = max(drop, keep) #比较抛弃或者保留
P[k][r] = keep > drop #存储该情况下的抉择(每个物品,True为放,False为不放)
print("依次增大背包容量,获得的最大价值:", m[k])
print(m[k])
return m[n][c]
if __name__ == "__main__":
w = [2, 3, 4, 5]
v = [3, 4, 5, 6]
c = 8
print("有限制的背包问题")
x = knapsack(w, v, c)
print(x)
输出:
有限制的背包问题
当我们具备前 1 个物品时候
依次增大背包容量,获得的最大价值
[0, 0, 3, 0, 0, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 0, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 3, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 3, 3, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 3, 3, 3, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 3, 3, 3, 3, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 3, 3, 3, 3, 3, 3]
当我们具备前 2 个物品时候
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 0, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 4, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 4, 7, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 4, 7, 7, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 4, 7, 7, 7, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 4, 7, 7, 7, 7]
当我们具备前 3 个物品时候
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 0, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 9, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 9, 9]
当我们具备前 4 个物品时候
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 0, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 0, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 9, 0]
依次增大背包容量,获得的最大价值
[0, 0, 3, 4, 5, 7, 8, 9, 10]
10