揹包九講問題
引言
通過學習揹包九講這個文件,掌握動態規劃題目的解決方法。
1 揹包問題
有N 件物品和一個容量為V 的揹包。第i 件物品的費用(體積)是c[i],價值是w[i]。
求解將哪些物品裝入揹包可使價值總和最大。這裡每一件物品只能取一次
1.1 思路
根據子問題定義狀態,找出狀態轉移方程。
子問題就是:第i件物品是否放入揹包。如果不放,那麼第i件物品放入揹包中的總價值和第i-1件物品放入揹包的總價值相當。如果放入揹包,也就是求出第i-1件物品放入v-c[i]的揹包中時的值與第i件物品的價值的和,得到的就是總價值。
fi=max{fi-1,fi-1]+w[i]}
核心程式碼如下:
以下程式碼中,注意i和j的起始遍歷位置,從第1行和第1列開始,此時的1表示的就是物品的編號。
//traverse N goods
for(int i = 1;i<=N;i++){
for(int j = 1;j<=V;j++){
if(j-C[i]>=0){
f[i][j] = Math.max(f[i-1][j],f[i-1][j-C[i]]+W[i]);
}else{
f[i][j] = f[i-1][j]; //這行程式碼可以不要。
}
}
}
1.2 初始化問題
已知有物品4件(N=4),揹包容量V=10,每件物品的體積為5,4,6,3,每件物品的對應價值為10,40,30,50。
首先是關於初始化的問題,可以建立一個$(N+1)*(V+1)$的二維陣列,增設第一行第一列是為了迴圈遍歷的方便。一步步構建如下所示,初始時可以構建二維陣列如下:
V N | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
得到的結果如下:
如果沒有要求必須恰好裝滿揹包,那麼初始化的時候可以將二維陣列全部初始化為0。若要求恰好裝滿揹包,那麼在初始化的時候除了第一行和第一列為0,其餘均為無窮大。此時表最終的結果如下:
V N | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | inf | inf | inf | inf | 10 | 10 | 10 | 10 | 10 | 10 |
2 | 0 | inf | inf | inf | 40 | 10 | 10 | 10 | 10 | 50 | 50 |
3 | 0 | inf | inf | inf | inf | inf | 30 | 10 | 10 | 50 | 70 |
4 | 0 | inf | inf | 50 | inf | inf | 30 | 10 | 10 | 80 | 70 |
需要注意的是,此時上述的核心程式碼中就要有一定的調整,調整如下:
//traverse N goods
for(int i = 1;i<=N;i++){
for(int j = 1;j<=V;j++){
if(j-C[i]>=0){
f[i][j] = Math.max(f[i-1][j],f[i-1][j-C[i]]+W[i]);
}else{
//這裡的語句需要刪掉,否則初始化為負無窮大就沒有了意義。
}
}
}
1.3 被選擇的物品
當求解出最大價值的時候,如何求出所選擇的的物品?根據狀態轉移方程可知,當fi-1 = fi的時候,表示i沒有放入揹包,否則表示放入揹包,此時減去該揹包的體積,然後再判斷該體積下的情況,直到第一件物品。
for(int i = N;i>=1;i--){
if(f[i-1][V] != f[i][V]){
System.out.print(i+" ");
V -= C[i];
}
}
1.4 空間優化
每一次的fi的值只與fi-1的值有關,因此可以修改為一維陣列,f[v]表示將前i個物品裝入容量為v的揹包時的最大價值。主要是注意遍歷的時候,第二個是倒序,此時表示每一次迴圈的時候,上一次儲存的f[v]還沒有發生變化,從而說明一件物品被使用了一次。如果不是倒序還是使用正序,此時會發現可以重複選擇物品。
下面程式碼中列印輸出了每一次迴圈的時候f[v]的值。使用一維陣列實現,從而在空間上進行了優化。如果要實現恰好裝滿揹包,那麼初始化的時候,f[0]為0,其餘初始化為無窮大。
for(int i = 1;i<=N;i++){
for(int j = V;j>=0;j--){
if(j>=C[i]){
f[j] = Math.max(f[j], f[j-C[i]]+W[i]);
}
}
for(int j = 0;j<=V;j++){
System.out.print(f[j]+" ");
}
System.out.println();
}
1.5 使用二維陣列解決揹包問題
程式碼如下,注意其中兩種的同的初始化。對於恰好裝滿揹包問題,初始化陣列的時候可以這樣理解:初始化資料也就是初始化揹包狀態,即沒有任何物品裝入的時候的揹包狀態。如果要求揹包恰好裝滿,那麼最開始的時候只有揹包容量為0的時候才有可能被價值為0的nothing恰好裝滿(因為揹包的初始狀態時什麼也沒裝),如果容量大於1,那麼此時因為揹包什麼也沒裝,則沒有合法的解,屬於未定義狀態,可以初始化為$inf$
# 0-1 knapsack problem: two-dimensional array and one-dimensional array are used to resolve this problem
import numpy as np
def knapsack_2_array(N, V, C, W):
"""
:param N: the number of goods
:param V: the total volume of goods
:param C: the volume of each goods type-list
:param W: the weight of each goods type-list
:return: f[N][V]
"""
# initialization
# f = np.zeros((N+1, V+1))
# another initialization for just full of knapsack
f = np.zeros((N+1, V+1))
for i in range(N+1):
for j in range(V+1):
if j == 0:
f[i][j] = 0
else:
f[i][j] = float(`-inf`)
print(f)
# repeat
for i in range(1, N+1):
for j in range(1, V+1):
if j-C[i]>=0:
f[i][j] = max(f[i-1][j], f[i-1][j-C[i]]+W[i])
else:
f[i][j] = f[i-1][j]
return f
def get_choosed_goods_2_array(N, V, C, W, f):
for i in range(N, 0, -1):
if f[i][V] == f[i-1][V]:
continue
else:
V = V-C[i]
print(i)
if __name__ == `__main__`:
N = 5
V = 10
C = [0, 6, 4, 4, 2, 3]
W = [0, 8, 10, 4, 5, 5]
f1 = knapsack_2_array(N, V, C, W)
print(f1)
恰好裝滿時的初始化結果和執行結果
[[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]]
[[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf 8. -inf -inf -inf -inf]
[ 0. -inf -inf -inf 10. -inf 8. -inf -inf -inf 18.]
[ 0. -inf -inf -inf 10. -inf 8. -inf 14. -inf 18.]
[ 0. -inf 5. -inf 10. -inf 15. -inf 14. -inf 19.]
[ 0. -inf 5. 5. 10. 10. 15. 15. 14. 20. 19.]]
被選擇物品 4 3 2
沒有要求恰好裝滿時的執行結果
[[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 8. 8. 8. 8. 8.]
[ 0. 0. 0. 0. 10. 10. 10. 10. 10. 10. 18.]
[ 0. 0. 0. 0. 10. 10. 10. 10. 14. 14. 18.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 15. 19.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 20. 20.]]
輸出被選擇的物品
5 4 2
1.6 使用一維陣列解決問題
def knapsack_1_array(N, V, C, W):
# initialization
f = np.zeros(V+1)
# initialization for just full of knapsack
# f = np.zeros(V+1)
# for i in range(V+1):
# if i == 0:
# f[i] = 0
# else:
# f[i] = float(`-inf`)
print("Output intermediate process result")
for i in range(1, N+1):
for j in range(V, 0, -1):
if j-C[i] >= 0:
f[j] = max(f[j], f[j-C[i]]+W[i])
print(f)
print("Output the final result")
return f
def get_choosed_goods_1_array(N, V, C, W, f):
for i in range(N, 0, -1):
if f[V] == f[V-C[i]]+W[i]:
V = V-C[i]
print(i)
輸出結果
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
Output intermediate process result
[ 0. -inf -inf -inf -inf -inf 8. -inf -inf -inf -inf]
[ 0. -inf -inf -inf 10. -inf 8. -inf -inf -inf 18.]
[ 0. -inf -inf -inf 10. -inf 8. -inf 14. -inf 18.]
[ 0. -inf 5. -inf 10. -inf 15. -inf 14. -inf 19.]
[ 0. -inf 5. 5. 10. 10. 15. 15. 14. 20. 19.]
Output the final result
[ 0. -inf 5. 5. 10. 10. 15. 15. 14. 20. 19.]
4 3 2
沒有要求恰好裝滿的時候
Output intermediate process result
[0. 0. 0. 0. 0. 0. 8. 8. 8. 8. 8.]
[ 0. 0. 0. 0. 10. 10. 10. 10. 10. 10. 18.]
[ 0. 0. 0. 0. 10. 10. 10. 10. 14. 14. 18.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 15. 19.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 20. 20.]
Output the final result
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 20. 20.]
選擇的物品
5 4 2
關於上面在j遍歷的使用使用逆序的原因,下面舉一個例子:如果按照j順序遍歷,當i=1,j=1,2的時候,f[1]=f[2] = 0;當j=3的時候,f[3] = max(f[3], f[3-3]+w[1])= max(0,4) = 4;當j=4,5的時候,f[4]=[5] = 4。但是當j=6的時候,有f[6] = max(f[6], f[6-3]+w[1]) = max(0, 8)。這個8其實是因為第一件物品取了兩次得到的,這顯然不符合01揹包問題(每件物品只能取一次)
在輸出結果的時候,注意遍歷的方向,N是從最大值向最小值遍歷,因為所得陣列的最後一項內容是要求解的結果,所以需要從後向前遍歷
2 完全揹包問題
與0-1揹包問題的差別就是每一個物品可以重複多次放入,因此子問題就相應的需要改變,針對第i個物品,不是放入揹包或者不放入揹包,而應該是放入0次,或者多次。因此狀態轉移方程需要進行修改如下:
$$
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
$$
k值表示放入的次數。
import numpy as np
def getMaxWorth(amount, total_capacity, volumes, worths):
"""
amount: the quantity of things type-int
total_capacity: the volume of backpack type-int
volumes: the volume of each thing type-list
worths: the worth of each thing type-list
"""
# create a amount*total_capacity array and initialization
f = np.zeros((amount+1,total_capacity+1), dtype=int)
# repeat
for i in range(1,amount+1):
for j in range(1,total_capacity+1):
temp = []
k = 0
while k*volumes[i] <= j :
temp.append(f[i-1][j-k*volumes[i]]+k*worths[i])
k+=1
f[i][j] = max(temp)
return f
amount = 4
total_capacity=10
volumes=[0,5,4,6,3]
worths=[0,10,40,30,50]
getMaxWorth(amount, total_capacity, volumes, worths)
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 20],
[ 0, 0, 0, 0, 40, 40, 40, 40, 80, 80, 80],
[ 0, 0, 0, 0, 40, 40, 40, 40, 80, 80, 80],
[ 0, 0, 0, 50, 50, 50, 100, 100, 100, 150, 150]])
上面的時間複雜度分析:此問題和01揹包問題一樣,有$O(N*V)$個狀態需要求解,但是每一個狀態求解的時間已經發生了變化,求解狀態fi的時間為O(v/c[i]),總的時間複雜度超過了O(NV),其中N是物品數量,V是揹包容量。
2.1 簡單優化方案
若兩件物品i,j滿足 volumes[i]< volumes[j] 並且 worths[i]>worths[j],則說明i物品體積小並且價值高,可以替換物品j,也就是可以將物品j刪除掉。
import numpy as np
def get_can_be_delete_item_index(volumes, worths):
"""
delete items with small volume and small value
"""
index_for_delete = set()
for i in range(1, len(volumes)-1):
for j in range(i+1, len(volumes)):
if volumes[i] > volumes[j] and worths[i] < worths[j]:
index_for_delete.add(i)
return index_for_delete
def getMaxWorth(amount, total_capacity, volumes, worths):
"""
amount: the quantity of things type-int
total_capacity: the volume of backpack type-int
volumes: the volume of each thing type-list
worths: the worth of each thing type-list
"""
new_volumes = []
new_worths = []
index_delete = get_can_be_delete_item_index(volumes, worths)
for i in range(0, len(volumes)):
if i not in index_delete:
new_volumes.append(volumes[i])
new_worths.append(worths[i])
# print(new_volumes, new_worths)
amount = len(new_volumes)
# create a amount*total_capacity array and initialization
f = np.zeros((amount,total_capacity+1), dtype=int)
volumes = new_volumes
worths = new_worths
# repeat
for i in range(1,amount):
for j in range(1,total_capacity+1):
temp = []
k = 0
while k*volumes[i] <= j :
temp.append(f[i-1][j-k*volumes[i]]+k*worths[i])
k+=1
f[i][j] = max(temp)
return f
amount = 4
total_capacity=10
volumes=[0,5,4,6,3]
worths=[0,10,40,30,50]
getMaxWorth(amount, total_capacity, volumes, worths)
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 50, 50, 50, 100, 100, 100, 150, 150]])
此問題還可以試著輸出一下被選擇的物品和次數之間的關係,以及如何選擇物品可以恰好裝滿
3 多重揹包問題
在問題的基礎上,增加每件物品的件數,比如,第i種物品最多n[i]件可用。
轉移狀態方程:共有N種物品,假設fi-1是將前i-1種物品放入容量v中時的最大價值,那麼針對第i種物品,它最多可以放入n[i]次,此時的轉義方程如下:
$$
f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i] | 0<=k<=n[i])
$$
因此,在上面程式碼的基礎上做了一些改變,如下。其中需要注意在逆推得到每件物品的時候,從最後一種物品開始,體積是從V開始並且不是遍歷V,而是根據結果動態改變計算出下一次的V
import numpy as np
def getMaxWorth(amount, total_capacity, volumes, worths, counts):
"""
amount: the quantity of things type-int
total_capacity: the volume of backpack type-int
volumes: the volume of each thing type-list
worths: the worth of each thing type-list
counts: the number of each thing type-list
"""
# create a amount*total_capacity array and initialization
f = np.zeros((amount + 1, total_capacity + 1), dtype=int)
# repeat
for i in range(1, amount + 1):
for j in range(1, total_capacity + 1):
temp = []
k = 0
while k * volumes[i] <= j and k <= counts[i]:
temp.append(f[i - 1][j - k * volumes[i]] + k * worths[i])
k += 1
f[i][j] = max(temp)
return f
def get_the_goods(f, amount, total_capacity, counts, volumes,worths):
"""
f: the f[i][v]
amount: the number of things
return: the goods and its choosed times
"""
flag = False
for i in range(amount, 0, -1):
flag = False
for k in range(1, counts[i] + 1):
if total_capacity-k*volumes[i] >= 0:
# note: the point is to use the state transfer formula to compute the k value
while f[i][total_capacity] == f[i - 1][total_capacity-k*volumes[i]] + k * worths[i]:
total_capacity -= k * volumes[i]
print("第%d件物品取%d件" % (i, k))
flag = True
break
if flag:
break
amount = 3
total_capacity = 8
volumes = [0, 1,2,2]
worths = [0, 6,10,20]
counts = [0,10,5,2]
f = getMaxWorth(amount, total_capacity, volumes, worths, counts)
print(f)
get_the_goods(f, amount, total_capacity, counts, volumes,worths)
輸出
[[ 0 0 0 0 0 0 0 0 0]
[ 0 6 12 18 24 30 36 42 48]
[ 0 6 12 18 24 30 36 42 48]
[ 0 6 20 26 40 46 52 58 64]]
第3件物品取2件
第1件物品取4件
根據以上程式碼可以發現,該演算法的時間複雜度是$O(V imes sum{counts[i]})$(時間複雜度計算:比如第一種物品的時候,會執行$V imes counts[1]$次,第二種物品,程式會執行$V imes counts[2]$次,因此可以得到總的時間複雜度)可以發現時間複雜度與物品的次數有關。
該方法可以轉化成01揹包問題進行思考,就是將第i種物品,如果出現次數為n[i],此時可以得到一個包含有$sum{n[i]}$中物品的01揹包問題,此時的時間複雜度仍然是$O(V imes sum{counts[i]})$
方法:將第i種物品分成若干件物品,其中每件物品有一個係數,這件物品的費用和價值是原來的物品的費用和價值乘以這個係數,使這些係數分別為$1,2,4,…2^{(k-1)}$,其中k應該滿足 $n[i]-2^k+1>0$的最大整數,比如,n[i]=13,那麼k最大為3,從而得到1,2,4;之後使用n[i]-(1+2+4)得到6,因此可以將這個物品的係數分為1,2,4,6;這樣的係數可以表示0~n[i]中的每一個整數,同時n[i]件物品對應的就變成了$log(n[i])$件物品,事件複雜度為$O(V imes sum{log({n[i]}))}$。
其中,將揹包中對應數量的物品拆分成指定係數的物品程式碼如下:
def multiple_pack(volumes, worths, counts):
"""
make the count of goods to different v and w goods
:param volumes: list
:param worths: list
:param counts: list
:return: tuple(list,list,list)
"""
v = [0]
w = [0]
for i in range(1, len(counts)):
# get the max value k according to the counts[i]
k = int(math.sqrt(counts[i]))
# generate the coeffcient according to k
k_list = [math.pow(2,item) for item in range(k)]
k_list.append(counts[i]-sum(k_list))
for item in k_list:
v.append(int(item * volumes[i]))
w.append(int(item * worths[i]))
return (v, w)
輸入
volumes = [0, 1, 2, 2]
worths = [0, 6, 10, 20]
counts = [0, 10, 5, 2]
輸出
[0, 1, 2, 4, 3, 2, 4, 4, 2, 2]
[0, 6, 12, 24, 18, 10, 20, 20, 20, 20]
之後使用01揹包問題進行求解,程式碼如下所示:
def get_f_of_01_backpack(n, total_capacity, volumes, worths):
"""
:param n: the number of goods
:param volumes: list
:param worths: list
:return: f
"""
# initialization
f = np.zeros((n+1, total_capacity+1))
#repeat
for i in range(1, n+1):
for j in range(1, total_capacity+1):
if j-volumes[i] >= 0:
f[i][j] = max(f[i-1][j], f[i-1][j-volumes[i]]+worths[i])
else:
f[i][j] = f[i-1][j]
return f
輸入
n = 9
total_capacity = 8
volumes = [0, 1, 2, 4, 3, 2, 4, 4, 2, 2]
worths = [0, 6, 12, 24, 18, 10, 20, 20, 20, 20]
輸出
[[ 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 6. 6. 6. 6. 6. 6. 6. 6.]
[ 0. 6. 12. 18. 18. 18. 18. 18. 18.]
[ 0. 6. 12. 18. 24. 30. 36. 42. 42.]
[ 0. 6. 12. 18. 24. 30. 36. 42. 48.]
[ 0. 6. 12. 18. 24. 30. 36. 42. 48.]
[ 0. 6. 12. 18. 24. 30. 36. 42. 48.]
[ 0. 6. 12. 18. 24. 30. 36. 42. 48.]
[ 0. 6. 20. 26. 32. 38. 44. 50. 56.]
[ 0. 6. 20. 26. 40. 46. 52. 58. 64.]]
通過逆向推導,可以得到被選擇的物品。從最後一個物品開始遍歷,直到到達第一個物品,需要注意的是提及V是根據結果進行動態調整的,最開始應該是體積V。
def get_path_of_01_backpack(n,total_capacity, volumes, f):
for i in range(n, 0,-1):
if f[i][total_capacity] != f[i-1][total_capacity]:
total_capacity -= volumes[i]
print(i)
輸出
9
8
3
與上面的第一種方法進行對比可以看出結果是一樣的,選擇的東西也是一樣的,只不過一個是拆開的物品。
本小結主要是關於將演算法的複雜度進行改進,需要特別注意的是“拆分物品”的思想和方法。
4 混合三種揹包問題
將前面三種揹包的問題混合起來,也就是有的物品可以取一次(01揹包),有的物品可以無限次取(完全揹包),有的物品可以取有限次(多重揹包問題)。應該怎麼求解。我的思路就是有一個儲存揹包次數的陣列,如果可取有限次,就對應的是物品的次數,如果是無限次,就先根據物品的體積和總體積得到每件物品的最大可取次數並填入陣列中。
5 二維費用的揹包問題
對於每件物品,具有兩種不同的費用:選擇這件物品的時候必須同時付出兩種代價,對於每種代價都有一個可以付出的最大值(比如揹包容量V和最多可取物品數量M),問怎樣選擇物品可以達到最大價值。設第i件物品的兩種代價分別為a[i]和b[i],兩種代價的最大值為V,M。
思路:費用增加一維,只需狀態增加一維即可.設fi[m]表示前i件物品付出v和m代價時的最大價值,那麼狀態轉移方程如下:
$$
f[i][v][m] = max{f[i-1][v][m], f[i-1][v-a[i]][m-b[i]]+w[i]}
$$
當然,和前面的方法一樣,也能使用二維陣列來解決:當每件物品只可以取一次的時候,變數v,m逆序迴圈,當物品可以取無限次的時候,可以採用順序的迴圈。當物品有如多重揹包問題的時候,可以拆分物品,然後使用01揹包問題求解。
5.1 使用三維陣列解決問題
# 2-dimensional cost knapsack problem
# 0-1 knapsack problem: two-dimensional array and one-dimensional array are used to resolve this problem
import numpy as np
def knapsack_2_array(N, V, M, A, B, W):
"""
:param N: the number of goods
:param V: the total volume1 of goods
:param M: the total volume2 of goods
:param A: the volume1 of each goods type-list
:param B: the volume2 of each goods type-list
:param W: the weight of each goods type-list
:return:
"""
# initialization
f = np.zeros((V+1, M+1))
# initialization for just full of M
# f = np.zeros((V+1, M+1))
# for i in range(V+1):
# for j in range(M+1):
# if j == 0:
# f[i][j] = 0
# else:
# f[i][j] = float(`-inf`)
# repeat
for i in range(1, N+1):
for j in range(V, 0,-1):
for k in range(M, 0, -1):
if j-A[i] >= 0 and k-B[i] >= 0:
f[j][k] = max(f[j][k], f[j-A[i]][k-B[i]]+W[i])
# print(f)
return f
def get_choosed_goods(N, V, M, A, B, W, f):
for i in range(N, 0, -1):
if f[V][M] == f[V-A[i]][M-B[i]]+W[i] and f[V][M] != float(`-inf`) and f[V-A[i]][M-B[i]] != float(`-inf`):
V -= A[i]
M -= B[i]
print(i)
if __name__ == `__main__`:
N = 5
V = 10
M = 10
A = [0,6, 4, 2, 2, 3]
B = [0, 8,4,2,2,3]
W = [0,8,10,4,5,5]
f1 = knapsack_2_array(N, V, M, A, B, W)
print(f1)
如果沒有要求揹包恰好裝滿,初始化的時候全部初始化為0即可,那麼輸出結果是:
[[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 5. 5. 5. 5. 5. 5. 5. 5. 5.]
[ 0. 0. 5. 5. 5. 5. 5. 5. 5. 5. 5.]
[ 0. 0. 5. 5. 10. 10. 10. 10. 10. 10. 10.]
[ 0. 0. 5. 5. 10. 10. 10. 10. 10. 10. 10.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 15. 15.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 15. 15.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 15. 15.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 15. 20. 20.]
[ 0. 0. 5. 5. 10. 10. 15. 15. 19. 20. 20.]]
5 4 2
如果要求恰好裝滿M,也就是M=0的這一列為0,其餘位-inf,那麼初始化的結果為:
[[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]]
輸出結果
[[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf -inf -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf 5. -inf -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf 5. 5. -inf -inf -inf -inf -inf -inf -inf]
[ 0. -inf 5. 5. 10. -inf -inf -inf -inf -inf -inf]
[ 0. -inf 5. 5. 10. 10. -inf -inf -inf -inf -inf]
[ 0. -inf 5. 5. 10. 10. 15. -inf 8. -inf -inf]
[ 0. -inf 5. 5. 10. 10. 15. 15. 8. -inf -inf]
[ 0. -inf 5. 5. 10. 10. 15. 15. 8. -inf 13.]
[ 0. -inf 5. 5. 10. 10. 15. 15. 8. 20. 13.]
[ 0. -inf 5. 5. 10. 10. 15. 15. 19. 20. 13.]]
被選擇的物品
4 1
注意在輸出被選擇的物品的時候,是需要逆序輸出的,同時需要注意存在float(`-inf`) = float(`-inf`)+10,也就是inf與一個數的和與inf相等,因此需要在輸出的時候注意值是否為inf
思考:如何保證恰好滿足V和M?該如何進行初始化??
6 分組的揹包問題
有N件物品和一個容量為V的揹包,第i件物品的體積和價值分別為c[i]和w[i]。這些物品被劃分為若干組,每組中的物品相互衝突,最多選擇一件。求解將哪些物品裝入揹包的時候可以使這些物品的體積總和不超過揹包容量,且可以取得最大的價值。
思路:因為物品劃分成了組,所以就以組作為索引,而不是物品。則問題可以描述為,針對第k組,選擇本組中的某一件還是一件都不選。fk表示前k組在體積為v的時候取得的最大價值,那麼狀態轉移方程為:
$$
f[k][v] = max{f[k-1][v], f[k-1][v-c[i]]+w[i]| i是第k組中的物品}
$$
核心程式碼如下:
for k in 1-->K:
for v in V-->0:
# 對於k組中的每一個物品
for i in k:
f[v] = max{f[v], f[v-c[i]]+w[i]}
分組揹包問題可以使用完全揹包問題中的提到的一個簡單的優化方法。
相關文章
- 揹包九講(部分)
- 模板問題:Knapsack (LeetCode: Coin Change)[揹包問題九講筆記]LeetCode筆記
- 揹包問題(01揹包與完全揹包)
- 揹包問題
- 演算法--揹包九講(詳細講解+程式碼)演算法
- 01揹包問題
- 01 揹包問題
- 01揹包和完全揹包問題解法模板
- 經典揹包問題
- 揹包問題大合集
- 部分揹包問題(挖
- 005多重揹包問題||
- 從【零錢兌換】問題看01揹包和完全揹包問題
- 揹包問題例題總結
- 【leetcode】揹包問題彙總LeetCode
- 2. 01揹包問題
- 揹包問題解題方法總結
- JavaScript中揹包問題(面試題)JavaScript面試題
- 51nod 1597 有限揹包計數問題 (揹包 分塊)
- chapter12-2-揹包問題APT
- 關於各種揹包問題
- 【動態規劃】揹包問題動態規劃
- 01揹包問題的解決
- 二維費用揹包問題
- 深入剖析多重揹包問題(上篇)
- 深入剖析多重揹包問題(下篇)
- 揹包問題----動態規劃動態規劃
- leetcode題解(0-1揹包問題)LeetCode
- 揹包問題的一道經典問題
- HDU Piggy-Bank(完全揹包問題)
- 整數0-1揹包問題
- 動態規劃 01揹包問題動態規劃
- 【動態規劃】01揹包問題動態規劃
- 動態規劃--01揹包問題動態規劃
- 動態規劃篇——揹包問題動態規劃
- 動態規劃-01揹包問題動態規劃
- 你真的懂01揹包問題嗎?01揹包的這幾問你能答出來嗎?
- Codeup 貨幣系統(完全揹包問題)