Last Updated: 2024-03-26 14:27:00 Tuesday
-- TOC --
有N个物品,分别有重量W和价格P两个属性,现在有一个包,可以容下最多T重量的物品,问将哪些物品放入包内,在不超重的情况下,物品价值总量最大。物品不可分割。
01或写成0/1,0-1...表示每个物品只能拿一次。如果两个物品相同,拿一次就少一个。
def knapsack(wlst, vlst, weight, value):
assert len(wlst) == len(vlst)
if weight < 0:
return -1
if not wlst or weight==0:
return value
a = knapsack(wlst[1:], vlst[1:], weight-wlst[0], value+vlst[0])
b = knapsack(wlst[1:], vlst[1:], weight, value)
return max(a,b)
T = 4
W = [4,5,1]
P = [1,2,3]
print(knapsack(W,P,T,0)) # 3
T = 3
W = [4,5,6]
P = [1,2,3]
print(knapsack(W,P,T,0)) # 0
T = 10
W = [1,2,3,4,5]
P = [5,4,3,2,1]
print(knapsack(W,P,T,0)) # 14
$$T(n)=2T(n-1)+1=O(2^n)$$
换个写法:
class knapsack():
def __init__(self, wlst, vlst):
self.length = len(wlst)
assert self.length == len(vlst)
self.wlst = wlst
self.vlst = vlst
def go(self, idx, weight, value):
if weight < 0:
return -1
if idx==self.length or weight==0:
return value
a = self.go(idx+1, weight-self.wlst[idx], value+self.vlst[idx])
b = self.go(idx+1, weight, value)
return max(a,b)
T = 4
W = [4,5,1]
P = [1,2,3]
kp = knapsack(W,P)
print(kp.go(0,T,0))
T = 3
W = [4,5,6]
P = [1,2,3]
kp = knapsack(W,P)
print(kp.go(0,T,0))
T = 10
W = [1,2,3,4,5]
P = [5,4,3,2,1]
kp = knapsack(W,P)
print(kp.go(0,T,0))
class knapsack():
def __init__(self, wlst, vlst):
self.length = len(wlst)
assert self.length == len(vlst)
self.wlst = wlst
self.vlst = vlst
self.rec = {}
def go(self, idx, weight, value):
if (idx,weight) in self.rec:
return self.rec[idx,weight]
if weight < 0:
return -1
if idx==self.length or weight==0:
return value
a = self.go(idx+1, weight-self.wlst[idx], value+self.vlst[idx])
b = self.go(idx+1, weight, value)
t = max(a,b)
self.rec[idx,weight] = t
return t
有多少子问题?\(O(n\cdot weight)\)
def knapsack(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
rec = [[0]*(T+1) for _ in range(wlen+1)]
for i,w in enumerate(W,1):
for t in range(T+1):
if t >= w:
rec[i][t] = max(rec[i-1][t], rec[i-1][t-w]+P[i-1])
else:
rec[i][t] = rec[i-1][t]
return rec[-1][-1]
T = 4
W = [4,5,1]
P = [1,2,3]
print(knapsack(W,P,T)) # 3
T = 3
W = [4,5,6]
P = [1,2,3]
print(knapsack(W,P,T)) # 0
T = 10
W = [1,2,3,4,5]
P = [5,4,3,2,1]
print(knapsack(W,P,T)) # 14
节省内存的版本:
def knapsack(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
rec = [0] * (T+1)
for i,w in enumerate(W):
c = rec[:]
for t in range(T+1):
if t >= w:
rec[t] = max(c[t], c[t-w]+P[i])
else:
rec[t] = c[t]
return rec[-1]
精简后最美妙的版本:
def knapsack(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
rec = [0] * (T+1)
for i,w in enumerate(W):
for t in reversed(range(w,T+1)):
rec[t] = max(rec[t], rec[t-w]+P[i])
return rec[-1]
def knapsack(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
pmatrix = [['x']*wlen for _ in range(T+1)] # x: no pick
rec = [0] * (T+1)
for i,w in enumerate(W):
c = rec[:]
for t in range(T+1):
if t >= w:
pw = c[t-w] + P[i]
if c[t] > pw:
# pmatrix[t][i] = '<' # <: previous, not this
rec[t] = c[t]
else:
pmatrix[t][i] = 'y' # y: pick this one
rec[t] = pw
else:
rec[t] = c[t]
pick = []
t = T
i = wlen-1
while not (t<0 or i<0):
if pmatrix[t][i] == 'y':
pick.append(i)
i -= 1
t -= W[i]
# elif pmatrix[t][i] == '<':
# i -= 1
else:
i -= 1
return rec[-1], pick[::-1]
T = 4
W = [4,5,1]
P = [1,2,3]
print(knapsack(W,P,T))
T = 3
W = [4,5,6]
P = [1,2,3]
print(knapsack(W,P,T))
T = 10
W = [1,2,3,4,5]
P = [5,4,3,2,1]
print(knapsack(W,P,T))
T = 10
W = [7,5,5,1]
P = [5,5,4,3]
print(knapsack(W,P,T))
输出:
(3, [2])
(0, [])
(14, [0, 1, 2, 3])
(9, [1, 2])
完全背包问题与01背包问题不一样的地方在于,每件物品的数量无穷。这有点像找零钱问题,但约束条件和目标不一样。
本想先写DP递归版本,结果失败了,还没有想清楚如何保存子问题的结果......下面是Bottom-Up版本,与01背包问题的差异很小,但很关键:
def knapsack_complete_bu_old(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
rec = [0] * (T+1)
for i,w in enumerate(W): # iterate items
prev = rec[:]
for t in range(T+1): # iterate remain space
if w <= t:
n = t // w
maxp = 0
for j in range(n+1):
maxp = max(maxp, prev[t-w*j]+P[i]*j)
rec[t] = maxp
else:
rec[t] = prev[t]
return rec[-1]
T = 4
W = [4,5,1]
P = [1,2,3]
print(knapsack_complete_bu(W,P,T))
T = 3
W = [4,5,6]
P = [1,2,3]
print(knapsack_complete_bu(W,P,T))
T = 10
W = [1,2,3,4,5]
P = [5,4,3,2,1]
print(knapsack_complete_bu(W,P,T))
T = 10
W = [7,5,2,2]
P = [5,5,4,3]
print(knapsack_complete_bu(W,P,T))
这个版本还可以继续简化成下面这个精妙的算法:
def knapsack_complete_bu(W,P,T):
wlen = len(W)
plen = len(P)
assert wlen == plen
rec = [0] * (T+1)
for i,w in enumerate(W): # iterate items
for t in range(w,T+1): # iterate remain space
rec[t] = max(rec[t], rec[t-w]+P[i])
return rec[-1]
本文链接:https://cs.pynote.net/ag/dp/202403101/
-- EOF --
-- MORE --