完全揹包問題從簡單到複雜
阿新 • • 發佈:2018-12-21
題目
有 N 種物品和一個容量為 V 的揹包,每種物品都有無限件可用。放入第 i 種物品的費用是 C i ,價值是 W i 。求解:將哪些物品裝入揹包,可使這些物品的耗費的費用總和不超過揹包容量,且價值總和最大。
第一種思路,基於投資問題模型
從每種物品的角度考慮,與它相關的策略已並非取或不取兩種,而是有取 0 件、取 1 件、取 2 件…直至取 ⌊V /C i ⌋ 件等許多種。
狀態方程為:
F [i, v] = max {F [i − 1, v − kC i ] + kW i | 0 ≤ kC i ≤ v}
基於以上狀態方式實現的三種方式,遞迴,自上而下,自下而上如下:
# another way: F [i, v] = max {F [i − 1, v − kC i ] + kW i | 0 ≤ kC i ≤ v}
def pack_complete_Rec2(N,V,C,W):
if N ==0:
return 0
k = V // C[N-1]
result = -1
for i in range(k+1):
A = pack_complete_Rec(N-1,V-i*C[N-1],C,W) + i*W[N-1]
if A > result:
result = A
return result
def pack_complete_Top_down2(N,V,C,W):
list = np.zeros((N+1,V+1),dtype=int)
list[1:,:] = -1
def complete_Top_down(N,V):
k = V // C[N-1]
result = -1
for i in range(k+1):
if list[N,V] == -1 and N >=1:
A = pack_complete_Rec(N-1,V-i*C[N-1],C,W) + i*W[N-1]
if A > result:
result = A
list[N,V] = result
return list[N,V]
return complete_Top_down(N,V)
def pack_complete_Bottom_up2(N,V,C,W):
list = np.zeros((N+1,V+1),dtype=int)
# list[1:,:] = -1
for i in range(1,N+1):
for j in range(0,V+1):
t = j // C[i-1]
result = -1000
for k in range(t+1):
A = list[i-1,j-k*C[i-1]] + k*W[i-1]
if A > result:
result = A
list[i,j] = result
return list[N,V]
把問題轉換成01揹包問題,這裡面涉及到等效或者泛化物品
將一種物品拆成多件只能選 0 件或 1 件的01揹包中的物品,以下是簡單實現,基於二進位制的實現,在多重揹包中實現:
# change complete to 01
def change_complete_to_01(N,V,C,W):
C_ =[]
W_ =[]
for i in range(N):
t = V // C[i]
for k in range(0,t+1):
C_.append(k*C[i])
W_.append(k*W[i])
def pack_0_1_first(N,V,C,W):
F =[0]*(V+1)
for i in range(1,N+1):
for v in range(V,C[i-1]-1,-1):
F[v] = max(F[v],F[v-C[i-1]] + W[i-1])
return F[V]
N_ = len(C_)
return pack_0_1_first(N_,V,C_,W_)
更為簡單的實現,把第i種物品看成一個整體,針對第i種物品就有2種策略,1不選:可以看成i-1的問題,二選,就可以看成第i類的01揹包問題,因為V是一定的,此時的i實際上不是無限的,是受V限制的,就可以把有限的i看成不同的物品,等價於一個01揹包問題,狀態方程如下:
F [i, v] = max (F [i − 1, v], F [i, v − C i ] + W i )
基於以上狀態方式實現的三種方式,遞迴,自上而下,自下而上如下:
def pack_complete_Rec(N,V,C,W):
if N ==0:
return 0
if V < C[N-1]:
return pack_complete_Rec(N-1,V,C,W)
return max(pack_complete_Rec(N-1,V,C,W),pack_complete_Rec(N,V-C[N-1],C,W) + W[N-1])
import numpy as np
def pack_complete_Top_down(N,V,C,W):
list = np.zeros((N+1,V+1),dtype=int)
list[1:,:] = -1
def complete_Top_down(N,V):
if list[N,V] == -1 and N >=1:
A = complete_Top_down(N-1,V)
if V < C[N-1]:
return A
else:
list[N,V] = max(A,complete_Top_down(N,V-C[N-1])+W[N-1])
return list[N,V]
return complete_Top_down(N,V)
def pack_complete_Bottom_up(N,V,C,W):
list = np.zeros((N+1,V+1),dtype=int)
list[1:,:] = -1
for i in range(1,N+1):
for j in range(0,V+1):
A = list[i-1,j]
if j < C[i-1]:
list[i,j] = A
else:
list[i,j] = max(A,list[i,j-C[i-1]]+W[i-1])
return list[N,V]
基於一維陣列的簡單的實現如下,對比上述的Bottom_up方法,使用一維陣列實現,通過初始化,能夠有效避免,左邊的走法:
def pack_complete_first(N,V,C,W):
def CompletePack(F,ci,wi):
for v in range(ci,V+1):
F[v] = max(F[v],F[v-ci] + wi)
return F
F =[0]*(V+1)
for i in range(1,N+1):
CompletePack(F,C[i-1],W[i-1])
return F[V]
完全揹包可行性問題,只取決於初始化,只有F[0,0]才為0時,才可以得到有效解。
def pack_complete_first_yes_or_no(N,V,C):
def CompletePack_or(F,ci,wi):
for v in range(ci,V+1):
F[v] = F[v] or F[v-ci]
return F
F =[False]*(V+1)
F[0] = True
for i in range(1,N+1):
CompletePack_or(F,C[i-1],W[i-1])
return F[V]
def pack_complete_Bottom_up_yes_or_no(N,V,C):
list = np.zeros((N+1,V+1),dtype=bool)
list[0,0] = True
for i in range(1,N+1):
for j in range(0,V+1):
A = list[i-1,j]
if j < C[i-1]:
list[i,j] = A
else:
list[i,j] = A or list[i,j-C[i-1]]
return list[N,V]
執行結果:
#%%
N = 7
V = 77
C = [1,2,3,9,13,6,7,5]
W = [1,2,9,7,5,11,6,14]
#%%
print pack_complete_first(N,V,C,W)
print pack_complete_Rec(N,V,C,W)
print pack_complete_Top_down(N,V,C,W)
print pack_complete_Bottom_up(N,V,C,W)
print pack_complete_first_yes_or_no(N,V,C)
print pack_complete_Bottom_up_yes_or_no(N,V,C)
print pack_complete_Rec2(N,V,C,W)
print pack_complete_Top_down2(N,V,C,W)
print pack_complete_Bottom_up2(N,V,C,W)
print change_complete_to_01(N,V,C,W)
227
227
227
227
True
True
227
227
227
227