模拟退火算法的原理+应用

article/2025/9/21 0:13:42

本文转自https://www.cnblogs.com/ranjiewen/p/6084052.html

模拟退火算法

     著名的模拟退火算法,它是一种基于蒙特卡洛思想设计的近似求解最优化问题的方法。

 

一点历史——如果你不感兴趣,可以跳过

     美国物理学家 N.Metropolis 和同仁在1953年发表研究复杂系统、计算其中能量分布的文章,他们使用蒙特卡罗模拟法计算多分子系统中分子的能量分布。这相当于是本文所探讨之问题的开始,事实上,模拟退火中常常被提到的一个名词就是Metropolis准则,后面我们还会介绍。


美国IBM公司物理学家 S.Kirkpatrick、C. D. Gelatt 和 M. P. Vecchi 于1983年在《Science》上发表了一篇颇具影响力的文章:《以模拟退火法进行最优化(Optimization by Simulated Annealing)》。他们借用了Metropolis等人的方法探讨一种旋转玻璃态系统(spin glass system)时,发觉其物理系统的能量和一些组合最优(combinatorial optimization)问题(著名的旅行推销员问题TSP即是一个代表例子)的成本函数相当类似:寻求最低成本即似寻求最低能量。由此,他们发展出以 Metropolis  方法为本的一套算法,并用其来解决组合问题等的寻求最优解。

 

几乎同时,欧洲物理学家 V.Carny 也发表了几乎相同的成果,但两者是各自独立发现的;只是Carny“运气不佳”,当时没什么人注意到他的大作;或许可以说,《Science》杂志行销全球,“曝光度”很高,素负盛名,而Carny却在另外一本发行量很小的专门学术期刊《J.Opt.Theory Appl.》发表其成果因而并未引起应有的关注。

 

Kirkpatrick等人受到Metropolis等人用蒙特卡罗模拟的启发而发明了“模拟退火”这个名词,因为它和物体退火过程相类似。寻找问题的最优解(最值)即类似寻找系统的最低能量。因此系统降温时,能量也逐渐下降,而同样意义地,问题的解也“下降”到最值。

 

一、什么是退火——物理上的由来

 

在热力学上,退火(annealing)现象指物体逐渐降温的物理现象,温度愈低,物体的能量状态会低;够低后,液体开始冷凝与结晶,在结晶状态时,系统的能量状态最低。大自然在缓慢降温(亦即,退火)时,可“找到”最低能量状态:结晶。但是,如果过程过急过快,快速降温(亦称「淬炼」,quenching)时,会导致不是最低能态的非晶形。

 

如下图所示,首先(左图)物体处于非晶体状态。我们将固体加温至充分高(中图),再让其徐徐冷却,也就退火(右图)。加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小(此时物体以晶体形态呈现)。

 

似乎,大自然知道慢工出细活:缓缓降温,使得物体分子在每一温度时,能够有足够时间找到安顿位置,则逐渐地,到最后可得到最低能态,系统最安稳。

 

二、模拟退火(Simulate Anneal)

 

如果你对退火的物理意义还是晕晕的,没关系我们还有更为简单的理解方式。想象一下如果我们现在有下面这样一个函数,现在想求函数的(全局)最优解。如果采用Greedy策略,那么从A点开始试探,如果函数值继续减少,那么试探过程就会继续。而当到达点B时,显然我们的探求过程就结束了(因为无论朝哪个方向努力,结果只会越来越大)。最终我们只能找打一个局部最后解B。

模拟退火其实也是一种Greedy算法,但是它的搜索过程引入了随机因素。模拟退火算法以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以上图为例,模拟退火算法在搜索到局部最优解B后,会以一定的概率接受向右继续移动。也许经过几次这样的不是局部最优的移动后会到达B 和C之间的峰点,于是就跳出了局部最小值B。

 

