货郎问题:回溯法和限界分支法
生活随笔
收集整理的這篇文章主要介紹了
货郎问题:回溯法和限界分支法
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
這個(gè)問(wèn)題可以堪稱一個(gè)全排列,[起點(diǎn),剩下的全排列]
回溯法
import numpy as npclass backtracking_Traveling_saleman:# 初始化,明確起點(diǎn)def __init__(self,graph,start=0):# 頂點(diǎn)的個(gè)數(shù)self.vertex_num = len(graph)self.graph = graphself.start = start# 當(dāng)前解,當(dāng)前解為頂點(diǎn)的集合[起點(diǎn),頂點(diǎn)1,...頂點(diǎn)n-1],初始時(shí),第0個(gè)為起點(diǎn)# 從第一個(gè)到n-1個(gè)為排列樹(shù)# 解為[起點(diǎn),頂點(diǎn)1,...頂點(diǎn)n-1]# 解的目標(biāo)函數(shù)為:起點(diǎn)到頂點(diǎn)1距離(depth =1)+頂點(diǎn)1到頂點(diǎn)2的+...+頂點(diǎn)depth-1到depth的距離# 頂點(diǎn)n-2到頂點(diǎn)n-1(depth=n-1)距離,最后還要構(gòu)成環(huán)路+頂點(diǎn)n-1到起點(diǎn)的距離,求上述和的最小值。self.curSolution = [i for i in range(self.vertex_num)]# 用于存最好的解self.bestSolution = [-1] *self.vertex_num# 當(dāng)前花費(fèi)初始距離為0self.curCost = 0# 界初始為很大,得到一個(gè)解之后更新,也可以提前更新self.bestCost = np.infdef backtracking(self,depth):# 當(dāng)?shù)竭_(dá)最后一個(gè)頂點(diǎn),為遞歸出口if depth == self.vertex_num-1:# 最后一層時(shí),目前函數(shù)應(yīng)該為:當(dāng)前的距離和+前一個(gè)頂點(diǎn)到最后頂點(diǎn)距離+最后頂點(diǎn)到起點(diǎn)的距離temp_cost = self.curCost + self.graph[self.curSolution[depth-1]][self.curSolution[depth]] + self.graph[self.curSolution[depth]][self.curSolution[self.start]]# 當(dāng)前解優(yōu)于最優(yōu)解的話,更新最優(yōu)解,假如求可行解的話,就需要把可行解保存起來(lái)if temp_cost < self.bestCost:self.bestCost = temp_costself.bestSolution[:] = self.curSolution[:]else:# 下面就是排列樹(shù)的處理,我們需要求除了起點(diǎn)之外的[頂點(diǎn)1,...頂點(diǎn)n-1]n-1個(gè)起點(diǎn)的全排列# 我們處理的是標(biāo)號(hào),也就是self.curSolution = [i for i in range(self.vertex_num)]# 所以我們?nèi)帕卸际莝elf.curSolution里面的數(shù)for i in range(depth,self.vertex_num): # self.curSolution[depth],self.curSolution[i] = self.curSolution[i],self.curSolution[depth]# 當(dāng)滿足我們定義的界時(shí),才可以進(jìn)入下一層,也就是走過(guò)的長(zhǎng)度<最優(yōu)值的話(注意我們這兒要求的是最小值),我們才繼續(xù)搜索,否則剪掉# 有沒(méi)有人注意到這里是depth-1到i,而不是depth-1 到 depth?# 實(shí)際上我們學(xué)習(xí)到模板應(yīng)該是,先交換,然后滿足constraint() && bound() 回溯,最后再交換# 編碼時(shí)先交換的話,這個(gè)地方就應(yīng)該寫(xiě)成depth-1 到 depth,還沒(méi)交換的話就是depth-1到i# 這里是做了優(yōu)化處理,不滿足條件的話,交換都沒(méi)有必要執(zhí)行if self.curCost + self.graph[self.curSolution[depth-1]][self.curSolution[i]] < self.bestCost:# 全排列,把 當(dāng)前 和第一位交換,除第一位以外剩下的的進(jìn)行全排列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]]# 進(jìn)入下一層self.backtracking(depth+1)# 回溯處理,恢復(fù)現(xiàn)場(chǎng)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):# 我們需要求除了起點(diǎn)之外的[頂點(diǎn)1,...頂點(diǎn)n-1]n-1個(gè)起點(diǎn)的全排列self.backtracking(1)print(self.bestCost) print(self.bestSolution)限界分支法,優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)方式
約束條件:只能未走過(guò)的點(diǎn)
代價(jià)函數(shù):走過(guò)的長(zhǎng)度+后續(xù)未走過(guò)的長(zhǎng)度
如下的優(yōu)先級(jí)隊(duì)列方式實(shí)現(xiàn),當(dāng)前最后解一直到底部才更新,沒(méi)有提前更新,所以首先有一次走到底部,而越靠近root的結(jié)點(diǎn)的界是越小的,因?yàn)榇蟛糠侄际亲钚〕鲞吅?#xff0c;那么走到底之后,就會(huì)回到靠近靠近root的結(jié)點(diǎn)再次往下走,加入當(dāng)前最優(yōu)解較大的話,那他就一直處于優(yōu)先級(jí)隊(duì)列的尾部,無(wú)出頭之日,那優(yōu)先級(jí)隊(duì)列就相當(dāng)于深度優(yōu)先回溯了。
import numpy as np import heapq class Node:def __init__(self,curCost=None,depth=None,rcost = None,path = None,lbound =None):# 限界函數(shù),我們定義限界函數(shù)為:當(dāng)前走過(guò)的長(zhǎng)度+未走過(guò)的結(jié)點(diǎn)最小出邊的長(zhǎng)度和# 這個(gè)用于優(yōu)先級(jí)隊(duì)列排序self.lbound = lbound# 當(dāng)前走過(guò)的距離self.curCost = curCost# 當(dāng)前的解的深度self.depth = depth# 路徑,從0到depth為已走過(guò)的,depth+1到n-1為未走過(guò)的結(jié)點(diǎn)self.path = path# depth到n-1為未走過(guò)的結(jié)點(diǎn)的最小出邊的和self.rcost = rcost# 這個(gè)用于結(jié)點(diǎn)比較大小,之前的(weights,node)方式有時(shí)會(huì)出問(wèn)題,是有時(shí)候,# 現(xiàn)在使用這種方式,lt means less thandef __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 = graphself.start = start# 用于存儲(chǔ)最優(yōu)的結(jié)果self.bestNode = Node(np.inf,-1,-1,None,-1)# 用于存儲(chǔ)每個(gè)頂點(diǎn)的最小出邊self.minOut = [np.inf]*self.num# 所有的最小出邊之和self.minSum =0for 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層,就是起點(diǎn),也可以認(rèn)為是第一層,這里為了處理數(shù)據(jù)方便成為第0層# [i for i in range(self.num)]為開(kāi)始的路徑[0,1,2,3],path[0]是已經(jīng)確定為0# 最開(kāi)始curCost =0,depth=0,rcost =self.minSum,lbound= self.minSumcurNode = Node(0,0,self.minSum,[i for i in range(self.num)],self.minSum)# 把第一個(gè)結(jié)點(diǎn)放入pqueue = [curNode,]depth =0while depth <= self.num-2:# 彈出結(jié)點(diǎn)curNode = heapq.heappop(pqueue)curCost = curNode.curCostdepth = curNode.depthrcost = curNode.rcostpath = curNode.path# 當(dāng)處于倒數(shù)第2層時(shí),就可以處理最后結(jié)果了,可以考慮結(jié)束了if depth == self.num-2:# 處于當(dāng)處于倒數(shù)第2層時(shí),lbound就是當(dāng)前走過(guò)的距離+當(dāng)前結(jié)點(diǎn)到最后一個(gè)結(jié)點(diǎn)的距離# + 最后一個(gè)結(jié)點(diǎn)到起點(diǎn)的距離;實(shí)際上也可以考慮self.num-1層,那就只需要加上# 當(dāng)前結(jié)點(diǎn)到起點(diǎn)的距離lbound = curCost + self.graph[path[depth]][path[depth+1]] + self.graph[path[depth+1]][path[0]]# 如果下界小于當(dāng)前最優(yōu)的結(jié)果,就可以考慮輸出結(jié)果了if lbound < self.bestNode.curCost:# 把當(dāng)前值和當(dāng)前路徑輸出self.bestNode.curCost = lboundself.bestNode.path = path# 以下只是方便退出,當(dāng)depth == self.num-1時(shí)退出node = Node(lbound,depth+1,lbound,path,lbound)heapq.heappush(pqueue,node)else:# 一般情況下首先更新下一層,也就是未走過(guò)結(jié)點(diǎn)最小出邊和,# 需要減去當(dāng)前結(jié)點(diǎn)的最小出邊,temp_rcost = rcost - self.minOut[path[depth]]for i in range(depth+1,self.num):# 當(dāng)前走過(guò)的距離,需要更新為加上當(dāng)前結(jié)點(diǎn)到下一個(gè)結(jié)點(diǎn)的距離temp_cur = curCost + self.graph[path[depth]][path[i]]# 當(dāng)前結(jié)點(diǎn)的下界為,當(dāng)前走過(guò)的距離+未走過(guò)結(jié)點(diǎn)最小出邊和lbound = temp_cur + temp_rcost# 只有當(dāng)下界小于self.bestNode.curCost才有放入優(yōu)先級(jí)隊(duì)列的必要if lbound < self.bestNode.curCost:# # 放入前需要更新path里面這一層depth加入的結(jié)點(diǎn)號(hào)# 只要把path[depth]記錄為當(dāng)前選擇的結(jié)點(diǎn)就行了,同時(shí)path[depth]位置# 之前放置的那個(gè)結(jié)點(diǎn)放到剛剛空的那個(gè)地方就行了# 這一層還有其他的分支,所有不能直接在path上操作,因?yàn)檫@樣下一個(gè)分支也# 用這個(gè)path就亂了。也可以可以先換了,壓入之后再換回來(lái)temp_path = path[:]temp_path[depth+1],temp_path[i] = temp_path[i],temp_path[depth+1]# 把這個(gè)分支壓入優(yōu)先級(jí)隊(duì)列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)測(cè)試結(jié)果
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]實(shí)際上也可以考慮self.num-1層,那就只需要加上當(dāng)前結(jié)點(diǎn)到起點(diǎn)的距離
def traveling_Saleman(self):pqueue =[]# 第1層,就是起點(diǎn),也可以認(rèn)為是第一層,這里為了處理數(shù)據(jù)方便成為第1層curNode = Node(0,0,self.minSum,[i for i in range(self.num)],self.minSum)pqueue = [curNode,]curNode = heapq.heappop(pqueue)curCost = curNode.curCostdepth = curNode.depthrcost = curNode.rcostpath = curNode.pathwhile depth <= self.num-1:# 處于解的最后一層,lbound就為當(dāng)前的距離+當(dāng)前點(diǎn)到起點(diǎn)的距離# 在這種情況下需要注意,需要把結(jié)點(diǎn)彈出放在循環(huán)體的最后,到達(dá)self.num就# 退出循環(huán)了,不會(huì)再進(jìn)入判斷體,否者進(jìn)入判斷,else就會(huì)報(bào)越界if depth == self.num-1:lbound = curCost + self.graph[path[depth]][path[0]]if lbound < self.bestNode.curCost:self.bestNode.curCost = lboundself.bestNode.path = pathnode = 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_rcostif lbound < self.bestNode.curCost:# should fixtemp_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.curCostdepth = curNode.depthrcost = curNode.rcostpath = curNode.pathdef print_Result(self):self.traveling_Saleman()print(self.bestNode.curCost)print(self.bestNode.path)總結(jié)
以上是生活随笔為你收集整理的货郎问题:回溯法和限界分支法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 限界分支法:01背包问题,优先级队列(包
- 下一篇: 限界分支法优先级队列方式出口和追踪解的两