问题描述一个销售员想去几个城市卖货,城市之间的路线(或路费)是已知。需要选择一条从车站出发,经过各个城市,最后回到车站的路线,使总距离(或总旅行费用)最小。本文只考虑4个城市的情况,下面的加权图是问题的转化。由于只有4个城市,如果规定业务员总是从1号城市出发,那么按照排列组合可以得到6种不同的旅行计划,如12341、13241等。在这些排列组合的基础上,很容易画出排列树,也就是问题的解空间树。置换树如下:根据解空间树,可以得到一些有用的信息:树的深度在两个节点之间为5路径上的标识数组表示返回城市1的路径没有体现在城市树,但其实这个距离应该在计算的时候考虑进去。下面分别使用回溯法和分支定界法求解问题,回溯法会给出相应的分支定界法的Python实现代码,将使用优先队列法介绍寻找最优的步骤问题的解决方案。回溯回溯法有点类似于暴力枚举的查找过程。回溯法的基本思想是按照深度优先搜索策略从根节点开始深入搜索解空间树。查找一个节点时,如果该节点可能包含问题,则继续向下查找;否则,返回其祖先节点并尝试其他路径搜索。如果问题只需要找到一个可行的解,那么对问题的解的搜索就可以结束;如果问题是最优解,则需要搜索整个解空间树,得到所有解后,选择最优解作为问题的解。或者在搜索叶节点之前确定该路径不是最优解时,可以对其进行剪枝以节省搜索时间,那么本文的旅行商问题就属于后者。解空间树中除叶节点外的每个节点都可能有多个子节点,而回溯法一次只能搜索一条路径,这也是与分支定界法的区别,这可以更好地帮助理解下面图片解释。下图中的F和L与原来的解空间树略有不同,但对步骤和结果没有影响。首先,已知销售人员从城市1开始。在第一步中,可以不受任何限制地得出任何可行的解决方案。上图路径为12341,F->A距离之和为59,也是当前最优解。由于L只有F的一个子节点,所以只能追溯到C节点,因为C还有另一条路径要查找。可以看出此时搜索到的路径为12431,M->A距离之和为66,此时距离之和大于最优解,所以最优解还是59,至此,节点C的所有子节点的查找结束。然后需要回溯到C的祖先节点B。此时的路径为13241,总距离为25,当前解远小于之前得到的最优解,所以需要更新最优解为25,然后再回到节点D。原理同上。可以看出这张图中的路径比前面几张图多了一个X(叉)。这一步也实现了对数剪枝。依据是什么?因为1号城市到3号城市再到4号城市的距离之和是26,无论怎么走,都不会比当前最优解好,所以Duck不需要继续往下找,直接回溯即可直接地。解空间树剩下的两条路径可以按照上面的方法继续搜索。遍历完所有可行路径后,最终的最优解best_length就是问题的全局最优解,对应的路径就是全局最优路径。下面介绍如何使用代码实现回溯法来搜索问题的最优解。这里省略了定义变量等一些简单的代码,只介绍关键部分。完整代码会在文末给出。首先要做的一定是改造地图。对于这张图,我们可以得到对应的邻接矩阵如下:$$\begin{pmatrix}-1&30&6&4\\30&-1&5&10\\6&5&-1&20\\4&10&20&-1\\\end{pmatrix}$$City_Graph=[[0,0,0,0,0],[0,-1,30,6,4],[0,30,-1,5,10],[0,6,5,-1,20],[0,4,10,20,-1]]path_num=len(City_Graph)isin=[0]*(path_num)#用于检测节点是否加入路径path=[0]*(path_num)#用于存储路径best_path=[0]*(path_num)#用于存储最优路径best_length=100000#初始化求和的最优路径的距离,那么就可以将地图存储在一个二维数组中,可以看到这和图对应的邻接矩阵有些不同,数组的第一行和第一列都是0,为什么要这样做?例如,城市1到城市2的距离为30,对应邻接矩阵中的(1,2)。此时二维数组对应的索引也是(1,2)。填0是为了统一索引。而城市本身的位置在这里填-1,表示没有路径。整个程序可以被一个条件分成两部分,是向下搜索还是向上回溯。如果不先计算剪枝操作,则搜索到的解空间树的叶子节点就是回溯的标志,中间节点通常用于向下搜索操作。.现在的问题是这个条件是什么?对于一个由1,2,3...n组成的解空间树,它是由[1:n]的所有排列组成的。如果我们暂且假设搜索树的深度为t的话,就会出现下面两种情况。当t<=n时,即当前扩展节点位于t-1层:若t-1层扩展节点与t层扩展节点之间存在路径,且[1:t]对应到路径的距离如果和小于最优解,继续向下搜索,否则进行剪枝。当t>n,即当前节点为叶子节点:整条路径只需要回到1号城市即可。此时需要判断是否存在这个环路。如果存在并且得到的距离之和优于当前最优解,则需要更新当前最优解。优秀的解决方案。这两种情况只是说可能有一些抽象,可以结合上面的解空间树来理解。可以看出,t>n是判断是否需要回溯的条件,即本文条件下的t>path_num-1。ift>path_num-1:#搜索到叶子节点foriinrange(1,path_num):#输出当前路径ThePath+=str(path[i])ThePath+='->'temp=int(ThePath.split('->')[3])ifCity_Graph[temp][1]>0:#判断是否存在循环ThePath+='1'#将循环添加到路径中,返回城市1print("Currentpath:%s"%ThePath)back_length=now_length+City_Graph[temp][1]#电路距离也需要加上print("Currentpathsum:%d"%back_length)ifback_length
