这篇文章主要介绍模拟退火算法的原理以及实现

算法来源

在物理学中,当一个物体的温度很高的时候,这个物体会由高温状态转变为低温状态,而其内部的粒子也 逐渐 由无序转变为有序。这就给我们提供了设计算法的思路:模拟粒子的无序状态,即 随机化 ,而当”温度“越来越低的时候,粒子越来越有序,而我们随机化的 幅度 越来越小,最后就可以得到我们想要的最优解

典例

这里选取来自oi-wiki的一张图片来具象说明

算法实现

基本思路

由算法原理我们可以提取几个关键词:逐渐随机化幅度
由此我们可以得到算法实现的基本思路:
定义出一个 足够高的温度T (T一定要足够高!!!
随机化答案,找到更优解替换当前解
那么我们会不会因为如此而陷入"目光短浅"的僵局呢?
答案是肯定会的!
例如拥有多个峰值的函数,如果单单朝着较优解进发,就很有可能会 错失最优解
所以当我们需要以 一定概率接受较差解 ,用于跳出这种“僵局”
那么这个概率是多少呢?
科学家们以及帮我们计算好了:
eΔ/Te^{-\Delta/T}

  • Δ\Delta 为当前解和已知解的 差值的绝对值
  • TT 为当前温度

易知 $e^{-\Delta/T} \in [0 , 1] $ 且随着 $ T $ 的增大而减小
即随机化的程度越来越小

Code

选自洛谷P1337 [JSOI2004]平衡点 / 吊打XXX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<bits/stdc++.h>
using namespace std;
const int maxn=10006;
int n;
double ansx=0,ansy=0,dis=1000005;
double x[maxn],y[maxn],m[maxn];
double dist(double x1,double y1,double x2,double y2){
return (double)sqrt((double)1.0*(x1-x2)*(x1-x2)+(double)1.0*(y1-y2)*(y1-y2));
}
//计算带权费马点
double census(double x1,double y1){
double res=0;
for(int i=1;i<=n;i++) res+=(double)dist(x[i],y[i],x1,y1)*m[i];
if(res<dis) dis=res,ansx=x1,ansy=y1;
return res;
}
double Rand(){
return (double)rand()/RAND_MAX;
}
void sian(){
double t=10000000;//足够高的温度
while(t>1e-15){
double x1=ansx+t*(2*rand()-RAND_MAX);
double y1=ansy+t*(2*rand()-RAND_MAX);
double delt=census(ansx,ansy)-census(x1,y1);
//如果得到较优解就替换或概率接受较差解
if(delt>0||exp(delt/t)*RAND_MAX>rand()) ansx=x1,ansy=y1;
t*=0.997;
}
for(int i=1;i<=1000;i++){
double x1=ansx+t*(2*Rand()-1);
double y1=ansy+t*(2*Rand()-1);
census(x1,y1);
}
}
//由于是随机化,比较看脸,多刷几次
void runn(){
sian();
sian();
sian();
sian();
}
inline int read(){
int x=0,cf=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')cf=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*cf;
}
int main(){
srand(time(NULL));
n=read();
for(int i=1;i<=n;i++){
scanf("%lf%lf%lf",&x[i],&y[i],&m[i]);
ansx+=x[i],ansy+=y[i];
}
ansx=1.0*ansx/n,ansy=1.0*ansy/n;
runn();
printf("%.3lf %.3lf",ansx,ansy);
return 0;
}

技巧&细节

保证随机化精确度

需要注意的是当我们没有接受较优解或者没有以一定概率接受较差解是时,需要以变化之前的状态再次进行随机化
例如交换完一个序列中的两个数,如果我们没有接受较优解或者没有以一定概率接受较差解,就要重新交换回两个数的位置,以保证随机化以原来的轨迹进行而不偏离