貨郎問題:回溯法和限界分支法
阿新 • • 發佈:2018-12-17
這個問題可以堪稱一個全排列,[起點,剩下的全排列]
回溯法
import numpy as np
class backtracking_Traveling_saleman:
# 初始化,明確起點
def __init__(self,graph,start=0):
# 頂點的個數
self.vertex_num = len(graph)
self.graph = graph
self.start = start
# 當前解,當前解為頂點的集合[起點,頂點1,...頂點n-1],初始時,第0個為起點
# 從第一個到n-1個為排列樹
# 解為[起點,頂點1,...頂點n-1]
# 解的目標函式為:起點到頂點1距離(depth =1)+頂點1到頂點2的+...+頂點depth-1到depth的距離
# 頂點n-2到頂點n-1(depth=n-1)距離,最後還要構成環路+頂點n-1到起點的距離,求上述和的最小值。
self.curSolution = [i for i in range(self.vertex_num)]
# 用於存最好的解
self.bestSolution = [ -1] *self.vertex_num
# 當前花費初始距離為0
self.curCost = 0
# 界初始為很大,得到一個解之後更新,也可以提前更新
self.bestCost = np.inf
def backtracking(self,depth):
# 當到達最後一個頂點,為遞迴出口
if depth == self.vertex_num-1:
# 最後一層時,目前函式應該為:當前的距離和+前一個頂點到最後頂點距離+最後頂點到起點的距離
temp_cost = self.curCost + self.graph[self.curSolution[depth-1]][self.curSolution[depth]] + self.graph[self.curSolution[depth]][self.curSolution[self.start]]
# 當前解優於最優解的話,更新最優解,假如求可行解的話,就需要把可行解儲存起來
if temp_cost < self.bestCost:
self.bestCost = temp_cost
self.bestSolution[:] = self.curSolution[:]
else:
# 下面就是排列樹的處理,我們需要求除了起點之外的[頂點1,...頂點n-1]n-1個起點的全排列
# 我們處理的是標號,也就是self.curSolution = [i for i in range(self.vertex_num)]
# 所以我們全排列都是self.curSolution裡面的數
for i in range(depth,self.vertex_num):
# self.curSolution[depth],self.curSolution[i] = self.curSolution[i],self.curSolution[depth]
# 當滿足我們定義的界時,才可以進入下一層,也就是走過的長度<最優值的話(注意我們這兒要求的是最小值),我們才繼續搜尋,否則剪掉
# 有沒有人注意到這裡是depth-1到i,而不是depth-1 到 depth?
# 實際上我們學習到模板應該是,先交換,然後滿足constraint() && bound() 回溯,最後再交換
# 編碼時先交換的話,這個地方就應該寫成depth-1 到 depth,還沒交換的話就是depth-1到i
# 這裡是做了優化處理,不滿足條件的話,交換都沒有必要執行
if self.curCost + self.graph[self.curSolution[depth-1]][self.curSolution[i]] < self.bestCost:
# 全排列,把 當前 和第一位交換,除第一位以外剩下的的進行全排列
self.curSolution[depth],self.curSolution[i] = self.curSolution[i],self.curSolution[depth]
# 同理這裡為什麼是depth-1 到 depth,為不是depth-1到i,也是一樣的道理
self.curCost += self.graph[self.curSolution[depth-1]][self.curSolution[depth]]
# 進入下一層
self.backtracking(depth+1)
# 回溯處理,恢復現場
self.curCost -= self.graph[self.curSolution[depth-1]][self.curSolution[depth]]
self.curSolution[depth],self.curSolution[i] = self.curSolution[i],self.curSolution[depth]
# self.curSolution[depth],self.curSolution[i] = self.curSolution[i],self.curSolution[depth]
def print_Result(self):
# 我們需要求除了起點之外的[頂點1,...頂點n-1]n-1個起點的全排列
self.backtracking(1)
print(self.bestCost)
print(self.bestSolution)
限界分支法,優先順序佇列實現方式
約束條件:只能未走過的點
代價函式:走過的長度+後續未走過的長度
如下的優先順序佇列方式實現,當前最後解一直到底部才更新,沒有提前更新,所以首先有一次走到底部,而越靠近root的結點的界是越小的,因為大部分都是最小出邊和,那麼走到底之後,就會回到靠近靠近root的結點再次往下走,加入當前最優解較大的話,那他就一直處於優先順序佇列的尾部,無出頭之日,那優先順序佇列就相當於深度優先回溯了。
import numpy as np
import heapq
class Node:
def __init__(self,curCost=None,depth=None,rcost = None,path = None,lbound =None):
# 限界函式,我們定義限界函式為:當前走過的長度+未走過的結點最小出邊的長度和
# 這個用於優先順序佇列排序
self.lbound = lbound
# 當前走過的距離
self.curCost = curCost
# 當前的解的深度
self.depth = depth
# 路徑,從0到depth為已走過的,depth+1到n-1為未走過的結點
self.path = path
# depth到n-1為未走過的結點的最小出邊的和
self.rcost = rcost
# 這個用於結點比較大小,之前的(weights,node)方式有時會出問題,是有時候,
# 現在使用這種方式,lt means less than
def __lt__(self,other):
return int(self.lbound) <int(other.lbound)
class prune_Traveling_saleman:
def __init__(self,graph,start=0):
self.num = len(graph)
self.graph = graph
self.start = start
# 用於儲存最優的結果
self.bestNode = Node(np.inf,-1,-1,None,-1)
# 用於儲存每個頂點的最小出邊
self.minOut = [np.inf]*self.num
# 所有的最小出邊之和
self.minSum =0
for i in range(self.num):
for j in range(self.num):
if i !=j and graph[i][j] < self.minOut[i]:
self.minOut[i] = graph[i][j]
self.minSum += self.minOut[i]
def traveling_Saleman(self):
pqueue =[]
# 第0層,就是起點,也可以認為是第一層,這裡為了處理資料方便成為第0層
# [i for i in range(self.num)]為開始的路徑[0,1,2,3],path[0]是已經確定為0
# 最開始curCost =0,depth=0,rcost =self.minSum,lbound= self.minSum
curNode = Node(0,0,self.minSum,[i for i in range(self.num)],self.minSum)
# 把第一個結點放入
pqueue = [curNode,]
depth =0
while depth <= self.num-2:
# 彈出結點
curNode = heapq.heappop(pqueue)
curCost = curNode.curCost
depth = curNode.depth
rcost = curNode.rcost
path = curNode.path
# 當處於倒數第2層時,就可以處理最後結果了,可以考慮結束了
if depth == self.num-2:
# 處於當處於倒數第2層時,lbound就是當前走過的距離+當前結點到最後一個結點的距離
# + 最後一個結點到起點的距離;實際上也可以考慮self.num-1層,那就只需要加上
# 當前結點到起點的距離
lbound = curCost + self.graph[path[depth]][path[depth+1]] + self.graph[path[depth+1]][path[0]]
# 如果下界小於當前最優的結果,就可以考慮輸出結果了
if lbound < self.bestNode.curCost:
# 把當前值和當前路徑輸出
self.bestNode.curCost = lbound
self.bestNode.path = path
# 以下只是方便退出,當depth == self.num-1時退出
node = Node(lbound,depth+1,lbound,path,lbound)
heapq.heappush(pqueue,node)
else:
# 一般情況下首先更新下一層,也就是未走過結點最小出邊和,
# 需要減去當前結點的最小出邊,
temp_rcost = rcost - self.minOut[path[depth]]
for i in range(depth+1,self.num):
# 當前走過的距離,需要更新為加上當前結點到下一個結點的距離
temp_cur = curCost + self.graph[path[depth]][path[i]]
# 當前結點的下界為,當前走過的距離+未走過結點最小出邊和
lbound = temp_cur + temp_rcost
# 只有當下界小於self.bestNode.curCost才有放入優先順序佇列的必要
if lbound < self.bestNode.curCost:#
# 放入前需要更新path裡面這一層depth加入的結點號
# 只要把path[depth]記錄為當前選擇的結點就行了,同時path[depth]位置
# 之前放置的那個結點放到剛剛空的那個地方就行了
# 這一層還有其他的分支,所有不能直接在path上操作,因為這樣下一個分支也
# 用這個path就亂了。也可以可以先換了,壓入之後再換回來
temp_path = path[:]
temp_path[depth+1],temp_path[i] = temp_path[i],temp_path[depth+1]
# 把這個分支壓入優先順序佇列
node = Node(temp_cur,depth+1,temp_rcost,temp_path,lbound)
heapq.heappush(pqueue,node)
def print_Result(self):
self.traveling_Saleman()
print(self.bestNode.curCost)
print(self.bestNode.path)
測試結果
g =[[0,5,9,4],
[5,0,13,2],
[9,13,0,7],
[4,2,7,0]]
backtracking_Traveling_saleman(g,0).print_Result()
prune_Traveling_saleman(g,0).print_Result()
23
[0, 1, 3, 2]
23
[0, 1, 3, 2]
實際上也可以考慮self.num-1層,那就只需要加上當前結點到起點的距離
def traveling_Saleman(self):
pqueue =[]
# 第1層,就是起點,也可以認為是第一層,這裡為了處理資料方便成為第1層
curNode = Node(0,0,self.minSum,[i for i in range(self.num)],self.minSum)
pqueue = [curNode,]
curNode = heapq.heappop(pqueue)
curCost = curNode.curCost
depth = curNode.depth
rcost = curNode.rcost
path = curNode.path
while depth <= self.num-1:
# 處於解的最後一層,lbound就為當前的距離+當前點到起點的距離
# 在這種情況下需要注意,需要把結點彈出放在迴圈體的最後,到達self.num就
# 退出迴圈了,不會再進入判斷體,否者進入判斷,else就會報越界
if depth == self.num-1:
lbound = curCost + self.graph[path[depth]][path[0]]
if lbound < self.bestNode.curCost:
self.bestNode.curCost = lbound
self.bestNode.path = path
node = Node(lbound,depth+1,lbound,path,lbound)
heapq.heappush(pqueue,node)
else:
temp_rcost = rcost - self.minOut[path[depth]]
for i in range(depth+1,self.num):
temp_cur = curCost + self.graph[path[depth]][path[i]]
lbound = temp_cur + temp_rcost
if lbound < self.bestNode.curCost:# should fix
temp_path = path[:]
temp_path[depth+1],temp_path[i] = temp_path[i],temp_path[depth+1]
node = Node(temp_cur,depth+1,temp_rcost,temp_path,lbound)
heapq.heappush(pqueue,node)
curNode = heapq.heappop(pqueue)
curCost = curNode.curCost
depth = curNode.depth
rcost = curNode.rcost
path = curNode.path
def print_Result(self):
self.traveling_Saleman()
print(self.bestNode.curCost)
print(self.bestNode.path)