1.什么是模拟退火算法?
模拟退火算法(Simulated Annlealing Algorithm,SAA最早是由N.Metropolis等人在1953年提出来的。
据说是他在洗澡的时候突然想到了这个模拟退火的方式。模拟退火的原理是初始时刻从一个较高的初始温度出发,开始物质中的分子处于随机排列的状态,随着温度系数的不断降低,随后分子逐渐以低能的状态进行排列,最终达到某种稳定的状态。
物理退火
要想理解模拟退火算法,首先我们要知道物理退火是怎样实现的,物理退火的过程大致分为三个阶段:
升温过程——我们通过不断地加热,加快分子的热运动,使整个物体处于随机、无序的状态,直到物体处于一个初始的温度T。
恒温过程——因为物体时时刻刻都在与外界进行物质或是温度的交换,物体的状态总是向自由能减少的方向进行,当自由能稳定时,物体就达到了一个平衡态。
冷却过程——物体内部的分子热运动逐渐减弱并且不断地趋于有序,物体的能量就会下降,从而达到了一个低能的晶体状。
Metropolis准则
模拟退火算法其中运用到了一个很重要的准则叫做——Metropolis准则 ,这个准则的过程如下:
1.首先设置一个初始的条件,初始次数k,初始温度T,过程中输出的解(同时也可以叫做状态)S(k),给S(k)一个初始值S(k)=S0;
2.①通过一定的方式在S(k)(当前解)的状态S上产生一个相邻的子集N(S(k)),
②在N(S(k))中随机的选取一个状态S’作为一个候选解,并计算这个候选解与当前解的差值,比较他们两个那一个更好。ΔC’=C(S’)-C(S(k));
3.接下来就要根据ΔC’的情况进行讨论:
①若ΔC’<=0,则S’成功地作为目前的最优解;
②若ΔC’>0,则要依据公式以概率P=e^{-(Ej−Ei)/(k∗T)}的方式选择S’作为最优解。其中Ej为新状态j的能量,Ei为旧状态i的能量,k为温度下降的比例系数,T为当前的温度。
4.k=k+1,状态进行更新,下一次的操作开始之前,检查算法是否已经满足了终止的条件:
①若满足,则进行步骤5。
②若不满足,则返回步骤2继续执行。
5.此时获得的最优解S(k)就是最后的答案,程序结束。
模拟退火
1.S(0)=S0 设置初始状态,i=0,记录运行的次数;
2.设置初始温度T=Ti,以T和S(k)调用Metropolis抽样算法,返回状态Si为算法的当前解,此时S=Si;
3.按照一定的方式进行降温,即T=T(i+1),其中T(i+1)<Ti,i=i+1;
4.检查是否满足终止条件,满足则执行步骤(5),否则执行步骤(2)。
5.当前Si为最优解,输出停止。
TSP问题
TSP问题又叫旅行商问题(Traveling Salesman Problem,TSP),是由爱尔兰数学家Sir William Rowan Hamilton和英国数学家Thomas Penyngton Kirkman在19世纪提出的数学问题。
它的描述是这样的:一名商人要到若干城市去推销商品,已知城市个数和各城市间的路程(或旅费),要求找到一条从城市1出发,经过所有城市且每个城市只能访问一次,最后回到城市1的路线,使总的路程(或旅费)最小。现已证明它属于NP(Non-deterministic Polynomial—非确定多项式)难题。
模拟退火解决TSP问题的优点
旧的算法解决TSP问题有以下缺点:时间复杂度高,并且在最坏的情况下时间复杂度未知,会陷入局部最优解的陷阱。
SAA依据Metropolis准则接受新解,除了接受优化解,还可以在一定的限度内接受恶化解。当温度T值较大时,接受的恶化解较差;T值较小时,接受的恶化解开始逐渐地变好。当T->0时,就不再接受恶化解了。
这样的算法就可以跳出局部最优解的陷阱,随着T的减小,算法返回的整体最优解概率单调增大,那么非最优解的概率就变小了。
代码
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include<fstream>
#include<typeinfo>
#include<vector>
#include <iterator>
#define N 200 //最大城市数量
#define T 3000 //初始温度
#define ET 1e-8 //终止温度
#define DELTA 0.98 //温度衰减率
#define LIMIT 1000 //概率选择上限
#define OUTLOOP 20 //外循环次数
#define INLOOP 100 //内循环次数
using namespace std;
//定义路线的结构体
struct Path
{
int city[N];//城市节点
double len;//路线的长度
};
//定义城市的结构体
struct City
{
double w[N][N];//记录两个城市之间的距离
double x, y;//记录城市的坐标
int n;
};
Path b;//最佳路径
City p[N];//记录每个城市的坐标
City C;
int ncase;//记录一共的测试次数
double dis(City A, City B)//计算A、B两点之间的距离
{
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
}
void GetDist(City& C,City p[])//将A,B两个城市之间的距离存储下来
{
for (int i = 0; i < C.n; i++)
for (int j = i + 1; j < C.n; j++)
C.w[i][j] = C.w[j][i] = dis(p[i], p[j]);
}
void Inputf(int& n,City p[])//输入各个城市的坐标
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
}
void Input(int& n, City& C)
{
C.n=n;
}
void tIn(int& n, City p[])
{
int i, datalen = 0;
double num[600];
ifstream file("data.txt");
while (!file.eof())
file >> num[datalen++];
n = num[0];
for (int i = 1; i < datalen - 1; i++)
{
if (i % 2 == 0)
p[i - 1].y = num[i];
else
p[i - 1].x = num[i];
}
}
void Init(City C)//创建出路径
{
ncase = 0;
b.len = 0;
for (int i = 0; i < C.n; i++)
{
b.city[i] = i;
if (i != C.n - 1)
{
cout << i <<"----->";//打印这个城市节点
b.len += C.w[i][i + 1];//最佳路径的长度增加
}
else
cout << i<<endl;//打印最后一个城市节点
}
}
void Print(Path t, City C)//打印具体路线及长度
{
cout << "Path is : ";
for (int i = 0; i <= C.n; i++)
{
if (i != C.n)
cout << t.city[i] << "-->";
else
cout << t.city[i];
}
cout << "\nThe path length is :\n" << t.len;
cout << "-----------------------------------\n\n";
}
Path GetNext(Path p,City C)
{
//Path p;
Path ans = p;
int x = rand() % (C.n - 1) + 1;// % 取余 -> 即将随机数控制在[1, n - 1]
int y = rand() % (C.n - 1) + 1;
while (x == y)//保证x与y不相等
{
x = rand() % (C.n - 1) + 1;
y = rand() % (C.n - 1) + 1;
}
swap(ans.city[x], ans.city[y]);//一种交换方式 交换两个城市的位置
ans.len = 0;
for (int i = 0; i < C.n-1; i++)
ans.len += C.w[ans.city[i]][ans.city[i + 1]];//计算路线的长度
ans.len += C.w[ans.city[C.n - 1]][ans.city[0]];
cout << "nCase = " << ncase << endl;//打印一共经历了几种情况
Print(ans, C);//打印路线
ncase++;
return ans;
}
void SA(City C)
{
double t = T;
srand((unsigned)(time(NULL)));
Path curPath = b;
Path newPath = b;
int P_L = 0;
int P_F = 0;
while (1) //外循环,主要更新参数t,模拟退火的过程
{
for (int i = 0; i < INLOOP; i++) //内循环,寻找在一定温度下的最优值
{
newPath = GetNext(curPath, C);//随机生成路线
double dE = newPath.len - curPath.len;//计算新路线是否更好
if (dE < 0) //如果新路线更好,直接更新
{
curPath = newPath;
P_L = 0;
P_F = 0;
}
else
{
double rd = rand() / (RAND_MAX + 1.0);//如果这个解不好,以一定概率更新,并且这个概率会越来越小
if (exp(dE / t) > rd&& exp(dE / t) < 1)
curPath = newPath;
P_L++;
}
if (P_L > LIMIT)//如果超过一千次的概率选择选择
{
P_F++;//一次外循环结束
break;
}
}
if (curPath.len < b.len)
b = curPath;//如果现在的路径最优 则更新
if (P_F > OUTLOOP || t < ET)//如果外循环超过20次,且温度降到终止温度则跳出循环
break;
t *= DELTA;//否则温度以0.98的速度衰减
}
}
int main(int argc, const char* argv[]) {
int n;
tIn(n, p);
Input(n,C);
GetDist(C,p);
Init(C);
SA(C);
Print(b, C);
cout << "Total test times is \n" << ncase;
return 0;
}
程序截图
data.txt 文本
这个文本放在程序所在的文件夹内,最初程序采用的是手动输入,然后手动输入存在数据量小、效率低下等原因,最终采用文本读入。
第一行是城市总数
下面是每个城市的坐标
运行截图
将所有的测试结果都打印了出来,最后一个结果即是最优的路径图。
在此感谢
https://blog.csdn.net/baimafujinji/article/details/52573630
和
https://blog.csdn.net/qq_34062105/article/details/80468038?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
的指点迷津。
来源:CSDN
作者:wjbmzbmr
链接:https://blog.csdn.net/weixin_38933749/article/details/104619655