模拟退火,算法如其名,真的就是个大模拟,模拟的是高温物体徐徐降温然后达到结晶态的过程,结晶态是内能最低的状态,而我们利用模拟退火,要找的也正是最值。
过程
模拟退火一般是用于在一个函数上找最值,也可以干一些奇奇怪怪的事情,这里就以函数为例子了。
既然是模拟退火,肯定要有个温度呀!所以要有一个温度 T,以及还要有个初始点 x,我们再给定一个跳跃的范围 dir(dirdirdir 因题目而异,你也可以用 TTT 来代替 dirdirdir,但个人认为 dirdirdir 根据题目手动设定比较好)就可以开始退火了。
为了方便,我们设问题是:要在一个函数上找最大值。
因为要退火,所以温度要降低,因为是徐徐降温,所以 TTT 减小得要慢。于是我们设一个 δ\deltaδ,每次使 T∗δT*\deltaT∗δ 即可。(δ\deltaδ 一般是一个 0.950.950.95 ~ 0.990.990.99 间的小数)
每次在 [x−dir,x+dir][x-dir,x+dir][x−dir,x+dir] 这个范围内随机找一个点 x′x'x′,(下文为了方便,将当前点的值设为 yyy,x′x'x′ 点的值为 y′y'y′)假如 y>y′y>y'y>y′,那么就贪心地跳过去(即 x=x′x=x'x=x′),否则,就按照一定的几率,决定是否跳过去,这个奇妙几率的计算公式是这样的:
e(y−y′)/T
e^{(y-y')/T}
e(y−y′)/T
至于这个公式怎么来的,还是去研究研究热力学吧。。
可以发现,当温度越来越低时,这个几率也会越来越小,那么在最后就有很大几率停在最优解上了。
但是为了防止最后还在往一些奇奇怪怪的地方跑,可以考虑记录沿途经过的所有点中的最优解。
并且因为这是个随机算法,所以为了保证正确率,多退几次火就好了。
代码如下:
#define delta 0.99
#define del 0.995
void fire()
{
double T=100;
double x=l+(r-l)*((double)rand()/RAND_MAX),xx=kk(x),dir=(r-l)/2;
//x为初始点,这个点在l~r范围内随机选一个,设kk()为求值函数
while(T>1e-4)
{
double y=(double)rand()/RAND_MAX*(dir*2.0)+x-dir;
if(y>r||y<l)continue;//如果越界就不要了
double yy=kk(y);
if(yy>xx)x=y,xx=yy;
else if(exp((xx-yy)/T)>(double)rand()/RAND_MAX)x=y,xx=yy;
T*=delta;dir*=del;
//dir要和T一样慢慢减小,防止搜到最大值附近之后还乱跑
if(xx>anss)ans=x,anss=xx;//记录答案
}
}