C++下基于模拟退火算法解决TSP问题

news2024/9/21 4:35:25

一、原理

首先明确TSP问题的目标是什么,假设当前有3个城市,需要你从第一个城市开始,遍历所有城市,然后回到初始位置,要求总路径最短。这个时候就需要计算每个城市之间的两两距离,然后按顺序确定一条最短路径。

最稳妥的方案是,使用暴力枚举,列举出来所有的路线为1->2->3->1、1->3->2->1,然后计算一个最短路径即可。

但是当城市数量很多的时候,暴力枚举的方法计算量十分大,此时就需要考虑有没有简单一些的方法,虽然不是十分准确,但是能猜出一个大致的结果,即使不是最优解,只要接近最优结果也能接受。其他大部分智能优化算法都是这种思路,只是使用的具体思路有区别。

模拟退火算法,实际就是猜答案,然后从猜的结果中找个最优的结果输出,当猜的数量够大,结果就最接近实际结果。它会设置一个初始温度比较高例如1000,一个结束温度20,以一定函数从1000降低到20,然后设置一个迭代次数,每次迭代都会随机打乱路线,如果这个路线比之前的路线好,则直接把它作为最优路线,如果没有之前的好,则以一个很小的概率接受,这个概率也是通过随机数确定的,这里为什么要以一个很小的概率接受一个不好的结果呢?其实是为了防止结果陷入局部最优解,但是对于模拟退火这种猜答案的方式,个人感觉其实用不着以很小的概率接受一共不好的路线。

所以这个算法名字听起来不知所云,但是主要就是猜答案,也没有什么特殊的。

二、代码

//模拟退火算法(SA)求解TSP问题
/*类的体会2:
1.类外访问类中private方式:
①通过设置类中的public,get()函数,来获取一些变量值;
②对于数组的获取,一方面可以通过get(下标)函数来完成一对一的获取;
另一方面可以通过在类中public处声明类外的友元函数friend(对象),友元函数通过对象可以访问到private
2.类中函数要访问类外数据:
①全局变量可以直接访问;
②在类外创建该类的对象,通过对象调用函数,并传递类外数据参数;
③访问其他类还可以用友元函数
*/

#include<iostream>
#include<stdlib.h>
#include<time.h>
#include<math.h>
#include<vector>
#include<algorithm>

using namespace std;

#define T0 1000				//初始温度
#define T_end 20			//退出温度(环境温度)
#define q 0.9				//退火系数
#define L 10				//每个温度时的迭代次数,即链长
#define C 10				//城市数量

//10个城市的坐标:
double citys_position[C][2] =
{
	{1304,2312},{3639,1315},{4177,2244},{3712,1399},{3488,1535},
	{3326,1556},{3238,1229},{4196,1004},{4312,7900},{4386,570}
};

class Simulated_Annealing
{
private:
	int i, j;
	int Current_Solution[C];		//存放一个解
	int Current_copy[C];			//存放解的副本,用于计算差异
	double T = T0;					//温度变量,并初始化
	double f1, f2, df;				//f1是初始解目标值,f2是新解目标值,df是新旧解目标值差
	int count = 0;					//记录降温次数(因为按系数退火,迭代次数不确定)
	vector<vector<double>> All_soluitons;	//存放每代中最后的解(也可以是全部解,加上链表迭代的) 
	vector<double> All_fitness;		//存放每代中最后的解值

public:
	/*《函数SA声明》:模拟退火主体功能*/
	void SA();
	/*《函数citys_generate声明》:初始化解*/
	void citys_generate();
	/*《函数Swap声明》:初始化解*/
	void Swap(int* input_solution);
	/*《Fitness函数声明:对传入解计算适应度值》*/
	double Fitness(int* input_solution);
	/*《distance函数声明:计算两两节点的距离,传入两个城市各自的坐标信息》*/
	double distance(double* city1, double* city2);
	/*《getCount函数:给外部调用count值》*/
	int getCount() { return count; }
	/*《getF1函数:给外部调用f1值》*/
	int getF1() { return f1; }
	/*《getCurrent_Solution函数:给外部调用该数组》*/
	int getCurrent_Solution(int k)
	{
		return Current_Solution[k];
	}

	/*《get_All_solutions函数申明:定义为类中的友元函数,可以通过对象去访问类中的private。
		注意:友元函数是可以访问类中的私有成员,但是得告诉他,他是哪个类的朋友。
		是通过对象的传参来作过渡,而不能实现直接的访问,他找不到!》*/
	friend void get_All_solutions(Simulated_Annealing &SA);

};

