货郎问题:回溯法和限界分支法

这个问题可以堪称一个全排列,[起点,剩下的全排列]

回溯法

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)
    原文作者:分支限界法
    原文地址: https://blog.csdn.net/weixin_40759186/article/details/84837696
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