模拟退火

Posted onglublog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟退火相关的知识,希望对你有一定的参考价值。

啥是模拟退火

本质上是一个欧皇算法

模拟退火是一个随机算法,实际上就是优雅地碰运气。

感觉它的原理其实和二分是差不多的,就是找出一个答案,看看答案是否更优。

但是由于模拟退火并不局限于单调查找,所以它能适用于非单调的问题的求解。

怎么搞模拟退火啊?

我们看一道例题:P1337 [JSOI2004]平衡点 / 吊打XXX

这道题要求找出系统最终的平衡位置。

首先我们可以进行一波物理分析。

 因为系统总是朝着能量更低的状态变化的,于是我们知道,最终的平衡位置,肯定是使整个系统能量最低的位置。

那么我们就可以随机猜测这个点的位置。

这里我们利用物理中的退火来模拟这个猜测过程。

算法流程


模拟退火的原理和金属退火的原理近似:将热力学的理论套用到统计学上,将搜寻空间内每一点想像成空气内的分子;分子的能量,就是它本身的动能;而搜寻空间内的每一点,也像空气分子一样带有“能量”,以表示该点对命题的合适程度。演算法先以搜寻空间内一个任意点作起始:每一步先选择一个“邻居”,然后再计算从现有位置到达“邻居”的概率。


这是百度百科上的解释,其实如果你对退火不了解根本看不懂。。

我们直白地理解一下这个算法流程。

一开始我们先随机确定一个初态。

对这个初态,我们随机找到它的一个相邻状态。也就是直接rand一个数(要求取得到正负),然后之前取的数加上(这个数乘温度值)。

找到它的相邻状态后,判断要不要选取相邻状态。

首先发现,如果相邻状态比当前状态更优的话,肯定直接选了。

如果相邻状态比当前状态不优的话,我们以一定的概率接受这个状态。

那以怎样的概率接受呢?

经过科学家的分析,这个概率大概就是$e^{frac{△}{T}}$,其中$△$是现在状态和最优状态的差值,$T$是当前的温度。

从这个概率看,$△$越大,这个概率越大,当前温度越小,这个概率越大。

感性地分析一下。

当前温度大的时候,也就是刚开始查找的时候,我们是大范围跳动($rand()*T$很大)。这时候我们应该直接找更加优秀的状态。

当前温度小的时候,我们就在之前找到的状态周边进行小范围跳跃,这时候我们就像亡命之徒一样,不顾一切碰运气想要找到一个更好的解。所以哪怕一个解比较差,我们也都暂时接受,期望能通过这个解跳出当前的峰,找到更高的峰。

代码

下面的代码我已经写好注释了,跟着注释看就可以理解了。

技术分享图片
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int read(){
 4     char c;int num,f=1;
 5     while(c=getchar(),!isdigit(c))if(c==-)f=-1;num=c-0;
 6     while(c=getchar(), isdigit(c))num=num*10+c-0;
 7     return f*num;
 8 }
 9 struct Hole{
10     int x,y,w;
11 }h[10009];
12 //小孔 
13 int n;
14 double xans,yans,ans=1e18+7;
15 const double delta=0.996;
16 //降温系数 
17 //洛谷上0.996能通过
18 //BZOJ上0.973能通过 
19 double ene(double x,double y){
20     double sum=0;
21     for(int i=1;i<=n;i++){
22         double delx,dely;
23         delx=x-h[i].x;
24         dely=y-h[i].y;
25         sum+=(sqrt(delx*delx+dely*dely)*h[i].w);
26     }
27     return sum;
28 }
29 //计算能量 
30 void simulate_anneal(){
31     double nx=xans,ny=yans,na;
32     double t=2000;
33     //初始位置和初始温度 
34     while(t>=1e-14){
35         double xtmp=nx+(rand()*2-RAND_MAX)*t;
36         double ytmp=ny+(rand()*2-RAND_MAX)*t;
37         //现在的坐标
38         //rand()*2-RAND_MAX的作用是让随机数的取值变为(-RAND_MAX,RAND_MAX) 
39         double nowa=ene(xtmp,ytmp);
40         //现在的能量 
41         double del=nowa-ans;
42         //与最优解的能力差 
43         if(del<0){
44             xans=nx=xtmp;
45             yans=ny=ytmp;
46             ans=nowa;
47             //找到更小的,直接取了 
48         }else if(exp(-del/t)*RAND_MAX>rand()){
49             nx=xtmp;
50             ny=ytmp;
51             //以一定概率接受当前能量 
52         }
53         t*=delta;
54         //降温 
55     }
56 }
57 void SA(){
58     simulate_anneal();
59     simulate_anneal();
60     simulate_anneal();
61     //进行多次模拟退火,取最优解 
62 }
63 int main()
64 {
65     srand(1926017);
66     //随机数 
67     n=read();
68     for(int i=1;i<=n;i++){
69         h[i].x=read();
70         h[i].y=read();
71         h[i].w=read();
72     }
73     SA();
74     //模拟退火 
75     printf("%.3f %.3f
",xans,yans);
76     return 0;
77 }
View Code

 

以上是关于模拟退火的主要内容,如果未能解决你的问题,请参考以下文章

Matlab 模拟退火算法模型代码

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

C++2018华为软挑:模拟退火+贪心FF解决装箱问题

模拟退火板子

模拟退火入门——求解TSP和洛谷P2210

模拟退火解TSP问题MATLAB代码