void main()
{
	srand((unsigned)time(NULL));

	time_t start, finish;			//计时变量
	start = clock();				//程序运行开始计时

	/*《调用Simulated_Annealing类》*/
	Simulated_Annealing SA1;
	SA1.SA();

	finish = clock();				//程序结束停止计时

	//计算运行时间
	double run_time = ((double)(finish - start)) / CLOCKS_PER_SEC;

	//输出结果
	printf("模拟退火算法解TSP问题:\n");
	cout << "初始温度T0=" << T0 << ",降温系数q=" << q << endl;
	printf("每个温度迭代%d次,共降温%d次。\n", L, SA1.getCount());

	/*《调用get_All_solutions函数:输出每次退火的方法和值,友元函数,可以通过对象去访问类中的private》*/
	get_All_solutions(SA1);

	printf("最终得TSP问题最优路径为:\n");
	for (int j = 0; j < C - 1; j++)
	{
		printf("%d—>", SA1.getCurrent_Solution(j));
	}
	printf("%d—>", SA1.getCurrent_Solution(C - 1));
	printf("%d\n", SA1.getCurrent_Solution(0));

	cout << "最优路径长度为:" << SA1.getF1() << endl;
	printf("程序运行耗时%1f秒.\n", run_time);

}

/*《函数SA定义》:模拟退火主体功能*/
void Simulated_Annealing::SA()
{
	/*①《调用函数citys_generate》:初始化解*/
	citys_generate();
	/*《调用Fitness函数:对传入解计算适应度值》*/
	f1 = Fitness(Current_Solution);//随机求解的一个代价值
	while (T > T_end)//当温度低于结束温度时,退火结束
	{
		All_soluitons.push_back({});	//每代开始添加一个空行,存放本代结果值
		All_fitness.push_back({});		//每代开始添加一个空行,存放本代代价值
		for (i = 0; i < L; i++)//迭代次数5
		{
			//②复制初始解
			for (j = 0; j < C; j++)
			{
				Current_copy[j] = Current_Solution[j];
			}
			/*③《调用Swap函数:对传入解做邻域搜索,两点交换》*/
			Swap(Current_copy);//随机生成一个新的路线
			/*④《调用Fitness函数:对传入解计算适应度值》*/
			f2 = Fitness(Current_copy);//计算当前代价
			df = f1 - f2;//当前代价和前一个代价差

			/*退火原理:Metropolis准则,df<0*/
			//⑤若新解>旧解值,df<0,则以概率 exp(df / T) 接受新解  df越大,指数越大。含义为新解效果越差,接受概率越小
			if (df < 0)
			{
				//随机数(0,1),用于决定是否接受新解
				double r;
				r = (double)rand() / RAND_MAX; //分母32767
				if (r < exp(df / T))//以很小的概率接受这个不好的解
				{
					//接受新解
					for (j = 0; j < C; j++)
					{
						Current_Solution[j] = Current_copy[j];
					}
					f1 = f2;
				}
			}
			//⑥若新解<=旧解值,df>=0,则直接接受新解
			else//如果新解效果比较好,直接用这个新解
			{
				for (j = 0; j < C; j++)
				{
					Current_Solution[j] = Current_copy[j];
				}
				f1 = f2;
			}
		}
		//⑦存放本次迭代的最后方案和适应值
		for (int j = 0; j < C; j++)
		{
			All_soluitons[count].push_back(Current_Solution[j]);//每次迭代的路径存入vector
		}
		All_fitness[count] = f1;

		//⑧以一定的速率降温
		T *= q;//以1/2的速率减小
		count++;
	}


}
/*《函数citys_generate定义》:初始化解*/
void Simulated_Annealing::citys_generate()
{
	//①一个城市序列
	vector<int> temp_city;
	for (int i = 0; i < C; i++)//城市数量
	{
		temp_city.push_back(i + 1);
	}
	//②打乱
	random_shuffle(temp_city.begin(), temp_city.end());//均匀分布来随机移动元素
	//③赋值给Current_Solution
	for (int j = 0; j < temp_city.size(); j++)
	{
		Current_Solution[j] = temp_city[j];//初始为随机解
	}
}
/*《函数Swap定义》:初始化解*/
void Simulated_Annealing::Swap(int* input_solution)
{
	//①生成交换节点下标
	int point1 = rand() % C;//交换点1
	int point2 = rand() % C;//交换点2
	while (point1 == point2)
	{
		point2 = rand() % C;//保证交换点不同
	}
	//②交换
	swap(input_solution[point1], input_solution[point2]);//随机交换最终路径的航点
}

