旅行商问题与其蚁群算法求解
目录
旅行商问题简介
蚁群算法简介
代码详解
完整代码
参数设定分析
参考文献
Ps: 你可以从各个小标题右边的按钮直接跳转到感兴趣的章节
旅行商问题简介
旅行商问题(Travelling salesman problem, TSP),指在一系列城市之中,一个旅行商想要知道走哪条路线能经过所有城市并回到起点且路径最短。旅行商问题是组合优化问题中一个典型的NP问题,也就是说随着数据规模(在TSP问题中是城市数量)的增长,计算量的增长会非常恐怖。
目前解决TSP问题的算法有两类:最优化算法和启发式算法。
最优化算法
最优化算法确保能求出每个TSP问题实例的最优解,但是时间复杂度较高。典型的有穷举、DFS、动态规划等。下面贴上他人用这几个方法解决TSP问题的链接。
穷举: https://blog.csdn.net/qq_43697892/article/details/104737764 时间复杂度O(n!)
DFS: https://www.cnblogs.com/mld-code-life/p/12398988.html 剪枝了不好算,开摆
DP: https://blog.csdn.net/qq_39559641/article/details/101209534 时间复杂度O(n2·2n)
启发式算法
启发式算法能算出一个TSP问题实例的一个可行解,至于这个可行解和最优解是否相同、不同时相差多少则难以统计。启发式算法最大的优点就是在参数设定合理的时候花费时间较小,在数据量较大最优化算法无法完成计算时启发式算法依然可以完成工作。解决TSP问题的典型启发式算法有蚁群算法、遗传算法等。
遗传算法:https://blog.csdn.net/qq_40834030/article/details/81257099
蚁群算法:本文是干啥的啊喂
延伸阅读
最后在这里安利两个关于TSP问题的延伸阅读
第一是一个关于TSP问题的网站,收录了几个TSP问题实例的世界纪录
http://www.math.uwaterloo.ca/tsp/index.html
第二是一个动画,在这个动画的第十集中有许多与图论和最优化相关的话题,并介绍了一个改进过的TSP问题——有可变等待时间的TSP问题。在这个改进问题中每个城市节点都多了一个等待时间属性,我个人想到的方法是将图的边权由距离改为路线花费时间+等待时间来解决,各位有兴趣的可以试着通过代码解决一下。当然动画本身也很有意思,推荐观看(逃
http://www.bilibili.com/bangumi/play/ep307485
蚁群算法简介
蚁群算法是群智能算法的一种,受自然界中蚂蚁行动启发。自然界中的蚂蚁在觅食中总是能找到从巢穴到食物所在地的最优或者近似最优路线。因为每只蚂蚁在前进的过程中都会留下一定浓度信息素,且每只蚂蚁都倾向于选择信息素浓度较大的路线。蚁群算法将这种正反馈过程用计算机模拟了出来,以解决一些复杂的组合优化问题。
蚁群算法原理简述
在使用蚁群算法求解TSP问题时,首先将一定数量蚂蚁随机放置在城市节点上。接下来进行若干次迭代,每次迭代中每只蚂蚁都会以一定方法选择一条路径访问所有的城市,然后返回出发点,并同时在路径上留下信息素。每次迭代后,将当前路径最短的蚂蚁定为当前解。在选择路径方法选择合适的情况下,迭代次数越多,当前解越接近最优解。
不难看出蚁群算法求解TSP问题中有两个难点。一个是选择去下一个城市的方法,一个是信息素更新的机制,这两个关键问题的解决方法决定了代码的效率与准确度。
选择去下一个城市的方法
选择去下一个城市的方法有许多种,比如完全随机、直接选择当前还未访问的最近城市、根据信息素浓度和启发函数确定一个概率再加权随机等。完全随机时程序需要花费大量时间收敛,选择当前还未访问的最近城市则类似于贪心算法,容易陷入局部最优解。于是本着跳出局部最优解同时减少时间花费的原则,本文选择了第三种方法,其中一只蚂蚁从城市i到城市j的概率计算公式如下。
城市i和j之间的信息素浓度 | |
待访问的城市集合 | |
信息素权重常数 | |
启发函数权重常数 | |
启发函数 |
信息素权重常数与启发函数权重常数分别决定了信息素和启发函数两个因素在选择城市过程中的影响力,这两个参数的设置合理与否直接决定了算法能否快速准确的找出最优解,我会在之后的章节提到参数的设定。启发函数表示蚂蚁从城市i到j的期望程度,通常和城市之间的距离有关。在本文中启发函数为为城市i和j之间的距离。在一次移动中,计算出从城市i到list中所有城市的概率之后,运用轮次相减法实现加权随机,最终确定蚂蚁下一个访问的城市。
信息素更新机制
信息素更新的机制有三种,蚁密模型、蚁量模型和蚁周模型。蚁密模型实时更新信息素,每条路径获得的信息素增量为常量。蚁量模型实时更新信息素,每条路径获得的信息素增量为变量。蚁周模型每次迭代后更新信息素,每条路径获得的信息素增量为变量。一般认为蚁周模型更适合解决TSP问题,其一条路径上信息素浓度变化量公式为
(如果蚂蚁k从城市i到了城市j,否则为0)
所有蚂蚁对路径ij贡献的信息素增量和 | |
第k只蚂蚁对路径ij贡献的信息素增量 | |
信息素常数,一只蚂蚁在一次迭代中释放的信息素总量 | |
第k只蚂蚁在这次迭代中的总路径长度 |
同时为了防止各条路径上信息素浓度过高,每次迭代时都会让信息素进行一定程度的挥发再加上新的信息素,信息素挥发公式为
迭代次数 | |
信息素挥发常数,决定信息素挥发强度 |
结合两个公式,即可算出每次迭代后每条路径上的信息素浓度。到这里蚁群算法的基本原理已经介绍完毕,接下来是代码和参数数值选择的技巧。
代码详解
代码概述
接下来是对蚁群算法求解TSP问题的代码的讲解,对于这种巨麻烦无比的算法,不用Python简直就是折磨自己。
个人认为要想读懂并掌握一篇有一定长度的代码,应当先对其程序流程和代码结构做一定了解,本程序流程图和结构图如下。本程序遵循面向对象设计思想(除了有些地方夹杂了点个人审美和省事因素),将关于所有求解TSP问题的相关函数和数据封装进了一个类,并且将所有蚂蚁的属性和行为封装进了另一个类作为内部类。
核心部分详解
选择下一个城市用的函数
def __choose_next(self): nextCity = -1 selectProb = [0.0 for i in range(TSP.cityNum)] # 定义一个存储去下个城市的概率的表 totalProb = 0.0 for i in range(TSP.cityNum): # 开始计算去其他城市的概率 if self.canVis[i]: # 如果这个城市不可以访问那么直接不用算了 # 通过公式计算概率 selectProb[i] = pow(TSP.pheromone[self.currentCity][i], TSP.alpha) * pow((1.0 / TSP.dis[self.currentCity][i]), TSP.beta) # 概率累加,轮次相减法用 totalProb += selectProb[i] # 开始通过伦次相减法实现加权随机,选择去哪个城市 if totalProb > 0.0: tempProb = random.uniform(0.0, totalProb) # 产生一个0~totalProb之间的随机概率 for i in range(TSP.cityNum): if self.canVis[i]: tempProb -= selectProb[i] # 轮次相减 if tempProb < 0.0: nextCity = i break if (nextCity == -1): # 保险起见,如果没有产生,则顺序选择一个城市 nextCity = random.randint(0, TSP.cityNum - 1) while self.canVis[nextCity] == False: nextCity = random.randint(0, TSP.cityNum - 1) return nextCity
信息素更新函数
def __update_pheromone(self): # 创建一个临时二维数组存储信息素 tempPheromone = [[0.0 for col in range(self.cityNum)] for row in range(self.cityNum)] # 遍历当前所有蚂蚁 for ant in self.ants: for i in range(1, self.cityNum): # 读取路径上相邻城市 start, end = ant.path[i - 1], ant.path[i] # 根据公式在路径上的每两个相邻城市间留下信息素 tempPheromone[start][end] += self.q / ant.totalDis tempPheromone[end][start] = tempPheromone[start][end] # 更新所有城市之间的信息素,旧信息素衰减加上新信息素,然后存入信息素矩阵 for i in range(self.cityNum): for j in range(self.cityNum): self.pheromone[i][j] = self.pheromone[i][j] * TSP.rho + tempPheromone[i][j]
完整代码
import random import tkinter import copy import sys class TSP: # 懒得给内部类传参了,所以开成了静态变量,反正大概率没人同时给这个类创建多个实例 alpha, beta, rho, q = 0, 0, 0, 0 #信息素权重常数、启发函数权重常数、信息素挥发常数、信息素常数 cityNum, antNum, maxIter = 0, 100, 100 # 城市数量 蚂蚁数量 最大迭代次数 distanceX, distanceY, dis, pheromone = [], [], [], [] #城市横坐标 城市纵坐标 城市间距离 信息素矩阵 def __init__(self, inputX, inputY, inputAlpha=4, inputBeta=4, inputRho=0.3, inputQ=100, inputAnts=50, inputIter=100): self.__init_data(inputX, inputY, inputAlpha, inputBeta, inputRho, inputQ, inputAnts, inputIter)# 初始化数据 self.__init_ants()#初始化蚁群 def runTSP(self): # 初始化画布 self.__init_canvas() # 进入迭代循环 for lp in range(TSP.maxIter): for ant in self.ants: ant.search_path()# 搜索一条路径并与当前最优蚂蚁比较 if ant.totalDis < self.bestAnt.totalDis: self.bestAnt = copy.deepcopy(ant)# 更新最优解 self.__update_pheromone()# 蚁周算法更新信息素 print(u"迭代次数:", self.iterTimes, u"最佳路径总距离:", int(self.bestAnt.totalDis)) self.__line(self.bestAnt.path)# 连线 self.canvas.update()# 更新画布 self.iterTimes += 1 self.__draw_text()# 防止文字被覆盖 self.TSPGUI.mainloop() def __init_data(self, inputX, inputY, inputAlpha, inputBeta, inputRho, inputQ, inputAnts, inputIter): #参数赋值 TSP.alpha, TSP.beta, TSP.rho, TSP.q = inputAlpha, inputBeta, inputRho, inputQ TSP.distanceX, TSP.distanceY = copy.deepcopy(inputX), copy.deepcopy(inputY) #判断横纵坐标是否数量相同 if len(TSP.distanceX) != len(TSP.distanceY): print(u"坐标输入错误!") sys.exit() TSP.cityNum,TSP.antNum ,TSP.maxIter= len(TSP.distanceX), inputAnts,inputIter TSP.dis = copy.deepcopy([[0.0 for col in range(TSP.cityNum)] for row in range(TSP.cityNum)]) TSP.pheromone = copy.deepcopy([[1.0 for col in range(TSP.cityNum)] for row in range(TSP.cityNum)]) self.nodes = [] # 节点画布上坐标 # 初始化城市间距离 for i in range(self.cityNum): for j in range(self.cityNum): tempDistance = pow((self.distanceX[i] - self.distanceX[j]), 2) + pow( (self.distanceY[i] - self.distanceY[j]), 2) tempDistance = pow(tempDistance, 0.5) self.dis[i][j] = int(tempDistance) # 初始城市之间信息素 for i in range(self.cityNum): for j in range(self.cityNum): self.pheromone[i][j] = 1.0 # 初始化显示相关参数 self.__circleRadius, self.__lineWidth, self.__circleWidth = 5, 2, 1 self.__displayWidth, self.__displayHeight = 800, 600 self.__outlineColor, self.__fillColor, self.__textColor, self.__bgColor, self.__lineColor = "#000000", "#ff0000", "#000000", "#ffffff", "#000000" def __init_canvas(self): # 创建窗口 self.TSPGUI = tkinter.Tk() self.TSPGUI.title("蚁群算法解TSP问题") self.canvas = tkinter.Canvas( self.TSPGUI, width=self.__displayWidth, height=self.__displayHeight, bg=self.__bgColor) self.canvas.pack(expand=tkinter.YES, fill=tkinter.BOTH) # 清空画布 for item in self.canvas.find_all(): self.canvas.delete(item) # 初始化城市节点 maxX, maxY = max(TSP.distanceX), max(TSP.distanceY) for i in range(len(self.distanceX)): # 计算画布上坐标 x, y = int(float(self.__displayWidth) / maxX * self.distanceX[i]), int( float(self.__displayHeight) / maxY * self.distanceY[i]) self.nodes.append((x, y)) # 生成节点圆 node = self.canvas.create_oval(x - self.__circleRadius, y - self.__circleRadius, x + self.__circleRadius, y + self.__circleRadius, fill=self.__fillColor, # 填充颜色 outline=self.__outlineColor, # 轮廓颜色 width=self.__circleWidth,) # 显示坐标文字 self.__draw_text() def __draw_text(self): for i in range(len(self.nodes)): self.canvas.create_text(self.nodes[i][0], self.nodes[i][1] - 2 * self.__circleRadius, # 使用create_text方法在坐标处绘制文字 text='(' + str(self.distanceX[i]) + ',' + str(self.distanceY[i]) + ')', # 所绘制文字的内容 fill=self.__textColor # 文字颜色 ) def __init_ants(self): self.ants = [self.Ant(i1) for i1 in range(self.antNum)] # 初始化蚁群 self.bestAnt = self.Ant(-1) # 初始化最优解 self.bestAnt.totalDis = 2147483647 # 初始最大距离,直接访问数据成员不是好习惯,小孩子不要学坏 self.iterTimes = 1 # 初始化迭代次数 def __update_pheromone(self): tempPheromone = [[0.0 for col in range(self.cityNum)] for row in range(self.cityNum)] for ant in self.ants: for i in range(1, self.cityNum): start, end = ant.path[i - 1], ant.path[i] # 根据公式在路径上的每两个相邻城市间留下信息素 tempPheromone[start][end] += self.q / ant.totalDis tempPheromone[end][start] = tempPheromone[start][end] # 更新所有城市之间的信息素,旧信息素衰减加上新信息素 for i in range(self.cityNum): for j in range(self.cityNum): self.pheromone[i][j] = self.pheromone[i][j] * TSP.rho + tempPheromone[i][j] def __line(self, path): self.canvas.delete("lines") # 删除原线 for i in path: p1, p2 = self.nodes[path[i]], self.nodes[path[i - 1]] self.canvas.create_line(p1, p2, fill=self.__lineColor, tags="lines", width=self.__lineWidth) def setColor(self, **inputColor): for i in inputColor.keys(): if i == 'fillColor': self.__fillColor = inputColor[i] if i == 'bgColor': self.__bgColor = inputColor[i] if i == 'textColor': self.__textColor = inputColor[i] if i == 'outlineColor': self.__outlineColor = inputColor[i] if i == 'lineColor': self.__lineColor = inputColor[i] def setLine(self, **inputLine): for i in inputLine.keys(): if i == 'circleRadius': self.__circleRadius = inputLine[i] if i == 'lineWidth': self.__lineWidth = inputLine[i] if i == 'circleWidth': self.__circleWidth = inputLine[i] def setScreen(self, inX, inY): self.__displayWidth, self.__displayHeight = inX, inY class Ant: def __init__(self, ID): # 初始化变量 self.ID = ID # 蚂蚁的id self.__init_data() def search_path(self): # 外部调用需要的函数 # 初始化数据 self.__init_data() # 搜素路径,遍历完所有城市为止 while self.cnt < TSP.cityNum: # 移动到下一个城市 nextCity = self.__choose_next() self.__move(nextCity) # 计算路径总长度 self.__cal_total_distance() def __init_data(self): self.path = [] # 已经走过的路径 self.totalDis = 0.0 # 已经走过的距离 self.currentCity = -1 # 初始化当前的城市 self.canVis = [True for i in range(TSP.cityNum)] # 可以访问的城市列表 self.cnt = 1 # 已经访问过的城市数量 tempCity = random.randint(0, TSP.cityNum - 1) # 随机初始点 self.currentCity = tempCity # 更新当前蚂蚁所在城市 self.path.append(tempCity) # 将初始点加入路径 self.canVis[tempCity] = False # 给初始点打标记 def __choose_next(self): nextCity = -1 selectProb = [0.0 for i in range(TSP.cityNum)] # 定义一个存储去下个城市的概率的表 totalProb = 0.0 for i in range(TSP.cityNum): # 获取去下一个城市的概率 if self.canVis[i]: # 如果这个城市不可以访问那么直接不用算了,好耶 # 通过公式计算概率 selectProb[i] = pow(TSP.pheromone[self.currentCity][i], TSP.alpha) * pow((1 / TSP.dis[self.currentCity][i]),TSP.beta) totalProb += selectProb[i] if totalProb > 0.0: # 选择城市代码 tempProb = random.uniform(0.0, totalProb) # 产生一个0~totalProb之间的随机概率 for i in range(TSP.cityNum): if self.canVis[i]: tempProb -= selectProb[i] # 轮次相减 if tempProb < 0.0: nextCity = i break if (nextCity == -1): # 保险起见,如果没有产生,则顺序选择一个城市 nextCity = random.randint(0, TSP.cityNum - 1) while self.canVis[nextCity] == False: # if==False,说明已经遍历过了 nextCity = random.randint(0, TSP.cityNum - 1) return nextCity def __cal_total_distance(self): # 计算路径的总距离 tempDistance = 0.0 start, end = 0, 0 for i in range(1, TSP.cityNum): start, end = self.path[i], self.path[i - 1] tempDistance += TSP.dis[start][end] # 最后加上末尾城市回到初始城市的距离,构成回路 end = self.path[0] tempDistance += TSP.dis[start][end] self.totalDis = tempDistance def __move(self, nextCity): # 移动函数 self.path.append(nextCity) self.canVis[nextCity] = False self.totalDis += TSP.dis[self.currentCity][nextCity] self.currentCity = nextCity self.cnt += 1 if __name__ == '__main__': X = [267, 562, 464, 770, 309, 536, 175, 419, 434, 614, 691, 111, 577, 363, 377, 548, 519, 2, 510, 582, 727, 625, 306, 666, 646, 347, 492, 306, 224, 489, 253, 114, 424, 762, 518, 385, 629, 518, 701, 452, 671, 490, 342, 775, 180, 561, 55, 421, 627, 106] Y = [422, 479, 111, 387, 300, 336, 458, 476, 531, 136, 331, 88, 189, 184, 400, 128, 75, 511, 520, 397, 184, 253, 449, 280, 64, 500, 32, 591, 561, 418, 224, 259, 377, 458, 401, 417, 589, 438, 76, 262, 145, 258, 362, 134, 374, 458, 312, 318, 285, 503] # 城市横坐标, 城市纵坐标, alpha, beta, rho, q, 蚂蚁数量, 最大迭代次数 A = TSP(X, Y, 1, 3, 0.5, 100, 75, 100) # 设置背景颜色, 节点填充颜色, 线颜色, 文字颜色, 节点边框颜色 A.setColor(bgColor='#ffdcf0', fillColor='#ff6699', lineColor='#f78eb9', textColor='#701d23', outlineColor='#dc3b40') # 设置节点半径, 设置线宽, 设置节点线宽 A.setLine(circleRadius=10, lineWidth=3, circleWidth=2) # . 设置屏幕大小 A.setScreen(800, 600) # . 开始迭代 A.runTSP()
参数设定分析
在《MATLAB数学建模方法与实践》一书中,通过计算和实验研究,分别给出了几个参数的合适范围。
蚂蚁数量 antNum | 城市数量的1.5倍左右 |
信息素权重常数 alpha | [1, 4] |
启发函数权重常数 beta | [3, 4.5] |
信息素挥发常数 rho | [0.2, 0.5] |
信息素常数 Q | [10, 1000] |
最大迭代次数 maxIter | [100, 500] |
在参数调整的过程中,可以线确定antNum,之后通过大范围调整比较确定alpha、beta和Q,最后再精细调整rho。对于maxIter则可以线给一个较大的数值,看一下收敛速度再决定(当然你也可以直接给个2147483647,之后看差不多收敛了后直接关闭程序)。
参考文献
https://zhuanlan.zhihu.com/p/137408401
https://blog.csdn.net/m0_37570854/article/details/83715944
《MTALAB数学建模方法与实践(第3版)》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端