遗传算法解决TSP问题
一、求解问题概述
1.1 TSP问题
TSP问题是指旅行商问题(Traveling Salesman Problem)。在TSP问题中,假设有一名旅行商要在给定的一组城市之间进行旅行,每个城市只能被访问一次,并且旅行商必须最终返回出发城市。问题的目标是找到一条路径,使得旅行商的总旅行距离最短。
TSP问题是一个经典的组合优化问题,在计算复杂性理论中被证明是NP-难问题,意味着在一般情况下,找到最优解需要耗费大量的计算时间。TSP问题在实际应用中具有广泛的应用,如物流规划、电路板设计、基因组测序等领域。为了解决TSP问题,许多算法和启发式方法被提出,包括穷举搜索、动态规划、近似算法(如最近邻算法和模拟退火算法)、遗传算法等。这些方法旨在找到一个近似最优解或者在可接受的时间内找到较好的解决方案。
1.2 目标函数
在该问题中,我们需要定义一个目标函数,它是根据决策变量的值来计算问题的目标。目标函数可以是线性的、非线性的、凸的或非凸的,具体取决于问题的性质。例如,在一个生产调度问题中,目标函数可以是最小化总生产时间或最大化利润。
arg min x ∑ i = 1 n ∑ j = 1 n c i j x i j \arg\min_{x}\sum_{i=1}^n\sum_{j=1}^n c_{ij}x_{ij} argxmini=1∑nj=1∑ncijxij
其中:
n n n 是城市的数量。
c i j c_{ij} cij 是城市i到城市j之间的距离(或时间)。
x i j x_{ij} xij 是决策变量,表示是否从城市i移动到城市j。当路径经过城市i到城市j时, x i j x_{ij} xij 取值为1,否则为0。
TSP
的目标函数即为所有路径距离或路径时间的总和,通过最小化这个目标函数,可以找到一条最优路径,使得旅行商经过所有城市后的总距离或总时间最小。
二、优化方法概述
TSP问题属于组合优化问题,往往这种问题如果用枚举法来求解的话,都会遇到 n ! n! n! 或者 m n m^n mn 等复杂度爆炸的情况,都属于 NP
难问题。
解决这种问题的方法一般采用近似法求解,即:在损失少量求解精度的前提下,节约大量的时间3开销。
下面采用一种改进的遗传算法对 TSP 问题进行求解。
三、程序代码
该算法参考自计算机学报论文《一种改进的求解 TSP 问题的演化算法》1
// 解决TSP问题的重构算法(主要是tsp0的代码风格太丑陋了,修改成C++风格)
#include<iostream>
#include<fstream>
#include<vector>
#include<time.h>
#include<string>
using namespace std;class TSP_M {
private:int numCity; // 城市数量vector<vector<double>> cityXY; // 城市坐标vector<vector<double>> cityDis; // 城市间距离的邻接矩阵int numColony = 100; // 种群数量vector<vector<int>> colony; // 种群vector<double> individualAdaptability; // 个体适应度int maxGen = 200000; // 最大演化代数double probabilityMutation = 0.02; // 变异概率vector<int> bestIndividual; // 当前最优个体double bestAdaptability; // 当前最优个体的适应度public:// 计算种群中每个个体的适应度void calculateAdaptability() {// 计算每个个体的适应度for (int i = 0; i < numColony; i++) {double sum = 0;for (int j = 0; j < numCity; j++) {sum += cityDis[colony[i][j]][colony[i][(j + 1) % numCity]];}individualAdaptability[i] = sum;}}// 初始化void init(string filePath) {readTspFile(filePath);// 根据tsp数据,初始化相关数据calculateData();}// 进行迭代计算void evolution() {for (int curGen = 0; curGen < maxGen; curGen++) { // 迭代maxGen次for (int i = 0; i < numColony; i++) { // 遍历种群中所有个体vector<int> path = colony[i]; // 用于存放变异后的路径int posC1 = rand() % numCity; // 随机生成变异点1(在path中的位置)int posC2 = rand() % numCity; // 随机生成变异点2(在path中的位置)int C1, C2; // 变异点1和变异点2对应的城市编号C1 = path[posC1]; // 获取变异点1对应的城市int j = rand() % numColony; // 用于外变异的另一个 与 i个体 不同的个体int pos_flag = 0; // 用于标记变异过的点的数量double distanceChange = 0; // 用于记录距离变化while (true){// 以 probabilityMutation (default = 0.02)的概率进行内变异if (rand() / 32768.0 < probabilityMutation) {posC2 = rand() % numCity;while (posC1 == posC2) { // 如果两个变异点相同,则重新生成posC2 = rand() % numCity;}C2 = colony[i][posC2]; // 获取变异点1对应的城市}else { // 进行外变异(交叉)j = rand() % numColony;while (i == j) { // 如果两个个体相同,则重新生成j = rand() % numColony;}// 获取个体 j 中 变异点1 对应城市的位置int pos = position(colony[j], path[posC1]);C2 = colony[j][(pos + 1) % numCity]; // 获取变异点2对应的城市posC2 = position(path, C2); // 获取变异点2在个体 i 中的位置(即变异点2对应的城市在个体 i 中的位置}// 如果两个变异点相邻,continueif ((posC1 + 1) % numCity == posC2 || (posC1 - 1 + numCity) % numCity == posC2)break;//if (abs(posC1 - posC2) == 1 || abs(posC1 - posC2) == numCity - 1) {// continue;//}// 否则进行倒位操作int C1_left = path[posC1]; // 变异点1左边的城市int C1_right = path[(posC1 + 1) % numCity]; // 变异点1右边的城市int C2_left = path[posC2]; // 变异点2左边的城市int C2_right = path[(posC2 + 1) % numCity]; // 变异点2右边的城市// 计算倒位后的路径长度distanceChange += cityDis[C1_left][C2_left] + cityDis[C1_right][C2_right]- cityDis[C1_left][C1_right] - cityDis[C2_left][C2_right];invert(path, posC1, posC2); // 倒位操作pos_flag++; // 变异点数量加一if (pos_flag >= numCity)break;posC1++; // 变异点1的位置加一if (posC1 >= numCity) posC1 = 0; // 如果变异点1的位置超过了numCity,则变异点1的位置为0}// 更新子个体的适应度individualAdaptability[numColony + i] = individualAdaptability[i] + distanceChange;distanceChange = 0;// 记录 产生的 子个体for (int j = 0; j < numCity; j++) {colony[numColony + i][j] = path[j];}}// 一轮迭代之后进行选择selection();bestIndividual = colony[0]; // 更新最优个体bestAdaptability = individualAdaptability[0]; // 更新最优个体的适应度for (int i = 1; i < numColony; i++) {if (individualAdaptability[i] < bestAdaptability) {bestIndividual = colony[i];bestAdaptability = individualAdaptability[i];}}// cout << "第" << curGen << "代的最优个体适应度为:" << bestAdaptability << endl;cout << curGen << ":" << bestAdaptability << endl;// 创建 outfile.txt 文件ofstream outfile("outfile.txt", ios::app);// 每 2000 代将最优个体的适应度写入文件if ((curGen + 1) % 2000 == 0) {outfile << curGen << ":" << bestAdaptability << endl;}// 关闭文件outfile.close();}}// 获取城市在路径中的位置int position(vector<int>& path, int city) {for (int i = 0; i < numCity; i++) {if (path[i] == city) {return i;}}return -1;}void invert(vector<int>& path, int pos1, int pos2) {// 如果pos1在pos2的左边,为一段if (pos1 < pos2) {for (int i = pos1 + 1, j = pos2; i < j; i++, j--) {swap(path[i], path[j]);}}// 如果pos1在pos2的右边,为两段else {// 右边的段 <= 左边的段if (numCity - 1 - pos1 <= pos2 + 1) {int i, j;for (i = pos2 + 1, j = pos1; i <= numCity - 1; i++, j--) {swap(path[i], path[j]);}for (i = 0; i < j; i++, j--) {swap(path[i], path[j]);}}// 右边的段 > 左边的段else {int i, j;for (i = pos2 + 1, j = pos1; j >= 0; i++, j--) {swap(path[i], path[j]);}for (j = numCity - 1; i < j; i++, j--) {swap(path[i], path[j]);}}}}// 在父代和子代中进行一个锦标赛选择void selection() {for (int i = 0; i < numColony; i++) {if (individualAdaptability[i] > individualAdaptability[numColony + i]) {individualAdaptability[i] = individualAdaptability[numColony + i];for (int j = 0; j < numCity; j++) {colony[i][j] = colony[numColony + i][j];}}}}// 读取tsp文件bool readTspFile(string filePath) {fstream input(filePath, ios::in);if (!input) {cout << "文件打开失败" << endl;return false;}input >> numCity; // 城市数量cout << numCity << endl;// 初始化cityXYcityXY = vector<vector<double>>(numCity, vector<double>(2));// 读取城市坐标double x, y;for (int i = 0; i < numCity; i++) {int tmp;input >> tmp >> x >> y;cout << tmp << " " << x << " " << y << endl;cityXY[i][0] = x;cityXY[i][1] = y;}// 关闭文件input.close();return true;}// 根据tsp数据计算城市之间的距离、并随机初始化种群、同时计算适应度void calculateData() {// 初始化cityDiscityDis = vector<vector<double>>(numCity, vector<double>(numCity));// 计算城市间距离for (int i = 0; i < numCity; i++) {for (int j = 0; j < numCity; j++) {cityDis[i][j] = sqrt(pow(cityXY[i][0] - cityXY[j][0], 2) + pow(cityXY[i][1] - cityXY[j][1], 2));}}// 初始化colony (包括父代和子代)colony = vector<vector<int>>(2 * numColony, vector<int>(numCity));// 以时间为种子,随机生成种群srand((unsigned)time(NULL));// 建立一个用于随机生成种群的数组vector<int> tmp(numCity);for (int i = 0; i < numCity; i++) {tmp[i] = i;}// 随机初始化种群for (int i = 0; i < numColony; i++) {int numNeedToRand = numCity; // 当前需要随机的次数for (int j = 0; j < numCity; j++) {int randIndex = rand() % numNeedToRand; // 随机生成下标colony[i][j] = tmp[randIndex]; // 将随机生成的下标对应的值赋给种群swap(tmp[randIndex], tmp[numNeedToRand - 1]); // 将已经随机过的下标与最后一个下标交换numNeedToRand--; // 需要随机的次数减一}}// 初始化individualAdaptabilityindividualAdaptability = vector<double>(2 * numColony); // 后面的numColony个是用于存放子个体的适应度的// 计算种群中每个个体的适应度calculateAdaptability();}// 获取最优个体vector<int> getBestIndividual() {int bestIndex = 0;for (int i = 1; i < numColony; i++) {if (individualAdaptability[i] < individualAdaptability[bestIndex]) {bestIndex = i;}}return colony[bestIndex];}
};int main() {TSP_M tsp;// tsp.readTspFile("./pcb442.tsp");tsp.init("./pcb442.tsp");tsp.evolution();vector<int> bestIndividual = tsp.getBestIndividual();// 输出最优个体到文件fstream output("./bestIndividual_Serial.txt", ios::out);for (int i = 0; i < bestIndividual.size(); i++) {output << bestIndividual[i] << " ";}return 0;
}
四、运行结果
从图中可以看出算法的所有的路线基本都没有交叉,性能较为鲁棒。
[1]蔡之华,彭锦国,高伟,魏巍,康立山.一种改进的求解TSP问题的演化算法[J].计算机学报,2005(05):823-828. ↩︎
相关文章:
遗传算法解决TSP问题
一、求解问题概述 1.1 TSP问题 TSP问题是指旅行商问题(Traveling Salesman Problem)。在TSP问题中,假设有一名旅行商要在给定的一组城市之间进行旅行,每个城市只能被访问一次,并且旅行商必须最终返回出发城市。问题的…...
设计模式-工厂设计模式
核心思想 在简单工厂模式的基础上进一步的抽象化具备更多的可扩展和复用性,增强代码的可读性使添加产品不需要修改原来的代码,满足开闭原则 优缺点 优点 符合单一职责,每个工厂只负责生产对应的产品符合开闭原则,添加产品只需添…...
TM4C123库函数学习(3)---串口中断
前言 (1)学习本文之前,需要先学习前两篇文章。 (2)学习本文需要准备好TTL转USB模块。 函数介绍 ROM_GPIOPinConfigure() 配置GPIO引脚的复用功能。因为引脚不可能只有一个输出输入作用…...
opencv 进阶13-Fisherfaces 人脸识别-函数cv2.face.FisherFaceRecognizer_create()
Fisherfaces 人脸识别 PCA 方法是 EigenFaces 方法的核心,它找到了最大化数据总方差特征的线性组合。不可否认,EigenFaces 是一种非常有效的方法,但是它的缺点在于在操作过程中会损失许多特征信息。 因此,在一些情况下,…...
基于mysql5.7制作自定义的docker镜像,适用于xxl-job依赖的数据库,自动执行初始化脚本(ddl语句和dml语句)
一、背景 xxl-job-admin依赖mysql数据库,且需执行初始化脚本,包括ddl和dml语句。 具体的步骤总结如下: 1、新建数据库xxl_job2、创建mysql表table3、执行dml语句,包括新建admin用户及密码,创建执行器和任务。 毫无疑…...
LeetCodeHot100python版本:单调栈,栈,队列,堆
单调栈 739. 每日温度 42. 接雨水 双指针 单调栈(横向求解) 84. 柱状图中最大的矩形 栈和队列 队列:先入先出 栈:先入后出 两个栈 模拟 队列 一个队列 可以模拟 栈 20. 有效的括号 155. 最小栈 394. 字符串解码 堆 215. 数组中的第K个最大元素 3…...
JUC初识
JUC 是什么 java.util.concurrent 在并发编程中使用的工具包 从线程start 开始 package com.jhj.Thread;public class ThreadDemo {public static void main(String[] args) {Thread t1 new Thread(() -> {}, "t1");t1.start();} }start 方法调的是native sta…...
stm32之5.长按按键(使用时钟源)调整跑马灯速度
------------------------------ 源码 #include <stm32f4xx.h> #include "led.h" #include "delay.h" #include "my_str.h" #include "beep.h" #include "key.h" int main(void) { key_init(); Led_init();…...
element ui datePick时间日期一段时间,限制选择日期的范围
想限制只能选日期间隔为一年,联合选择器样式不好改,使用俩单独的 有两个办法限制 1.一个在外层使用form通过表单验证控制,出现错误提示(由于是两个单独的组件,触发验证的方式又为单个失去焦点,所以俩组件…...
kubernetes--技术文档-真--集群搭建-三台服务器一主二从(非高可用)-三服务器位于同交换机中
在使用k8s之前如果不太熟悉k8s的可以先看这个文章: kubernetes--技术文档--基本概念--《10分钟快速了解》_一单成的博客-CSDN博客 三节点相同安装操作: 1、设置hosts解析 根据角色在三个服务器中运行,设置自己的hostname。 标识…...
高性能MySQL实战(三):性能优化
大家好,我是 方圆。这篇主要介绍对慢 SQL 优化的一些手段,而在讲解具体的优化措施之前,我想先对 EXPLAIN 进行介绍,它是我们在分析查询时必要的操作,理解了它输出结果的内容更有利于我们优化 SQL。为了方便大家的阅读&…...
198. 打家劫舍
题目 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放…...
Pydev·离线git包
Pydev离线git包 1.下载离线git包:eclipse.egit.repository-4.4.0.201606070830-r.zip 2.将解压后目录:eclipse.egit.repository-4.4.0.201606070830-r\plugins下的jar文件放到 ide\eclipse\plugins目录下 3.重启pydevIDE 百度搜索站长工具:h…...
Vue-12.集成postcss.config.js
PostCSS 介绍 PostCSS 是一个用于处理样式的工具,可以通过插件来定制其行为。以下是一些常用的 PostCSS 插件和 API 的介绍: Autoprefixer: 这是一个流行的 PostCSS 插件,用于自动添加浏览器前缀,以确保您的样式在不同浏览器中具…...
基于前端技术原生HTML、JS、CSS 电子病历编辑器源码
电子病历系统采取结构化与自由式录入的新模式,自由书写,轻松录入。实现病人医疗记录(包含有首页、病程记录、检查检验结果、医嘱、手术记录、护理记录等等。)的保存、管理、传输和重现,取代手写纸张病历。不仅实现了纸…...
Linux环境下远程访问SVN服务:SVN内网穿透的详细配置与操作指南
文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…...
创建k8s operator
目录 1.前提条件 2.进一步准备 2.1.安装golang 2.2.安装code(vscode的linux版本) 2.3.安装kubebuilder 3.开始创建Operator 3.1.什么是operator? 3.2.GV & GVK & GVR 3.3.创建operator 3.3.1. 生成工程框架 3.3.2.生成api(GVK) …...
python模拟登入某平台+破解验证码
概述 python模拟登录平台,遇见验证码识别!用最简单的方法seleniumda破解验证码,来自动登录平台 详细 python用seleniumxpath模拟登录破解验证码 先随便找个小说平台用户登陆 - 书海小说网用户登陆 - 书海小说网用户登陆 - 书海小说网 准…...
【图像分割】理论篇(2)经典图像分割网络基于vgg16的Unet
UNet 是一种用于图像分割任务的深度学习架构,最早由 Olaf Ronneberger、Philipp Fischer 和 Thomas Brox 在2015年的论文 "U-Net: Convolutional Networks for Biomedical Image Segmentation" 中提出。UNet 在医学图像分割等领域取得了显著的成功&#x…...
vue插入重复的html内容
vue添加重复的html内容是通过绑定一个数组来v-for循环实现的。 效果展示: 首先创建数组,里面为重复内容的数量,里面默认存在一个初始值。 然后通过v-for来绑定这个数组,循环数据。 通过添加点击事件,来增加或删除数组…...
计算机网络-物理层(三)-信道的极限容量
计算机网络-物理层(三)-信道的极限容量 当信号在信道中传输失真不严重时,在信道的输出端,这些信号可以被识别 当信号在信道中,传输失真严重时,在信道的输出端就难以识别 造成失真的因素 码元传输速率信号传输距离噪声干扰传输媒…...
Http/Websocket协议的长连接和短连接的错误认识详细解读(史上最通俗)
从一个问题聊起: Http/Websocket 都称为一种协议,能用现实中的例子来解释协议吗? AI 举例: 您(客户端): 您坐在餐厅桌子上,想点一份菜单。 服务员(服务器)…...
两两交换链表中的节点
你存在,我深深的脑海里~ 题目: 示例: 思路: 这个题有点类似于反转一个单链表,不同的地方在于这个题不全反转,所以我们不同的地方在于此题多用了一个prve指针保存n1的前一个节点,以及头的改变&a…...
HTTP与RPC的取舍
HTTP与RPC的取舍 HTTP和RPC都是常用的网络通信协议,它们各有优劣。选择何种协议,主要取决于应用的需求和场景。 HTTP和RPC都有各自的优点和缺点,首先我们对两种协议进行一个总结。 HTTP协议图 HTTP的优点: 广泛的支持࿱…...
微前端学习(上)
一、课程目标 微前端概念;现有方案利弊;Single-spa实现原理;掌握使用qiankun搭建微应用;二、课程大纲 微前端背景现在web应用面临的问题微前端的价值微前端应用具备哪些能力微前端解决方案有哪些基于qiankun的实践1、微前端背景 2014年: Martin Fowler和James Lewis共同提…...
【Axure视频教程】标签版多选下拉列表
今天教大家在Axure里如何制作标签版多选下拉列表的原型模板,该模板用中继器制作,制作完成后使用也方便,只需要在中继器表格里维护选项信息,即可自动生成交互效果,包括显示隐藏选项列表,选中和取消选中选项&…...
Sharepoint2013必备软件安装路径
SP2013是最后一个有foundation版本的,后续各个版本都是server版,要买lisence。免费的可以用,但安装组件有些链接已经失效了,自己手动下载的路径备份一下,已经下载好的完整版,在文章最后可以直接下载&#x…...
C++day4(关系运算符的重载)
关系运算符重载的作用:可以让两个自定义类型对象进行对比操作。 代码实现关系运算符的重载: #include <iostream>using namespace std;class Person {// friend const Person operator(const Person &L, const Person &R); private:int …...
农业水价综合改革系统主要组成
一、系统概述 农业水价改革灌区信息化系统主要由感知采集层、网络传输层、系统应用层等部分组成。通过无线技术、感知层技术与新型应用的有效结合,可以用于各种业务的传送,充分满足灌区监测站间的物与物互联,农业生产的自动化和信息化相结合。…...
使用批处理文件(.bat)启动多个CMD窗口并执行命令
由于每次启动本机的mongodb和kafka,都需要进入相关目录进行启动,操作相对繁琐,于是想起了批处理来帮忙一键启动。 在桌面新建一个txt文件,改后缀名为.bat,并加上下面的代码。 cd /d D:\env-java\mongodb-win32-x86_64…...
网站设计培训班如何/搜狗链接提交入口
在应用中有时我们希望在不中断应用界面导航的前提下,我们希望插入一个展示内容的窗口。我们可以是用DefaultSheet及ComposerSheet来显示我们所需要的内容。其实在以前我们的Dialog教程中,有类似的功能尽管展示有一点不同。 我们来做一个练习:…...
web开发兼职网站开发/如何优化关键词
请用程序实现 输入一个正整数num,计算这个正整数的阶乘,并将计算结果输出。 # 请使用 input() 输入一个正整数 num numint(input()) # 请计算这个正整数的阶乘,并将计算结果输出 count1 for i in range(num,1,-1):countcount*i print(count)…...
医疗公司logo设计图片/关键词seo公司真实推荐
之前写了一篇博文,是一种画矩形的方法,但是今天介绍的方法比之前的要好一些,总结出来方便自己,方便需要的小伙伴们。。。。。。 直接上代码: 在头文件中写如下代码 protected:HICON m_hIcon;CPoint m_ptbegin;CPoint m…...
分销系统网站/免费发广告的平台有哪些
上天对谁都是一视同仁的,它在关上一扇门的同时,必定会打开一扇窗;无论多么糟糕的东西,世界都为其预留了位置,相信雨点不会仅仅落在你一个人的屋顶之上,相信你自己,大千世界总有属于你的角落;拥有积极乐观的…...
小说网站的里面的搜索是怎么做的/百度极速版下载
在本篇文章中,我们将介绍Visual C#对数据库的一个基本操作,即:如何往数据库中添加记录。我们将通过一些数据库操作的例子,来具体说明一下。为了更清楚的说明这个问题,在选用数据库方面采用了二种当前比较典型的数据库&…...
浙江省建设网站/网站提交收录
Java技术栈www.javastack.cn优秀的Java技术公众号以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新。Java 基础篇Java 多线程篇Java JVM篇Java 进阶篇Java 新特性篇Java 工具类篇Java 综合篇Java基础篇歪…...