根据Metropolis准则,粒子在温度T时趋于平衡的概率为exp(-ΔE/(kT)),其中E为温度T时的内能,ΔE为其改变数,k为Boltzmann常数。Metropolis准则常表示为


Metropolis准则表明,在温度为T时,出现能量差为dE的降温的概率为P(dE),表示为:P(dE) = exp( dE/(kT) )。其中k是一个常数,exp表示自然指数,且dE<0。所以P和T正相关。这条公式就表示:温度越高,出现一次能量差为dE的降温的概率就越大;温度越低,则出现降温的概率就越小。又由于dE总是小于0(因为退火的过程是温度逐渐下降的过程),因此dE/kT < 0 ,所以P(dE)的函数取值范围是(0,1) 。随着温度T的降低,P(dE)会逐渐降低。

我们将一次向较差解的移动看做一次温度跳变过程,我们以概率P(dE)来接受这样的移动。也就是说,在用固体退火模拟组合优化问题,将内能E模拟为目标函数值 f,温度T演化成控制参数 t,即得到解组合优化问题的模拟退火演算法:由初始解 i 和控制参数初值 t 开始,对当前解重复“产生新解→计算目标函数差→接受或丢弃”的迭代,并逐步衰减 t 值,算法终止时的当前解即为所得近似最优解,这是基于蒙特卡罗迭代求解法的一种启发式随机搜索过程。退火过程由冷却进度表(Cooling Schedule)控制,包括控制参数的初值 t 及其衰减因子Δt 、每个 t 值时的迭代次数L和停止条件S。

 

总结起来就是:

  • 若f( Y(i+1) ) <= f( Y(i) )  (即移动后得到更优解),则总是接受该移动;
  • 若f( Y(i+1) ) > f( Y(i) )  (即移动后的解比当前解要差),则以一定的概率接受移动,而且这个概率随着时间推移逐渐降低(逐渐降低才能趋向稳定)相当于上图中,从B移向BC之间的小波峰时,每次右移(即接受一个更糟糕值)的概率在逐渐降低。如果这个坡特别长,那么很有可能最终我们并不会翻过这个坡。如果它不太长,这很有可能会翻过它,这取决于衰减 t 值的设定。

 

关于普通Greedy算法与模拟退火,有一个有趣的比喻:

  • 普通Greedy算法:兔子朝着比现在低的地方跳去。它找到了不远处的最低的山谷。但是这座山谷不一定最低的。这就是普通Greedy算法,它不能保证局部最优值就是全局最优值。
  • 模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向低处,也可能踏入平地。但是,它渐渐清醒了并朝最低的方向跳去。这就是模拟退火。

     

      通过一个实例来编程演示模拟退火的执行。特别地,我们这里所采用的实例是著名的“旅行商问题”(TSP,Traveling Salesman Problem),它是哈密尔顿回路的一个实例化问题,也是最早被提出的NP问题之一。

     TSP是一个最常被用来解释模拟退火用法的问题,因为这个问题比较有名,我们这里不赘言重述,下面直接给出C++实现的代码:

