啥是模拟退火
本质上是一个欧皇算法
模拟退火是一个随机算法,实际上就是优雅地碰运气。
感觉它的原理其实和二分是差不多的,就是找出一个答案,看看答案是否更优。
但是由于模拟退火并不局限于单调查找,所以它能适用于非单调的问题的求解。
怎么搞模拟退火啊?
我们看一道例题: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\n",xans,yans); 76 return 0; 77 }