/*《Fitness函数定义:对传入解计算适应度值》*/
double Simulated_Annealing::Fitness(int* input_solution)//代价函数
{
	//初始化路径长度
	double cost = 0;
	//从第一个城市遍历到最后一个城市
	for (int j = 0; j < C - 1; j++)
	{
		/*《调用distance函数:计算两两节点的距离,传入两个城市各自的坐标信息》*/
		int k = input_solution[j];//城市序号-1=城市位置下标   第二个参数为第二个城市的位置
		cost += distance(citys_position[input_solution[j] - 1], citys_position[input_solution[j + 1] - 1]);//初始位置为第一个城市
	}
	//从最后一个城市回到第一个城市
	cost += distance(citys_position[input_solution[C - 1] - 1], citys_position[input_solution[0] - 1]);

	return cost;
}
/*《distance函数声明:计算两两节点的距离,传入两个城市各自的坐标信息》*/
double Simulated_Annealing::distance(double* city1, double* city2)
{
	/*计算相邻两城市间距离*/
	double x1 = city1[0];			//城市1横坐标
	double y1 = city1[1];
	double x2 = city2[0];
	double y2 = city2[1];			//城市2纵坐标
	double dist = pow((pow((x1 - x2), 2) + pow((y1 - y2), 2)), 0.5);

	return dist;					//返回距离值
}
/*《get_All_solutions函数申明:定义为类中的友元函数,通过对象可以访问类中的向量》*/
void get_All_solutions(Simulated_Annealing &SA)
{
	cout << "每次退火内部迭代的最终解:" << endl;
	for (int i = 0; i < SA.All_soluitons.size(); i++)
	{
		for (int j = 0; j < SA.All_soluitons[i].size(); j++)
		{
			cout << SA.All_soluitons[i][j] << "—>";
		}
		cout << SA.All_soluitons[i][0] << endl;
		cout << "路线代价为:" << SA.All_fitness[i] << std::endl << std::endl;
	}
}

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

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

相关文章

【Unity】2D 对话模块的实现

对话模块主要参考 【Unity教程】剧情对话系统 实现。 在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话&#xff0c;也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。 …

机器视觉康耐视visionpro-CogFitLineTool拟合直线错误上的理解

视频详细解答&#xff1a;康耐视VisionPro高级脚本系列教程-2.拟合指教脚本遍历编写 什么是直线&#xff1f; 直线&#xff1a;那么直线是由无线的点组成的。特点是无端点&#xff1b;不能度量&#xff0c;两边无限延长。错误示例子如下&#xff1a; 首先上面是错误的理解&…

