SA求解TSP问题C++
1、模拟退火算法(SA)
模拟退火(simulated annealing, SA)最早是由Metropolis等借鉴统计力学中物质退火方法而提出的一种启发式随机搜索算法。该算法将组合优化问题和统计热力学中的热平衡问题类比,开辟了一条求解组合优化问题的新途径。
其出发点是固体的退火过程,即在对固体物质进行退火处理时,首先将它加温,使其可自由运动;然后降温,粒子逐渐形成低能态的晶体。若在凝结点附近温度下降得足够慢,则固体物质一定会形成最低能量的基态。
模拟退火算法能以随机搜素技术从概率的意义上找出目标函数的全局最优解。在搜索最优解的过程中,模拟退火算法除了可接受优化解外,还能够有限度地接受恶化解,且接受恶化解的概率慢慢趋于0。因而从理论上讲,经过足够长时间后可跳出局部最优解,从而收敛到全局最优解。模拟退火算法是一种通用的优化算法,适合解决大型组合优化问题,目前已在生产调度、控制工程、图像处理等方面广泛应用。
1.1 基本原理
模拟退火算法的基本思想是从一定解开始,从邻域中随机产生另一个解,允许目标函数在有限范围内变坏。它由控制参数 T T T决定,其作用类似于物理过程中的温度。对于控制参数的每一取值,算法持续进行“产生——判断——接受或舍弃”的迭代过程,对应着固体在某一恒定温度下趋于热平衡的过程。经过大量的解变换后,可以求得给定控制参数 T T T值时优化问题的相对最优解;然后减小控制参数 T T T的值,重复执行上述迭代过程,当控制参数逐渐减小并趋于0时,系统亦越来越趋于平衡状态,最后系统状态对应于优化问题的全局最优解。该过程称为冷却过程。因为固体退火必须缓慢降温,才能使得固体在每一个温度下都达到热平衡,最终趋于平衡状态,所以模拟退火算法中的控制参数 T T T也得缓慢衰减,才能使得模拟退火算法最终得到优化问题的整体最优解。
1.2 算法步骤
模拟退火算法的实现步骤如下:
步骤1:初始化:给定模型每一个采纳数的变化范围,在这个范围内随机选择一个初始解 x x x,并计算响应的目标函数值 E ( x ) E(x) E(x)。设定初始温度 T 0 T_0 T0,终止温度 T f T_f Tf产生随机数 ξ ( 0 , 1 ) ξ(0,1) ξ(0,1)作为概率阈值,设定降温规律。
步骤2:在某温度 T T T下,对当前解 x x x进行扰动,产生一个新解 c = x + △ x c=x+△x c=x+△x,计算相应的目标函数值 E ( x ′ ) E(x^{'}) E(x′)与 E ( x ) E(x) E(x)的差,得到
△ E = E ( x ′ ) − E ( x ) △E=E(x^{'})-E(x) △E=E(x′)−E(x)
步骤3:若 △ E < 0 △E<0 △E<0,则新解 x ′ x^{'} x′被接受;若 △ E > 0 △E>0 △E>0,则新解 x ′ x^{'} x′按概率 P = e x p ( − △ E / K ′ T ) P=exp(-△E/K^{'}T) P=exp(−△E/K′T)被接受, K ′ K^{'} K′为某一常数,通常取1; T T T为温度。若 P > ξ P>ξ P>ξ,则接受新解 x ′ x^{'} x′。当解 x ′ x^{'} x′被接受时,置 x = x ′ x=x^{'} x=x′。
步骤4:在温度 T T T下,重复一定次数的扰动和接受过程,即重复步骤2和步骤3.
步骤5:缓慢降低温度 T T T。
步骤6:重复步骤2~5,直到满足收敛条件为止。
模拟退火算法实质上分两次循环,随机扰动产生新解,并计算目标函数的变化;决定新解是否被接受。算法初温设计在高温条件使得能量 E E E增大的解可能被接受,因而能舍去局部极小值,通过缓慢地降低温度,算法最终能收敛到全局最优解。
从上述步骤可看出模拟退火算法根据 M e t r o p o l i s Metropolis Metropolis准则接受优化解。为此,除了接受优化解外,还在一定限度内接受恶化解,这正是模拟退火算法与局部搜索算法的本质区别。开始的时候 T T T值大,可能接受较差的恶化解;随着 T T T的减小,则只能接受较好的恶化解,最后在 T T T值趋于零时,就不再接受恶化解了,从而使得模拟退火算法能从局部最优的“陷阱”中跳出,最后得到全局最优解。
1.3 算法流程图
2、SA求解TSP问题的C++实现
2.1 输入数据文件:bayg29.tsp
1 1150.0 1760.0
2 630.0 1660.0
3 40.0 2090.0
4 750.0 1100.0
5 750.0 2030.0
6 1030.0 2070.0
7 1650.0 650.0
8 1490.0 1630.0
9 790.0 2260.0
10 710.0 1310.0
11 840.0 550.0
12 1170.0 2300.0
13 970.0 1340.0
14 510.0 700.0
15 750.0 900.0
16 1280.0 1200.0
17 230.0 590.0
18 460.0 860.0
19 1040.0 950.0
20 590.0 1390.0
21 830.0 1770.0
22 490.0 500.0
23 1840.0 1240.0
24 1260.0 1500.0
25 1280.0 790.0
26 490.0 2130.0
27 1460.0 1420.0
28 1260.0 1910.0
29 360.0 1980.0
2.2 头文件
#include <iostream>
#include <fstream>
#include <string>
#include <random>
#include <time.h>
using namespace std;
2.3 所需的类
所需的类包括城市类City、包含城市的地图类Graph、模拟退火算法类SA。
2.3.1 城市类City
class City
{
public:
string name;//城市名称
double x, y;//城市点的二维坐标
void shuchu()
{
std::cout << name + ":" << "(" << x << "," << y << ")" << endl;
}
};
2.3.2 包含城市的地图类Graph
class Graph
{
public:
int Citycount;
City *city;//城市数组
double distance[citycount][citycount];//城市间的距离矩阵
void Readcoordinatetxt(string txtfilename)//读取城市坐标文件的函数
{
Citycount = citycount;
city = new City[Citycount];
ifstream myfile(txtfilename, ios::in);
double x = 0, y = 0;
int z = 0;
if (!myfile.fail())
{
int i = 0;
while (!myfile.eof() && (myfile >> z >> x >> y))
{
city[i].name = to_string(_Longlong(z));//城市名称转化为字符串
city[i].x = x; city[i].y = y;
i++;
}
}
else
cout << "文件不存在";
myfile.close();//计算城市距离矩阵
for (int i = 0; i < citycount; i++)
for (int j = 0; j < citycount; j++)
{
distance[i][j] = sqrt((pow((city[i].x - city[j].x), 2) + pow((city[i].y - city[j].y), 2)) / 10.0);//计算城市ij之间的伪欧式距离
if (round(distance[i][j] < distance[i][j]))distance[i][j] = round(distance[i][j]) + 1;
else distance[i][j] = round(distance[i][j]);
}
}
void shuchu()
{
cout << "城市名称 " << "坐标x" << " " << "坐标y" << endl;
for (int i = 0; i < citycount; i++)
city[i].shuchu();
cout << "距离矩阵: " << endl;
for (int i = 0; i < citycount; i++)
{
for (int j = 0; j < citycount; j++)
{
if (j == citycount - 1)
std::cout << distance[i][j] << endl;
else
std::cout << distance[i][j] << " ";
}
}
}
};
2.3.3 模拟退火算法类SA
class SA //模拟退火算法类
{
public:
int x[citycount];//解
int newx[citycount];//扰动解
int best[citycount];//最优解
double bestdistancevalue;//最优解对应的路径距离值
void Init()
{
int *X=Random_N(citycount);
for (int i = 0; i < citycount; i++)
{
x[i] = X[i];
best[i] = x[i];
}
bestdistancevalue = Evaluate(best);
}
double Evaluate(int a[citycount])//计算解对应的目标函数值
{
double juli = 0;
for (int i = 0; i < citycount - 1; i++)
juli += Map_City.distance[a[i]-1][a[i + 1]-1];
juli += Map_City.distance[a[citycount - 1]-1][a[0] - 1];
return juli;
}
void Updatebest()//更新最优解的函数
{
if (Evaluate(x) < bestdistancevalue)
{
for (int i = 0; i < citycount; i++)
best[i] = 0;
for (int i = 0; i < citycount; i++)
best[i] = x[i];
bestdistancevalue = Evaluate(best);
}
}
void Adjuxt_validjie(int x[citycount])//调整蜜源路径有效性的函数,使得蜜源的位置符合TSP问题解的一个排列
{
int route[citycount];//1-citycount
bool flag[citycount];//对应route数组中是否在位置中存在的数组,参考数组为route
int biaoji[citycount];//对每个元素进行标记的数组,参考数组为粒子位置x
for (int j = 0; j < citycount; j++)
{
route[j] = j + 1;
flag[j] = false;
biaoji[j] = 0;
}
//首先判断位置中是否有某个城市且唯一,若有且唯一,则对应flag的值为true,
for (int j = 0; j < citycount; j++)
{
int num = 0;
for (int k = 0; k < citycount; k++)
{
if (x[k] == route[j])
{
biaoji[k] = 1;//说明k号元素对应的城市在route中,并且是第一次出现才进行标记
num++; break;
}
}
if (num == 0) flag[j] = false;//路线中没有route[j]这个城市
else if (num == 1) flag[j] = true;//路线中有route[j]这个城市
}
for (int k = 0; k < citycount; k++)
{
if (flag[k] == false)//路线中没有route[k]这个城市,需要将这个城市加入到路线中
{
int i = 0;
for (; i < citycount; i++)
{
if (biaoji[i] != 1)break;
}
x[i] = route[k];//对于标记为0的进行替换
biaoji[i] = 1;
}
}
}
void ShuchuBest()
{
for (int i = 0; i < citycount; i++)
{
if (i == 0)std::cout << "f(" << best[i] << ",";
else if (i == citycount - 1) std::cout << best[i] << ")=" << Evaluate(best) << endl;
else std::cout << best[i] << ",";
}
}
void SA_TSP(int itetime,double To,double Tf,double xishu,string filename)
{
ofstream outfile;
outfile.open("result.txt", ios::trunc);
Map_City.Readcoordinatetxt(filename);
Map_City.shuchu();
outfile << "城市名称 " << "坐标x" << " " << "坐标y" << endl;
for (int i = 0; i < citycount; i++)
outfile << Map_City.city[i].name << " " << Map_City.city[i].x << " " << Map_City.city[i].y << endl;
outfile << "距离矩阵: " << endl;
for (int i = 0; i < citycount; i++)
{
for (int j = 0; j < citycount; j++)
{
if (j == citycount - 1)
outfile << Map_City.distance[i][j] << endl;
else
outfile << Map_City.distance[i][j] << " ";
}
}
Init();//随机生成初始解
outfile << "初始解如下:" << endl;
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << x[i] << ",";
else if (i == citycount - 1) outfile << x[i] << ") = " << Evaluate(x) << endl;
else outfile << x[i] << ",";
}
double T = To;//设定初始温度
while (T > Tf)
{
outfile << "*********************************温度为" << T << "时的计算路径*********************************" << endl;
for (int t = 0; t < itetime; t++)
{
for (int i = 0; i < citycount; i++)
newx[i] = x[i];
Adjuxt_validjie(newx);//调整解对应路径的有效性,使之成为可行解
int randompos1, randompos2;
randompos1 = rand() % citycount;
while (true)
{
randompos2 = rand() % citycount;
if (randompos2 != randompos1)break;
}
int temp = newx[randompos1];
newx[randompos1] = newx[randompos2];
newx[randompos2] = temp;
double dE = Evaluate(newx) - Evaluate(x);
if (dE < 0)//接受新解
{
for (int i = 0; i < citycount; i++)
x[i] = newx[i];
}
else
{
double P = exp(-dE / T), ξ = r(random);
if (P > ξ)//按概率接受新解
{
for (int i = 0; i < citycount; i++)
x[i] = newx[i];
}
}
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << x[i] << ",";
else if (i == citycount - 1) outfile << x[i] << ") = " << Evaluate(x) << endl;
else outfile << x[i] << ",";
}
Updatebest();
}
std::cout << "温度为"<<T<<"时的最优解:" ;
ShuchuBest();//输出最优解
outfile << "温度为" << T << "时的最优解:";
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile<<"f("<< best[i] << ",";
else if (i == citycount - 1) outfile<<best[i] << ")=" << bestdistancevalue << endl;
else outfile << best[i] << ",";
}
T = xishu*T;
}
std::cout << "迭代结束后的最优解如下:" << endl;
ShuchuBest();//输出最优解s
outfile << "迭代结束后的最优解如下:" << endl;
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << best[i] << ",";
else if (i == citycount - 1) outfile << best[i] << ")=" << bestdistancevalue << endl;
else outfile << best[i] << ",";
}
outfile.close();
}
};
2.4 自定义函数
2.4.1 随机生成TSP问题的一个可行解的函数
int * Random_N(int n)//随机生成TSP问题的一个可行解的函数
{
int *geti;
geti = new int[n];
int j = 0;
while (j<n)
{
while (true)
{
int flag = -1;
int temp = rand() % n + 1;
if (j > 0)
{
int k = 0;
for (; k < j; k++)
{
if (temp == *(geti + k))break;
}
if (k == j)
{
*(geti + j) = temp;
flag = 1;
}
}
else
{
*(geti + j) = temp;
flag = 1;
}
if (flag == 1)break;
}
j++;
}
return geti;
}
2.4.2 调整解称为TSP可行的有效解的函数
Adjuxt_validjie函数被封装到模拟退火算法类SA中了。
void Adjuxt_validjie(int x[citycount])//调整蜜源路径有效性的函数,使得蜜源的位置符合TSP问题解的一个排列
{
int route[citycount];//1-citycount
bool flag[citycount];//对应route数组中是否在位置中存在的数组,参考数组为route
int biaoji[citycount];//对每个元素进行标记的数组,参考数组为粒子位置x
for (int j = 0; j < citycount; j++)
{
route[j] = j + 1;
flag[j] = false;
biaoji[j] = 0;
}
//首先判断位置中是否有某个城市且唯一,若有且唯一,则对应flag的值为true,
for (int j = 0; j < citycount; j++)
{
int num = 0;
for (int k = 0; k < citycount; k++)
{
if (x[k] == route[j])
{
biaoji[k] = 1;//说明k号元素对应的城市在route中,并且是第一次出现才进行标记
num++; break;
}
}
if (num == 0) flag[j] = false;//路线中没有route[j]这个城市
else if (num == 1) flag[j] = true;//路线中有route[j]这个城市
}
for (int k = 0; k < citycount; k++)
{
if (flag[k] == false)//路线中没有route[k]这个城市,需要将这个城市加入到路线中
{
int i = 0;
for (; i < citycount; i++)
{
if (biaoji[i] != 1)break;
}
x[i] = route[k];//对于标记为0的进行替换
biaoji[i] = 1;
}
}
}
2.5 全局变量
const int citycount = 29;
std::default_random_engine random((unsigned int)time(NULL));
std::uniform_real_distribution<double> r(0, 1); //随机数分布对象
Graph Map_City;//定义全局对象图,放在Graph类后
2.6 主函数
int main()
{
system("mode con cols=200");
system("color fc");
std::cout << "模拟退火算法求解TSP问题!" << endl;
SA sa;
sa.SA_TSP(10, 250, 50, 0.98, "E:\\计算智能代码\\SA_TSP\\SA_TSP\\bayg29.tsp");
system("pause");
return 0;
}
2.7 运行结果
2.7.1 控制台运行结果
2.7.2 生成result.txt文件结果
2.8 MATLAB绘制最优路径结果
附录(完整代码)
#include <iostream>
#include <fstream>
#include <string>
#include <random>
#include <time.h>
using namespace std;
const int citycount = 29;
std::default_random_engine random((unsigned int)time(NULL));
std::uniform_real_distribution<double> r(0, 1); //随机数分布对象
class City
{
public:
string name;//城市名称
double x, y;//城市点的二维坐标
void shuchu()
{
std::cout << name + ":" << "(" << x << "," << y << ")" << endl;
}
};
class Graph
{
public:
int Citycount;
City *city;//城市数组
double distance[citycount][citycount];//城市间的距离矩阵
void Readcoordinatetxt(string txtfilename)//读取城市坐标文件的函数
{
Citycount = citycount;
city = new City[Citycount];
ifstream myfile(txtfilename, ios::in);
double x = 0, y = 0;
int z = 0;
if (!myfile.fail())
{
int i = 0;
while (!myfile.eof() && (myfile >> z >> x >> y))
{
city[i].name = to_string(_Longlong(z));//城市名称转化为字符串
city[i].x = x; city[i].y = y;
i++;
}
}
else
cout << "文件不存在";
myfile.close();//计算城市距离矩阵
for (int i = 0; i < citycount; i++)
for (int j = 0; j < citycount; j++)
{
distance[i][j] = sqrt((pow((city[i].x - city[j].x), 2) + pow((city[i].y - city[j].y), 2)) / 10.0);//计算城市ij之间的伪欧式距离
if (round(distance[i][j] < distance[i][j]))distance[i][j] = round(distance[i][j]) + 1;
else distance[i][j] = round(distance[i][j]);
}
}
void shuchu()
{
cout << "城市名称 " << "坐标x" << " " << "坐标y" << endl;
for (int i = 0; i < citycount; i++)
city[i].shuchu();
cout << "距离矩阵: " << endl;
for (int i = 0; i < citycount; i++)
{
for (int j = 0; j < citycount; j++)
{
if (j == citycount - 1)
std::cout << distance[i][j] << endl;
else
std::cout << distance[i][j] << " ";
}
}
}
};
Graph Map_City;//定义全局对象图,放在Graph类后
int * Random_N(int n)//随机生成TSP问题的一个可行解的函数
{
int *geti;
geti = new int[n];
int j = 0;
while (j<n)
{
while (true)
{
int flag = -1;
int temp = rand() % n + 1;
if (j > 0)
{
int k = 0;
for (; k < j; k++)
{
if (temp == *(geti + k))break;
}
if (k == j)
{
*(geti + j) = temp;
flag = 1;
}
}
else
{
*(geti + j) = temp;
flag = 1;
}
if (flag == 1)break;
}
j++;
}
return geti;
}
class SA //模拟退火算法类
{
public:
int x[citycount];//解
int newx[citycount];//扰动解
int best[citycount];//最优解
double bestdistancevalue;//最优解对应的路径距离值
void Init()
{
int *X=Random_N(citycount);
for (int i = 0; i < citycount; i++)
{
x[i] = X[i];
best[i] = x[i];
}
bestdistancevalue = Evaluate(best);
}
double Evaluate(int a[citycount])//计算解对应的目标函数值
{
double juli = 0;
for (int i = 0; i < citycount - 1; i++)
juli += Map_City.distance[a[i]-1][a[i + 1]-1];
juli += Map_City.distance[a[citycount - 1]-1][a[0] - 1];
return juli;
}
void Updatebest()//更新最优解的函数
{
if (Evaluate(x) < bestdistancevalue)
{
for (int i = 0; i < citycount; i++)
best[i] = 0;
for (int i = 0; i < citycount; i++)
best[i] = x[i];
bestdistancevalue = Evaluate(best);
}
}
void Adjuxt_validjie(int x[citycount])//调整蜜源路径有效性的函数,使得蜜源的位置符合TSP问题解的一个排列
{
int route[citycount];//1-citycount
bool flag[citycount];//对应route数组中是否在位置中存在的数组,参考数组为route
int biaoji[citycount];//对每个元素进行标记的数组,参考数组为粒子位置x
for (int j = 0; j < citycount; j++)
{
route[j] = j + 1;
flag[j] = false;
biaoji[j] = 0;
}
//首先判断位置中是否有某个城市且唯一,若有且唯一,则对应flag的值为true,
for (int j = 0; j < citycount; j++)
{
int num = 0;
for (int k = 0; k < citycount; k++)
{
if (x[k] == route[j])
{
biaoji[k] = 1;//说明k号元素对应的城市在route中,并且是第一次出现才进行标记
num++; break;
}
}
if (num == 0) flag[j] = false;//路线中没有route[j]这个城市
else if (num == 1) flag[j] = true;//路线中有route[j]这个城市
}
for (int k = 0; k < citycount; k++)
{
if (flag[k] == false)//路线中没有route[k]这个城市,需要将这个城市加入到路线中
{
int i = 0;
for (; i < citycount; i++)
{
if (biaoji[i] != 1)break;
}
x[i] = route[k];//对于标记为0的进行替换
biaoji[i] = 1;
}
}
}
void ShuchuBest()
{
for (int i = 0; i < citycount; i++)
{
if (i == 0)std::cout << "f(" << best[i] << ",";
else if (i == citycount - 1) std::cout << best[i] << ")=" << Evaluate(best) << endl;
else std::cout << best[i] << ",";
}
}
void SA_TSP(int itetime,double To,double Tf,double xishu,string filename)
{
ofstream outfile;
outfile.open("result.txt", ios::trunc);
Map_City.Readcoordinatetxt(filename);
Map_City.shuchu();
outfile << "城市名称 " << "坐标x" << " " << "坐标y" << endl;
for (int i = 0; i < citycount; i++)
outfile << Map_City.city[i].name << " " << Map_City.city[i].x << " " << Map_City.city[i].y << endl;
outfile << "距离矩阵: " << endl;
for (int i = 0; i < citycount; i++)
{
for (int j = 0; j < citycount; j++)
{
if (j == citycount - 1)
outfile << Map_City.distance[i][j] << endl;
else
outfile << Map_City.distance[i][j] << " ";
}
}
Init();//随机生成初始解
outfile << "初始解如下:" << endl;
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << x[i] << ",";
else if (i == citycount - 1) outfile << x[i] << ") = " << Evaluate(x) << endl;
else outfile << x[i] << ",";
}
double T = To;//设定初始温度
while (T > Tf)
{
outfile << "*********************************温度为" << T << "时的计算路径*********************************" << endl;
for (int t = 0; t < itetime; t++)
{
for (int i = 0; i < citycount; i++)
newx[i] = x[i];
Adjuxt_validjie(newx);//调整解对应路径的有效性,使之成为可行解
int randompos1, randompos2;
randompos1 = rand() % citycount;
while (true)
{
randompos2 = rand() % citycount;
if (randompos2 != randompos1)break;
}
int temp = newx[randompos1];
newx[randompos1] = newx[randompos2];
newx[randompos2] = temp;
double dE = Evaluate(newx) - Evaluate(x);
if (dE < 0)//接受新解
{
for (int i = 0; i < citycount; i++)
x[i] = newx[i];
}
else
{
double P = exp(-dE / T), ξ = r(random);
if (P > ξ)//按概率接受新解
{
for (int i = 0; i < citycount; i++)
x[i] = newx[i];
}
}
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << x[i] << ",";
else if (i == citycount - 1) outfile << x[i] << ") = " << Evaluate(x) << endl;
else outfile << x[i] << ",";
}
Updatebest();
}
std::cout << "温度为"<<T<<"时的最优解:" ;
ShuchuBest();//输出最优解
outfile << "温度为" << T << "时的最优解:";
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile<<"f("<< best[i] << ",";
else if (i == citycount - 1) outfile<<best[i] << ")=" << bestdistancevalue << endl;
else outfile << best[i] << ",";
}
T = xishu*T;
}
std::cout << "迭代结束后的最优解如下:" << endl;
ShuchuBest();//输出最优解s
outfile << "迭代结束后的最优解如下:" << endl;
for (int i = 0; i < citycount; i++)
{
if (i == 0)outfile << "f(" << best[i] << ",";
else if (i == citycount - 1) outfile << best[i] << ")=" << bestdistancevalue << endl;
else outfile << best[i] << ",";
}
outfile.close();
}
};
int main()
{
system("mode con cols=200");
system("color fc");
std::cout << "模拟退火算法求解TSP问题!" << endl;
SA sa;
sa.SA_TSP(10, 250, 50, 0.98, "E:\\计算智能代码\\SA_TSP\\SA_TSP\\bayg29.tsp");
system("pause");
return 0;
}