模拟退火详解

前言

模拟退火是一个极其玄学通用的算法,在题目中虽不能保证拿满分但部分分至少是有的,适用于想不出正解却还想得分的人(比如我)。

正文

模拟退火的原理是源自金属退火,金属的温度不断下降,逐渐冷却。


你初始有一个温度值 \(T_{1}\) ,建议为一个较大值,和一个最终温度值 \(T_{0}\) ,建议为一个略大于0的数,还有一个降温系数 \(s\) 为一个略小于1的数。

最初你将 \(T=T_{1}\) ,接着随机寻找一个数字 \(begin\) ,将他视为当前最优解,每次查找都在一定范围内再随机寻找一个数 \(now\) ,并计算他们的差值 \(\Delta=f(now)-f(begin)\) ,显然有两种情况 \(\Delta\leq0\)\(\Delta>0\) ,第一种情况很好想,肯定是当前的数比之前的好,直接转移就好了,但是我们现在要重点考虑第二种情况,直接搬公式:

\[P=\left\{\begin{array}{ll} 1 & \Delta \leq 0 \\ e^{-\Delta / T} & \Delta >0 \end{array}\right. \]

\(P\) 是啥?是我们选这个数的概率,简单来说就是当它大于零的时候肯定是没有之前的解优秀的,但我们还是要以 \(P\) 概率来选取,因为这样可以扩展我们查找的范围最后能找出查找更优解,至于为什么长这样接下来再解释。最后再将 \(T=Ts\) ,使温度下降。整个模拟过程如图:

simulated annealing

可以看到,随着温度的降低,答案越来越难以被更改,最后的答案基本是最优解。


至于为什么要以 \(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\) ,如果调大了降温系数,那么模拟退火会多执行很多次
  • 在一个温度内多遍历几个点,更易找出最大值
  • 简单粗暴直接打多次模拟退火,取最优解
  • 进行分块模拟退火,这样更容易取最优

总结

由上大家可以看出,模拟退火是基于随机化的算法,本身不具有正确性,所以说该打正解就打正解,实在不行再用模拟退火。Lazy Bat

posted @ 2025-07-12 12:49  aqzjklo  阅读(159)  评论(1)    收藏  举报