复制代码
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <stdio.h>
#include <time.h>
#include <math.h>#define N     30      //城市数量
#define T     3000    //初始温度
#define EPS   1e-8    //终止温度
#define DELTA 0.98    //温度衰减率#define LIMIT 1000   //概率选择上限
#define OLOOP 20    //外循环次数
#define ILOOP 100   //内循环次数using namespace std;//定义路线结构体
struct Path
{int citys[N];double len;
};//定义城市点坐标
struct Point
{double x, y;
};Path bestPath;        //记录最优路径
Point p[N];       //每个城市的坐标
double w[N][N];   //两两城市之间路径长度
int nCase;        //测试次数double dist(Point A, Point B)
{return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
}void GetDist(Point p[], int n)
{for(int i = 0; i < n; i++)for(int j = i + 1; j < n; j++)w[i][j] = w[j][i] = dist(p[i], p[j]);
}void Input(Point p[], int &n)
{scanf("%d", &n);for(int i = 0; i < n; i++)scanf("%lf %lf", &p[i].x, &p[i].y);
}void Init(int n)
{nCase = 0;bestPath.len = 0;for(int i = 0; i < n; i++){bestPath.citys[i] = i;if(i != n - 1){printf("%d--->", i);bestPath.len += w[i][i + 1];}elseprintf("%d\n", i);}printf("\nInit path length is : %.3lf\n", bestPath.len);printf("-----------------------------------\n\n");
}void Print(Path t, int n)
{printf("Path is : ");for(int i = 0; i < n; i++){if(i != n - 1)printf("%d-->", t.citys[i]);elseprintf("%d\n", t.citys[i]);}printf("\nThe path length is : %.3lf\n", t.len);printf("-----------------------------------\n\n");
}Path GetNext(Path p, int n)
{Path ans = p;int x = (int)(n * (rand() / (RAND_MAX + 1.0)));int y = (int)(n * (rand() / (RAND_MAX + 1.0)));while(x == y){x = (int)(n * (rand() / (RAND_MAX + 1.0)));y = (int)(n * (rand() / (RAND_MAX + 1.0)));}swap(ans.citys[x], ans.citys[y]);ans.len = 0;for(int i = 0; i < n - 1; i++)ans.len += w[ans.citys[i]][ans.citys[i + 1]];cout << "nCase = " << nCase << endl;Print(ans, n);nCase++;return ans;
}void SA(int n)
{double t = T;srand((unsigned)(time(NULL)));Path curPath = bestPath;Path newPath = bestPath;int P_L = 0;int P_F = 0;while(1)       //外循环,主要更新参数t,模拟退火过程
    {for(int i = 0; i < ILOOP; i++)    //内循环,寻找在一定温度下的最优值
        {newPath = GetNext(curPath, n);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 < bestPath.len)bestPath = curPath;if(P_F > OLOOP || t < EPS)break;t *= DELTA;}
}int main(int argc, const char * argv[]) {freopen("TSP.data", "r", stdin);int n;Input(p, n);GetDist(p, n);Init(n);SA(n);Print(bestPath, n);printf("Total test times is : %d\n", nCase);return 0;
}
复制代码

注意由于是基于蒙特卡洛的方法,所以上面代码每次得出的结果并不完全一致。你可以通过增加迭代的次数来获得一个更优的结果。

我们这里需要说明的是,在之前的文章里,我们用求最小值的例子来解释模拟退火的执行:如果新一轮的计算结果更前一轮之结果更小,那么我们就接受它,否则就以一个概率来拒绝或接受它,而这个拒绝的概率会随着温度的降低(也即是迭代次数的增加)而变大(也就是接受的概率会越来越小)。

但现在我们面对一个TSP问题,我们如何定义或者说如何获取下一轮将要被考察的哈密尔顿路径呢?在一元函数最小值的例子中,下一轮就是指向左或者向右移动一小段距离。而在TSP问题中,我们可以采用的方式其实是很多的。上面代码中GetNext()函数所采用的方式是随机交换两个城市在路径中的顺序。例如当前路径为 A->B->C->D->A,那么下一次路径就可能是A->D->C->B->A,即交换B和D。而在文献【3】中,作者采样的代码如下(我们截取一个片段,完整代码请参考原文):

public class Tour{... ...// Creates a random individual public void generateIndividual() { // Loop through all our destination cities and add them to our tour for (int cityIndex = 0; cityIndex < TourManager.numberOfCities(); cityIndex++) { setCity(cityIndex, TourManager.getCity(cityIndex)); } // Randomly reorder the tour Collections.shuffle(tour); } ... ... }