UG\NX CAM二次开发 设置工序切削区域 UF_CAMGEOM_append_items

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 设置工序切削区域 UF_CAMGEOM_append_items 效果: 代码: static int init_proc(UF_UI_selection_p_t select, void* user_data) { int errorCode = 0; int num_triples = 1;//UF_UI_mas…

来可LCWLAN-600P产品使用和常见问题说明

01LCWLAN-600P简介 LCWLAN-600P是来可电子最新生产的一款CAN转WiFi设备&#xff0c;该设备的主要功能是将CAN数据转换成网络数据并通过无线网络转发出去。设备支持8~30V宽压供电&#xff0c;出厂默认配置为AP模式&#xff0c;设备供电后可在电脑的WiFi搜索栏搜索到名称为LCWLA…

2023年意大利富时MIB指数研究报告

第一章 指数概况 1.1 指数基本情况 富时MIB指数&#xff08;意大利语&#xff1a;Financial Times Stock Exchange Milano Indice di Borsa, FTSE MIB&#xff09;&#xff0c;2009年6月之前称为S&P/MIB&#xff0c;是意大利证券交易所的基准股票市场指数。该指数于2004年…

[其他]IDEA中Maven项目配置国内源

配置国内源主要解决了,在maven项目中pom.xml下载jar包失败或过慢的问题. 在IDEA中的设置分成两种,设置当前项目与新创项目. 我们就需要两种都进行设置,不然只有在当前项目配置了国内源,新创项目的时候还是默认的状态. 由于下面两种设置的界面与方法是一致的,下面以当前项目设…

jmeter采集ELK平台海量业务日志( 采用Scroll)

由于性能测试需要&#xff0c;需采集某业务系统海量日志&#xff08;百万以上&#xff09;来使用&#xff0c;做稳定性压测使用。但Elasticsearch的结果分页size单次最大为10000&#xff08;运维同事为保证ES安全&#xff09;。为了能够快速采集ELK平台业务日志&#xff0c;可以…

Python解释器和Pycharm的傻瓜式安装部署

给我家憨憨写的python教程 有惊喜等你找噢 ——雁丘 Python解释器Pycharm的安装部署 关于本专栏一 Python解释器1.1 使用命令提示符编写Python程序1.2 用记事本编写Python程序 二 Pycharm的安装三 Pycharm的部署四 Pycharm基础使用技巧4.1 修改主题颜色4.2 修改字体4.3 快速修…

# (1462. 课程表 IV leetcode)广搜+拓扑-------------------Java实现

&#xff08;1462. 课程表 IV leetcode&#xff09;广搜拓扑-------------------Java实现 题目表述 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你想…

【Fiddler】mac m1 机器上使用 fiddler 抓取接口

mac m1 机器上使用 fiddler 抓取接口&#xff08;非虚拟机模式&#xff09; author: jwensh date:2023.09.12 文章目录 mac m1 机器上使用 fiddler 抓取接口&#xff08;非虚拟机模式&#xff09;1. 环境准备2. 进行配置3. 使用情况 1. 环境准备 想要抓取 mac 上浏览器的接口&a…

香橙派配合IIC驱动OLED 使用SourceInsight解读源码

OLED屏幕介绍 & 硬件接线 OLED也是老熟人了&#xff0c;详细的介绍见&#xff1a; IIC 协议 和 OLED_iic oled-CSDN博客 再次回顾香橙派硬件接线&#xff1a; 根据之前的学习了解到&#xff0c;OLED屏幕的驱动是需要IIC协议的&#xff0c;在学习C52时我使用了代码模拟IIC协…

C++之map如何实例化类对象(一百九十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

总结 NAT 机制的工作流程及优缺点

什么是NAT NAT定义 **NAT&#xff08;Network Address Translator&#xff0c;网络地址转换&#xff09;**是用于在本地网络中使用私有地址,在连接互联网时转而使用全局IP地址的技术. 实际上是为解决IPv4地址短缺而开发的技术: NAT技术作为当前解决IP地址不够用的主要手段&a…

pgzrun 拼图游戏制作过程详解(3)

3. 绘制完整的拼图 建立Gird列表存储小拼图的基本信息 Gird[] for i in range(6):for j in range(4):SquareActor("girl_06")Square.leftSquare_size*jSquare.topSquare_size*iGird.append(Square) 修改draw()绘制函数 建立循环绘制Gird列表中的所有小拼图 def d…

laravel安装初步使用学习 composer安装

一、什么是laravel框架 Laravel框架可以开发各种不同类型的项目&#xff0c;内容管理系统&#xff08;Content Management System&#xff0c;CMS&#xff09;是一种比较典型的项目&#xff0c;常见的网站类型&#xff08;如门户、新闻、博客、文章等&#xff09;都可以利用CM…

stm32---基本定时器(TIM6,TIM7)

STM32F1的定时器非常多&#xff0c;由两个基本定时器&#xff08;TIM6&#xff0c;TIM7&#xff09;、4个通用定时器&#xff08;TIM2-TIM5&#xff09;和两个高级定时器&#xff08;TIM&#xff11;&#xff0c;TIM&#xff18;&#xff09;组成。基本定时器的功能最为简单&am…

优维低代码实践:菜单

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

并查集基础与二分搜索树的特性

并查集基础 一、概念及其介绍 并查集是一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题。 并查集的思想是用一个数组表示了整片森林&#xff08;parent&#xff09;&#xff0c;树的根节点唯一标识了一个集合&#xff0c;我们只要找到了某个元素的的…

卫星物联网生态建设全面加速,如何抓住机遇?

当前&#xff0c;卫星通信无疑是行业最热门的话题之一。近期发布的华为Mate 60 Pro“向上捅破天”技术再次升级&#xff0c;成为全球首款支持卫星通话的大众智能手机&#xff0c;支持拨打和接听卫星电话&#xff0c;还可自由编辑卫星消息。 据悉&#xff0c;华为手机的卫星通话…

开源日报 0829 | 改变面试方式:拒绝死板问题,推崇真实情境

poteto/hiring-without-whiteboards Stars: 38.3k License: MIT 这个项目是一个不进行 “白板” 面试的公司 (或团队) 的列表。“白板” 在这里被用作一种隐喻&#xff0c;代表与糟糕的面试实践相关联的 CS 知识问答题。该项目中列出的公司和团队使用类似日常工作情境下的面试…