遗传算法解决TSP问题

news2024/11/24 14:00:09

一、求解问题概述

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=1nj=1ncijxij

其中:

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 中的位置
					}

					// 如果两个变异点相邻,continue
					if ((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;
		// 初始化cityXY
		cityXY = 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() {
		// 初始化cityDis
		cityDis = 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--; // 需要随机的次数减一
			}
		}

		// 初始化individualAdaptability
		individualAdaptability = 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. [1]蔡之华,彭锦国,高伟,魏巍,康立山.一种改进的求解TSP问题的演化算法[J].计算机学报,2005(05):823-828. ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/923189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python爬虫猿人学逆向系列——第六题

题目&#xff1a;采集全部5页的彩票数据&#xff0c;计算全部中奖的总金额&#xff08;包含一、二、三等奖&#xff09; 地址&#xff1a;https://match.yuanrenxue.cn/match/6 本题比较简单&#xff0c;只是容易踩坑。话不多说请看分析。 两个参数&#xff0c;一个m一个f&…

三次握手四次挥手之全连接半连接队列

什么是全连接半连接 在 TCP 三次握手的时候&#xff0c;Linux 内核会维护两个队列&#xff0c;分别是&#xff1a; 半连接队列&#xff0c;也称 Listen 队列&#xff1b;全连接队列&#xff0c;也称 accept 队列&#xff1b; 工作原理 每一个socket执行listen时&#xff0c…

day-30 代码随想录算法训练营 回溯part06

332.重新安排行程 思路&#xff1a;使用unordered_map记录起点机场对应到达机场&#xff0c;内部使用map记录到达机场的次数&#xff08;因为map会进行排序&#xff0c;可以求出最小路径&#xff09; class Solution { public:vector<string>res;unordered_map<stri…

高等数学之曲率

a代表改变角度 s代表弧长 圆的曲率

JAVA-编程基础-10-集合

Lison <dreamlison163.com>, v1.0.0, 2023.04.23 JAVA-编程基础-10-集合 文章目录 JAVA-编程基础-10-集合List、Set、Map、队列全面解析ListArrayList创建ArrayList 向ArrayList中添加元素 List、Set、Map、队列全面解析 Java 集合框架可以分为两条大的支线&#xff1a;…

OpenSIPS 注册终端 30s 自动挂断问题

文章目录 1. 背景2. 问题分析3. 案例解决 1. 背景 在开发呼叫中心应用时&#xff0c;使用 OpenSIPS 作为 SIP 注册服务器&#xff0c;测试发现偶现电话接通后 30s 左右自动挂断的问题。一个正常的 SIP 注册及呼叫流程如下所示&#xff0c;可以看到 OpenSIPS 作为转发层只负责代…

Spark Standalone环境搭建及测试

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 篇一&#xff1a;Linux系统下配置java环境 篇二&#xff1a;hadoop伪分布式搭建&#xff08;超详细&#xff09; 篇三&#xff1a;hadoop完全分布式集群搭建&#xff08;超详细&#xf…

Unity 之 GameObject.Find()在场景中查找指定名称的游戏对象

文章目录 GameObject.Find 是 Unity 中的一个函数&#xff0c;用于在场景中查找指定名称的游戏对象。这个函数的主要作用是根据游戏对象的名称来查找并返回一个引用&#xff0c;使您能够在代码中操作该对象。以下是有关 GameObject.Find 的详细介绍&#xff1a; 函数签名&…

rust actix-web定义中间件(middleware)记录接口耗时(接口耗时中间件和鉴权中间件)

文章目录 Actix-web定义中间件(middleware)记录接口耗时中间件简介中间件添加的两种方式&#xff08;接口耗时中间件&#xff09;使用wrap_fn 闭包实现使用warp struct实现中间件调用顺序actix自带的接口耗时中间件 鉴权中间件 Actix-web定义中间件(middleware)记录接口耗时 …

一文全懂!带你了解芯片“流片”!

一、流片是什么&#xff1f; 流片(tape-out)是指通过一系列工艺步骤在流水线上制造芯片&#xff0c;是集成电路设计的最后环节&#xff0c;也就是送交制造。 流片即为"试生产"&#xff0c;简单来说就是设计完电路以后&#xff0c;先生产几片几十片&#xff0c;供测试…

Packet_Tracer的使用

一、实验目的&#xff1a; 通过该实验了解Packet Tracer的使用方法&#xff0c;能够用Packet Tracer建立和模拟网络模型。 二、主要任务&#xff1a; 1.熟悉PT的界面&#xff0c;了解按键用途。 2.尝试自己建立一个小型网络&#xff0c;并测试连通性。 3.学习P…

STM32--USART串口

文章目录 通信接口串口通信硬件电路电平标准参数时序 USART主要特性框图 数据帧发送器 波特率发生器SWART串口发送与接收工程串口收发数据包 通信接口 通信接口是指连接中央处理器&#xff08;CPU&#xff09;和标准通信子系统之间的接口&#xff0c;用于实现数据和控制信息在不…

【JVM 内存结构 | 程序计数器】

内存结构 前言简介程序计数器定义作用特点示例应用场景 主页传送门&#xff1a;&#x1f4c0; 传送 前言 Java 虚拟机的内存空间由 堆、栈、方法区、程序计数器和本地方法栈五部分组成。 简介 JVM&#xff08;Java Virtual Machine&#xff09;内存结构包括以下几个部分&#…

关于CC2652的看门狗和系统时钟的我呢

看门狗 可以在CCS的syscfg的ui中配置&#xff0c;如下图 如果想看相关例程&#xff0c;可以电极最顶部watchdog旁边的问号 相关问题&#xff1a; 例程中没有添加hw_wdt的头文件&#xff0c;需要#include <ti/devices/cc13x2_cc26x2/inc/hw_wdt.h>&#xff0c;否则在获…

全面介绍ERP采购审批管理

在现代企业中&#xff0c;采购管理对于保障企业正常运营和维护供应链的稳定性至关重要。然而&#xff0c;传统的手动采购审批流程常常存在效率低下、易出错和缺乏可追溯性等问题。为了解决这些问题&#xff0c;越来越多的企业选择采用ERP采购审批管理方法&#xff0c;以实现更高…

CentOS7 TAR安装 EMQX(MQTT)

1、软件下载 官网 --> 右上角[免费试用] --> EMQX 下载 --> EMQX 开源版 --> 选择版本 系统 --> [免费下载] 选择 tar.gz amd64 --> [立即下载] 选择对应下载方式 上传到 /usr/local/ 目录下。 2、安装 #进入操作目录 cd /usr/local#创建安装目录 mk…

javascript常用的东西

JavaScript 是一门强大的编程语言&#xff0c;用于为网页添加交互性和动态性。也可以锻炼人们的逻辑思维&#xff0c;是一个非常好的东西。 一、变量和数据类型&#xff1a; 变量&#xff1a; 变量是用于存储数据值的容器。在 JavaScript 中&#xff0c;你可以使用 var、let…

ELK之LogStash介绍及安装配置

一、logstash简介 集中、转换和存储数据 Logstash 是免费且开放的服务器端数据处理管道&#xff0c;能够从多个来源采集数据&#xff0c;转换数据&#xff0c;然后将数据发送到您最喜欢的“存储库”中。 Logstash 能够动态地采集、转换和传输数据&#xff0c;不受格式或复杂度的…

2023.8 - java - Java 方法

什么是方法呢&#xff1f; Java方法是语句的集合&#xff0c;它们在一起执行一个功能。 方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建&#xff0c;在其他地方被引用 方法的命名规则 1.方法的名字的第一个单词应以小写字母作为开头&#xff0…

铁威马教程丨铁威马NAS如何使用安全顾问工具

在使用NAS的过程中&#xff0c;我们时常可能忽略了一些小细节&#xff0c;久而久之可能造成一定的风险&#xff0c;影响着我们NAS的健康。而使用铁威马NAS的安全顾问工具&#xff0c;可以快速地帮我们扫描系统设置是否安全&#xff0c;让我们更放心更安心地使用NAS。 安全顾问…