    可见作者的方法是把上一轮路径做一个随机的重排(这显然也是一种策略)。

TSP.data的数据格式如下,第一行的数字表示一个有多少座城市,第2至最后一行,每行有两个数字表示,城市的坐标(平面直角坐标系)。例如: 

20 80 
16 84 
23 66 
62 90 
11 9 
35 28 
最后请读者自行编译执行程序并观察分析输出的结果。

参考文献与推荐阅读材料

【1】关于哈密尔顿问题和TSP问题请参考下面两个资料以了解更多:

  • 从哈密尔顿路径谈NP问题
  • William J. Cook,迷茫的旅行商:一个无处不在的计算机算法问题,人民邮电出版社,2013

【2】上面的C++代码在下面这个两个帖子中都有给出,原作者无法考证

  • http://www.henufz.cn/bencandy.php?fid=151&id=1894    吓尿了,这居然是高中的信息学科竞赛!!!原来我们在高中就输了....

  • http://blog.csdn.net/acdreamers/article/details/10019849

【3】关于TSP问题的一个Java语言实现的源码,请参考

  • http://www.theprojectspot.com/tutorial-post/simulated-annealing-algorithm-for-beginners/6
C/C++基本语法学习 STL C++ primer



http://chatgpt.dhexx.cn/article/irWnKKhH.shtml

相关文章

优化算法——模拟退火算法

模拟退火算法原理 爬山法是一种贪婪的方法&#xff0c;对于一个优化问题&#xff0c;其大致图像(图像地址)如下图所示&#xff1a; 其目标是要找到函数的最大值&#xff0c;若初始化时&#xff0c;初始点的位置在 C C C处&#xff0c;则会寻找到附近的局部最大值 A A A点处&a…

模拟退火算法详细讲解(含实例python代码)

模拟退火算法详细讲解&#xff08;含实例python代码&#xff09; &#xff08;一&#xff09;模拟退火算法简介&#xff08;二&#xff09;模拟退火算法原理&#xff08;三&#xff09;退火过程中参数控制&#xff08;四&#xff09;算法步骤&#xff08;五&#xff09;实例分析…

模拟退火算法(SA)

一、算法简介 模拟退火算法(SA) 一种模拟物理退火过程而设计的优化算法 初始处于一个高温状态下&#xff0c;然后逐渐退火&#xff0c;在每个温度下慢慢冷却&#xff0c;最终达到物理基态&#xff08;相当于算法找到最优解&#xff09; 求解TSP问题(旅行商问题)、最值问题、全…

模拟退火算法(Simulated Annealing,SA)

这是一篇关于模拟退火算法的总结博客&#xff0c;包括算法思想&#xff0c;算法步骤&#xff0c;Python实现&#xff0c;MATLAB实现&#xff0c;算法进阶等。 目录 1 算法思想2 算法步骤3 SA解函数最小值&#xff08;python实现&#xff09;4 SA解旅行商问题&#xff08;MATLA…

退火算法(Annealing)简介与详解

模拟退火算法&#xff08;Simulated Annealing&#xff0c;SA&#xff09; 秒懂爬山算法&#xff08;Hill Climbing&#xff09;退火算法 详解算法来源数学推导来源&#xff1a;metropolis准则&#xff08;蒙特卡洛准则&#xff09; 算法流程算法优劣例题 秒懂 爬山算法&#…

Instrumentation框架分析及其使用

本文旨在从Android系统源码出发,简单梳理Instrumentation框架的行为及逻辑结构,供有兴趣的同学一起学习 从am instrument谈起 am instrument命令的执行 我们知道,命令行运行Android测试的命令是adb shell am instrument,这个命令是如何调起我们的测试代码来进行测试的呢,…

android.app.instrumentation解析

已经在Android SDK中学习了很多关于JUnit的内容&#xff0c;但是感觉一直有几个问题没有解决&#xff08;不知道大家是否有同样的感受&#xff09;JUnit的测试都自动化的&#xff0c;完全是不需要任何操作的&#xff0c;有2个问题我一直都还没有找到答案&#xff0c;这2个问题如…

Java探针 Instrumentation

背景&#xff1a;假如我们想打印出某些系统->某些类->某些方法的执行耗时&#xff0c;方式有很多&#xff0c;但是想要无侵入的做到这一点&#xff0c;只有Java探针一种方式。这也是很多调用链系统依赖的技术基础。 什么是Java探针 通俗来讲&#xff0c;就是Java提供的一…

opentelemetry-java-instrumentation翻译

原文地址&#xff1a;原文 OpenTelemetry Instrumentation for Java 这个项目提供了一个Java agent JAR&#xff0c;这个jar可以attached to任何Java8的应用上&#xff0c;动态地注入&#xff08;inject&#xff09;字节码从大量的流行库和框架&#xff08;popular libraries …

Instrument

使用Instruments 里面的Automation&#xff0c;可以对iOS进行自动化测试。 参考这篇文章&#xff1a;http://www.codeproject.com/KB/iPhone/UI_Automation_Testing.aspx 我用的是xcode4.2。 在这里下载修改好的项目&#xff0c;xcode4.2下用的&#xff1a;http://download.csd…

android Instrumentation

Android提供了一系列强大的测试工具&#xff0c;它针对Android的环境&#xff0c;扩展了业内标准的JUnit测试框架。尽管你可以使用JUnit测试Android工程&#xff0c;但Android工具允许你为应用程序的各个方面进行更为复杂的测试&#xff0c;包括单元层面及框架层面。 Android测…

Instrumentation 应用简介

引用&#xff1a; java-instrumentation 引用&#xff1a;Instrumentation 新功能 简介 java Instrumentation指的是可以用独立于应用程序之外的代理&#xff08;agent&#xff09;程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态&#…

AndroidStudio单元测试——instrumentation

前言&#xff1a;这几天老大要我搞代码自动测试&#xff0c;eclispe的已经解决了&#xff0c;可他们都是用android studio&#xff0c;所以要在android studio 上重新试验&#xff0c;这个有难度啊&#xff0c;android studio国内资料极少&#xff0c;更不要说单元测试了。goog…

Android Instrumentation 简介

Instrumentation 简介 APIs && Source code 官方APIs地址(需要翻墙)Source code Instrumentation 特点 该框架基于JUnit&#xff0c;因此既可以直接使用Junit 进行测试&#xff0c;也可以使用Instrumentation 来测试Android 组件其为Android 应用的每种组件提供了测…

冲突域和广播域?

如何理解冲突域和广播域&#xff1f; 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Switch。交…

广播域与冲突域

广播域与冲突域 一个集线器&#xff08;中继器&#xff09;连接的网络成为冲突域&#xff0c;因为每台主机都连接在了同一条线路上&#xff0c;所以传送信息时会冲突。 冲突域是基于第一层(物理层) 而交换机的本质是一个多借口网桥&#xff0c;就是说由交换机组成的网络中&…

冲突域广播域

网络互连设备可以将网络划分为不同的冲突域、广播域。但是&#xff0c;由于不同的网络互连设备可能工作在OSI模型的不同层次上。因此&#xff0c;它们划分冲突域、广播域的效果也就各不相同。如中继器工作在物理层&#xff0c;网桥和交换机工作在数据链路层&#xff0c;路由器工…

广播域和冲突域问题

该图中有几个冲突域几个广播域&#xff1f; 解答&#xff1a; 1、两个广播域&#xff0c;七个冲突域。 这样的&#xff1a;集线器属于物理层&#xff0c;所有接口同属于一个冲突域、一个广播域&#xff1b;交换机属于数据链路层&#xff0c;每个接口是一个单独的冲突域…

冲突域和碰撞域的理解

如何理解冲突域和广播域&#xff1f; 转载 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Swi…

如何计算冲突域和广播域-图解分析

如何理解冲突域和广播域?冲突域:【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。【分层】基于OSI的第一层(数据链路层)物理层【设备】第二层设备能隔离冲突域,比如Switch。交换机能缩小冲突域的范围,交…