模拟退火详解
前言
模拟退火是一个极其玄学且通用的算法,在题目中虽不能保证拿满分但部分分至少是有的,适用于想不出正解却还想得分的人(比如我)。
正文
模拟退火的原理是源自金属退火,金属的温度不断下降,逐渐冷却。
你初始有一个温度值 \(T_{1}\) ,建议为一个较大值,和一个最终温度值 \(T_{0}\) ,建议为一个略大于0的数,还有一个降温系数 \(s\) 为一个略小于1的数。
最初你将 \(T=T_{1}\) ,接着随机寻找一个数字 \(begin\) ,将他视为当前最优解,每次查找都在一定范围内再随机寻找一个数 \(now\) ,并计算他们的差值 \(\Delta=f(now)-f(begin)\) ,显然有两种情况 \(\Delta\leq0\) 和 \(\Delta>0\) ,第一种情况很好想,肯定是当前的数比之前的好,直接转移就好了,但是我们现在要重点考虑第二种情况,直接搬公式:
\(P\) 是啥?是我们选这个数的概率,简单来说就是当它大于零的时候肯定是没有之前的解优秀的,但我们还是要以 \(P\) 概率来选取,因为这样可以扩展我们查找的范围最后能找出查找更优解,至于为什么长这样接下来再解释。最后再将 \(T=Ts\) ,使温度下降。整个模拟过程如图:

可以看到,随着温度的降低,答案越来越难以被更改,最后的答案基本是最优解。
至于为什么要以 \(e^{-\Delta/T}\) ,为概率呢?
- \(e^{-x}\) 可以保证 \(P\in[0,1]\) 。
- 随着 \(T\) 的增加 \(P\) 也会增加,越来越难以接受新答案,答案越稳定。
因此使用上述表达式是应该比较合适的,当然如果你有更好的公式也是可以的。
例题
给定 \(n\) 个正整数,求出其中的最大值。用这么简单的题让大家理解一下模拟退火的基本原理。
code:
#include <bits/stdc++.h>
using namespace std;
int n,a[10005],tans,bans;
double bt=1000,et=1e-5,lt=0.995;//bt为初始温度,et为结束温度,lt为温度系数
int main()
{
srand(time(0));
srand(rand());
cin>>n;
for (int i=1; i<=n; i++)
{
cin>>a[i];
tans=max(a[i],tans);
}
int ans=rand()%n+1;
int na=a[ans];//初始随机选择一个数作为最优解
while (bt>et)
{
int g=rand()%(min(ans+int(bt)*100,n)-max(ans-int(bt)*100,1)+1)+max(ans-int(bt)*100,1);//根据当前温度来选择合适的遍历范围
int de=na-a[g];
if (de<=0)//新的解更优直接更新
{
bans=a[g];
na=a[g];
ans=g;
}
else if(double(rand())<exp(double(-de)/bt)*RAND_MAX) //差的解也有一定概率接受
{
na=a[g];
ans=g;
}
bt*=lt;//退火
}
cout<<tans<<" "<<bans;
}
调整
正如我前面所说,模拟退火是一个玄学算法,你经常会遇到答案不对的地方,下面有几种方法可以让你的答案更加正确,前提是不会 TLE(雾。
- 调大 \(s\) ,如果调大了降温系数,那么模拟退火会多执行很多次
- 在一个温度内多遍历几个点,更易找出最大值
- 简单粗暴直接打多次模拟退火,取最优解
- 进行分块模拟退火,这样更容易取最优
总结
由上大家可以看出,模拟退火是基于随机化的算法,本身不具有正确性,所以说该打正解就打正解,实在不行再用模拟退火。

浙公网安备 33010602011771号