本期任務:介紹算法中關於回溯思想的幾個經典問題
一、問題描述
給定n種物品和一揹包。物品i的重量是wi>0,其價值爲vi>0,揹包的容量爲c。
問應如何選擇裝入揹包中的物品,使得裝入揹包中物品的總價值最大? (要求使用回溯法)
輸入:
n, c = 4, 7
w = [3, 5, 2, 1]
v = [9, 10, 7, 4]
輸出:
20
[0, 2, 3]
二、算法思路
-
0-1揹包問題是典型的“多階段決策最優解”問題:每個物品決策一次(拿或者不拿),共決策n次(n爲物品數量);最優解是揹包容量限制下的最大價值。
-
本題使用回溯算法暴力窮舉所有可能的排列方式,並通過剪枝策略進行優化。
-
暴力窮舉,每個物品都可能有2種(拿或者不拿),所有可能方式共有,窮舉過程遵循深度優先搜索規則。
-
維護一個長度爲n的數組res,用於保存每個物品的是否取。
-
剪枝策略:揹包容量無法容納當前物品時,剪枝跳過。
-
結算情形:所有物品遍歷完成時。
示例對應的遞歸樹如下,其中代表裝入編號爲0的物品前,揹包容量爲7,當前總價值爲0:
三、Python代碼實現
class Package01():
def __init__(self, n, w, v):
self.n = n # 物品數量
self.w = w # 物品重量
self.v = v # 物品價值
self.max_v = 0 # 記錄最大價值
self.cur_ls = [False] * n # 記錄當前物品清單
self.res = [] # 記錄最大價值對應的物品清單
def package01(self, cur_c, cur_v=0, index=0):
if index == self.n: # 當遍歷完所有物品進行結算!!!
if self.max_v < cur_v:
self.max_v = cur_v
self.res = list(self.cur_ls)
return
if cur_c >= self.w[index]: # 揹包裝的下當前物品時
self.cur_ls[index] = True
self.package01(cur_c - self.w[index], cur_v + self.v[index], index + 1)
self.package01(cur_c, cur_v, index + 1) # 不裝當前物品
def main():
n, c = 4, 7
w = [3, 5, 2, 1]
v = [9, 10, 7, 4]
pk = Package01(n, w, v)
pk.package01(7, 0, 0)
print(pk.max_v)
print([i for i, _ in enumerate(pk.res) if _])
if __name__ == '__main__':
main()
輸出結果:
20
[0, 2, 3]