有N件物品和一個容量爲V的揹包。第i件物品的重量是w[i],價值是v[i]。求解將哪些物品裝入揹包可以使這些物品的重量總和不超過揹包容量,且價值總和最大。在選擇裝入揹包的物品時,對於每種物品i,只能選擇裝包或不裝包,不能裝入屢次,也不能部分裝入,所以成爲0-1揹包問題。python
形式化描述爲:給定n個物品,揹包容量C >0,重量 第i件物品的重量w[i]>0, 價值v[i] >0 , 1≤i≤n.要求找一n元向量(X1,X2,…,Xn,), Xi∈{0,1}, 使得 ∑(w[i] * Xi) ≤C,且∑ v[i] * Xi達最大.即一個特殊的整數規劃問題。算法
數學描述爲: 數組
求解最優值:app
設最優值m(i,j)爲揹包容量爲j、可選擇物品爲i,i+1,……,n時的最優值(裝入包的最大價值)。因此原問題的解爲m(1,C)ide
將原問題分解爲其子結構來求解。要求原問題的解m(1,C),可從m(n,C),m(n-1,C),m(n-2,C).....來依次求解,便可裝包物品分別爲(物品n)、(物品n-1,n)、(物品n-2,n-1,n)、……、(物品1,物品2,……物品n-1,物品n)。最後求出的值即爲最優值m(1,C)。函數
若求m(i,j),此時已經求出m(i+1,j),即第i+1個物品放入和不放入時這兩者的最大值。spa
對於此時揹包剩餘容量 j=0,1,2,3……C,分兩種狀況:code
(1)當 w[i] > j ,即第i個物品重量大於揹包容量j時,m(i,j)=m(i+1,j)blog
(2)當 w[i] <= j ,即第i個物品重量不大於揹包容量j時,這時要判斷物品i放入和不放入對m的影響。索引
若不放入物品i,則此時m(i,j)=m(i+1,j)
若放入物品i,此時揹包剩餘容量爲 j-w[i],在子結構中已求出當容量k=0,1,2……C 時的最優值m(i+1,k)。因此此時m(i,j)=m(i+1,j-w[i])+v[i]。
取上述兩者的最大值,即m(i,j) = max{ m(i+1,j),m(i+1,j-w[i])+v[i] }
總結得出狀態轉移方程爲:
該算法的python代碼實現:
1 # 0-1揹包問題 2 __author__ = 'ice' 3 4 5 # 揹包容量0~capacity,不是0~capacity-1 6 def knapsack(weight, value, capacity): 7 if len(weight) != len(value): 8 print("parameter err!") 9 return 10 obj_num = len(weight) 11 result = [[] for x in range(obj_num)] 12 divide = min(weight[-1], capacity) 13 result[-1] = [0 for x in range(divide)] 14 result[-1].extend(value[-1] for x in range(divide, capacity + 1)) 15 for i in reversed(list(range(1, obj_num - 1))): 16 divide = min(weight[i], capacity) 17 for j in range(divide): 18 result[i].append(result[i + 1][j]) 19 for j in range(divide, capacity + 1): 20 result[i].append(max(result[i + 1][j], result[i + 1][j - weight[i]] + value[i])) 21 22 result[0] = {capacity: result[1][capacity]} 23 if weight[0] <= capacity: 24 result[0][capacity] = max(result[1][capacity], result[1][capacity - weight[0]] + value[0]) 25 26 vector = [0 for x in range(obj_num)] 27 capacity_temp = capacity 28 for i in range(obj_num - 1): 29 if result[i][capacity_temp] != result[i + 1][capacity_temp]: 30 vector[i] = 1 31 capacity_temp -= weight[i] 32 33 if capacity_temp == 0: 34 vector[-1] = 0 35 else: 36 vector[-1] = 1 37 38 return {'total_value': result[0][capacity], 'select': vector}
可是,可是!!該算法有兩個明顯的缺點:1,基於上述代碼,由於數組索引的須要,要求所給物品重量爲整數。2,當揹包容量C很大時,算法所需計算時間較多。當C>2^n時,須要Ω(n*2^n)計算時間。
因此,因此!!改進算法以下:
對於函數m(i,j)的值,當i肯定,j爲自變量時,是單調不減的跳躍式增加,如圖所示。而這些跳躍點取決於在(物品i,物品i+1,……物品n)中選擇放入哪些物品使得在放入重量小於容量 j (0<=j<=C)的狀況下m取得最大值。對於每個肯定的i值,都有一個對應的跳躍點集Pi={ ( j, m(i,j) ),……}。j始終小於等於C
(1)開始求解時,先求Pi,初始時Pn+1={(0,0)},i=n+1,由此按下列步驟計算Pi-1,Pi-2……P1,即Pn,Pn-1,……P1
(2)求Qi,利用Pi求出m(i,j-w[i-1])+v[i-1],即Pi當放入物品i-1後的變化後的跳躍點集Qi={ ( j+w[i-1], m(i,j)+v[i-1] ),……},在函數圖像上表現爲全部跳躍點橫軸座標右移w[i-1],縱軸座標上移v[i-1]。
(3)求Pi-1,即求Pi∪Qi而後再去掉受控跳躍點後的點集。此處有個受控跳躍點的概念:若點(a,b),(c,d)∈Pi∪Qi,且a<=c,b>d,則(c,d)受控於(a,b),因此(c,d)∉Pi-1。去掉受控跳躍點,是爲了求得在物品i-1放入後m較大的點,即 使m取最優值的跳躍點。
由此計算得出Pn,Pn-1,……,P1。求得P1的最後那個跳躍點即爲所求的最優值m(1,C)。
舉個栗子:
n=5,c=10,w={2,2,6,5,4},v={6,3,5,4,6}。跳躍點的計算過程以下:
初始時p[6]={(0,0)}
所以,q[6]=p[6]⊕(w[5],v[5])={(4,6)}
p[5]={(0,0),(4,6)}
q[5]=p[5]⊕(w[4],v[4])={(5,4),(9,10)}
p[4]={(0,0),(4,6),(9,10)} p[5]與q[5]的並集p[5]∪q[5]={(0,0),(4,6),(5,4),(9,10)}中跳躍點(5,4)受控於跳躍點(4,6)。將受控跳躍點(5,4)清除後,獲得p[4]
q[4]=p[4]⊕(6,5)={(6,5),(10,11)}
p[3]={(0,0),(4,6),(9,10),(10,11)}
q[3]=p[3]⊕(2,3)={(2,3),(6,9)}
p[2]={(0,0),(2,3),(4,6),(6,9),(9,10),(10,11)}
q[2]=p[2]⊕(2,6)={(2,6),(4,9),(6,12),(8,15)}
p[1]={(0,0),(2,6),(4,9),(6,12),(8,15)}
p[1]的最後的那個跳躍點(8,15)即爲所求的最優值,m(1,C)=15
最後,python代碼的實現:
1 class Point: 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 6 7 # 0-1揹包問題 改進 8 def knapsack_improve(weight, value, capacity): 9 if len(weight) != len(value): 10 print("parameter err!") 11 return 12 obj_num = len(weight) 13 jump_points_p = [[] for x in range(obj_num)] 14 jump_points_q = [[] for x in range(obj_num)] 15 jump_points_p.append([Point(0, 0)]) 16 jump_points_q.append([Point(weight[obj_num - 1], value[obj_num - 1])]) 17 for i in reversed(list(range(1, obj_num))): 18 jump_points_p[i] = merge_points(jump_points_p[i + 1], jump_points_q[i + 1]) 19 jump_points_q[i] = [Point(point.x + weight[i - 1], point.y + value[i - 1]) for point in jump_points_p[i] if 20 point.x + weight[i - 1] <= capacity] 21 result = merge_points(jump_points_p[1], jump_points_q[1]) 22 return result 23 24 25 def merge_points(points_x, points_y): 26 x_len = len(points_x) 27 y_len = len(points_y) 28 merged_points = [] 29 i = j = 0 30 while True: 31 if i == x_len or j == y_len: 32 break 33 if points_x[i].x < points_y[j].x: 34 merged_points.append(points_x[i]) 35 if points_x[i].y >= points_y[j].y: 36 j += 1 37 i += 1 38 else: 39 merged_points.append(points_y[j]) 40 if points_y[j].y >= points_x[i].y: 41 i += 1 42 j += 1 43 while i < x_len: 44 if points_x[i].x > merged_points[-1].x and points_x[i].y > merged_points[-1].y: 45 merged_points.append(points_x[i]) 46 i += 1 47 while j < y_len: 48 if points_y[j].x > merged_points[-1].x and points_y[j].y > merged_points[-1].y: 49 merged_points.append(points_y[j]) 50 j += 1 51 return merged_points 52 53 54 result = knapsack_improve([2, 2, 6, 5, 4], [6, 3, 5, 4, 6], 10) 55 print() 56 for point in result: 57 print('(' + str(point.x) + ',' + str(point.y) + ')', end=' ') 58 59 #(0,0) (2,6) (4,9) (6,12) (8,15)