限界分支法:01揹包問題,優先順序佇列(包含解的追蹤)
阿新 • • 發佈:2018-12-17
前面提到:
不知道大家注意到沒有?上述實現方式沒有使用單位體積價值的排序,和之前提到01揹包回溯法基於單位體積價值實現不一樣(先裝單位體積價值高的)。
我們網上經常看到都是基於以上實現的,到底這個用有什麼好處了?實際上基於排序的單位體積價值是一個非常精確的限界函式
基於優先順序佇列的實現方式,就需要用到以上的結構:
優先順序佇列方式需要資料預處理,為什麼需要預處理資料了,這裡使用的是優先順序佇列,這個優先順序的選區非常重要,也非常重要
之前的策略curValue+rest就不行了,不是不行,是不夠好,那是一個粗糙的界,可以看到一開始選的就不是最優,然後又往回跳躍,因為在這種策略下,無法保證最優,這時需要精心挑選優先順序,使之儘可能少往回跳躍或者跳低點,就像思考貪心策略那樣,實際上就是思考貪心策略,貪心就是配合優先順序佇列使用的這裡首先把資料安裝單位體積價值降序排列。
程式碼實現如下:
import heapq
class Nodes:
def __init__(self,CurValue=None,CurCost=None,depth =None,parent=None,Flag=None):
# 部分解所佔體積
self.CurCost = CurCost
# 部分解所佔價值
self.CurValue = CurValue
# 處於那一層
self.depth = depth
# 當前結點是否選擇了物品
self.isleft = Flag
# 前一個結點是誰
self.parent = parent
class PQ_pack_01_with_solution_tracking:
def __init__(self,N,V,C,W):
self.num = N
self.volume = V
self.cost = C
self.value = W
#(0當前的價值,1,當前體積,2當前的深度,3父節點,4是否選擇了該物品)
self.bestnode = Nodes(0,0,0,None,False)
# 儲存最後的方案
self.bestsolution = [False]*N
# 資料預處理,為什麼需要預處理資料了,這裡使用的是優先順序佇列,這個優先順序的選區非常重要,也非常重要
# 之前的策略curValue+rest就不行了,因為在這種策略下,無法保證最優,這時需要精心挑選優先順序,就像
# 思考貪心策略那樣,實際上就是思考貪心策略,貪心就是配合優先順序佇列使用的
# 這裡首先把資料安裝單位體積價值降序排列,self.order記錄與之前排列的標號,用於恢復最後結果
self._cost,self._value,self.order = self.sort_group(N,C,W)
# 把資料安裝單位體積價值降序排列
def sort_group(self,N,C,W):
# 原來資料的標號
O = [i for i in range(N)]
perp = [0]*N
for i in range(N):
perp[i] = W[i]/C[i]
for i in range(N-1):
for j in range(i+1,N):
if perp[i] < perp[j]:
temp = perp[i]
perp[i] = perp[j]
perp[j] = temp
temp = O[i]
O[i] = O[j]
O[j] = temp
temp = C[i]
C[i] = C[j]
C[j] = temp
temp = W[i]
W[i] = W[j]
W[j] = temp
return C,W,O
# 限界函式,這個限界函式就非常精確,確保了每一次往下都是最優的策略
def bound(self,depth,CurCost,CurValue):
left_weight = self.volume - CurCost
b = CurValue
while depth < self.num and self._cost[depth] <= left_weight:
left_weight -=self._cost[depth]
b += self._value[depth]
depth +=1
if depth < N:
b += (self._value[depth]/self._cost[depth]) * left_weight
return b
def PQ_pack_01(self):
pqueue = []
# 初始化,從root開始
current_node = None
current_value = 0
current_cost = 0
depth = 0
# 終止條件,優先順序佇列裡面存的是(0當前的價值,1,當前體積,2當前的深度,3父節點,4是否選擇了該物品)
# 只要取第self.num層最優的current_value就可以了,只要到self.num層終止就行了
while depth != self.num:
# 滿足約束條件,存入左結點
if current_cost + self._cost[depth] <= self.volume:
# 每次進入左結點更新最優質,這樣方便剪枝,讓沒有必要的點不放進優先順序佇列
if current_value + self._value[depth] > self.bestnode.CurValue:
self.bestnode.CurValue =current_value + self._value[depth]
# 確定待放入結點的上界
temp = self.bound(depth+1,current_cost+self._cost[depth],current_value+self._value[depth])
# 把待放入結點的上界,當前價值,當前花費,當前層次,父親,是左是右放入優先順序佇列
# 因為是要求最大堆,隨意優先順序取了-,heapq預設是取最小堆,直接求最大堆的包還不知道怎麼用
heapq.heappush(pqueue,(-temp,Nodes(current_value + self.value[depth],current_cost+self._cost[depth],depth+1,current_node,True)))
# 對於右結點計算上界
up = self.bound(depth+1,current_cost,current_value)
# 加入上界小於當前最優質,就沒有必要放入優先順序佇列,免得給優先順序佇列增加負擔
# 等於的情況需要放進去,因為這時路徑必須的,沒有等於0,就沒法深入了
if up >= self.bestnode.CurValue:
heapq.heappush(pqueue,(-up,Nodes(current_value,current_cost,depth+1,current_node,False)))
# 彈出下一個最優的結點,0代表上界,1包含了所需要的資訊
current_node = heapq.heappop(pqueue)[1]
current_value = current_node.CurValue
current_cost = current_node.CurCost
depth = current_node.depth
print(depth,current_value)
self.bestnode = current_node
print(self.bestnode.CurValue)
# 追蹤解
def solution_tracking(self):
# 追蹤解,獲取最優方案
BestResult =[False]*N
for i in range(self.num -1,-1,-1):
BestResult[i] = self.bestnode.isleft
self.bestnode = self.bestnode.parent
# 將最優方案翻譯成原來的排序
for i in range(N):
if BestResult[i]:
self.bestsolution[self.order[i]] = True
print(self.bestsolution)
N = 8
V = 30
C = [11,2,3,9,13,6,15,7]
W = [5.0,2.0,5.0,7.0,5.0,11.0,6.0,14.0]
tt =PQ_pack_01_with_solution_tracking(N,V,C,W)
tt.PQ_pack_01()
tt.solution_tracking()
1 14.0
2 25.0
3 30.0
4 32.0
5 39.0
6 39.0
7 39.0
4 30.0
8 39.0
39.0
[False, True, True, True, False, True, False, True]