模拟退火算法(SAA)解决TSP问题

[亡魂溺海] 提交于 2020-03-04 07:42:49

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 文本

这个文本放在程序所在的文件夹内,最初程序采用的是手动输入,然后手动输入存在数据量小、效率低下等原因,最终采用文本读入。

第一行是城市总数
下面是每个城市的坐标
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
的指点迷津。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!