AcWing算法进阶课 搜索 模拟退火法

涉及到最优化问题都可以尝试使用模拟退火法,比如DP问题、计算几何问题、贪心问题。

模拟退火法是一个经过优化的随机化算法,有很大概率找到最优解。

在使用这个算法的时候,题目应该具有一定的连续性。(函数的连续性比较强,有多峰的话,都可以尝试一下)

温度(步长) : 初始温度(由题目的搜索空间范围确定),终止温度(温度降到什么时候程序会停下来),衰减系数(取一个比较接近1的从0到1的小数,采用指数型衰减,这个参数需要自己手调)

每次随机的时候,从当前点开始,向它一个区域内随机。温度越大,我们所考虑的当前点的邻域(范围)就越大,

温度是一个不断降低的过程,每次随机的区间是不断缩小的,最终会固定住点的位置。

 以取最小值点为例:

随机选择一个点:f(新点) - f(当前点) = ΔE

情况1:ΔE < 0 ,则跳到新点(能量值降低了,意味着当前点一定不是最低点,当前点一定不可选,则跳到新点)

情况2:ΔE > 0, 则以一定概率跳到新点,同时ΔE的值越小,跳过去的概率就越大,Δ的值越大,跳过去的概率就越小。

(如果Δ大于0,说明我们找的新点的能量值大于当前点,没有当前点能量值低,但是考虑到存在多个极值点,而我们找的是最小值,所以,我们以一定的概率跳到新的点,如果ΔE的值越小,说明两个点的能量值越接近,那么我们跳到新点的概率就越大,如果新点的能量比当前点的能量大了很多的话,那基本上就不需要跳过去了,也就是以很小的概率跳过去,取值的经验为:e^(-ΔE/T)(如果ΔE的值小于0,那么概率就是一个大于1的数,可以直接跳过去,若ΔE的值大于0,那么我们的取值就是一个0~1之间的数,而且如果E越大的话,我们的取值就会越小。

我们在操作的过程中,温度会不断降低,那么每次随机的区域就会越来越小,温度越低,同时往外乱跳的概率就会越小。最终,当我们的温度降到了终止温度的时候,程序就可以停下来了。

模拟退火法可以解决连续性较强的函数多峰问题的,当然也有可能最后只收敛到了一个局部最优解,为了降低收敛到局部最优解的概率,模拟退火整个的过程一般会迭代若干次,比如做100次,做100次意思是我们随机100个初值点。

模拟退火法流程:

void simulate_anneal()
{
  随意一个初值点
  for (T = 初始温度; T > 终止温度; T = T * 终止系数)
  {
    在当前点周围随机一个点
    Δ = f(新点) - f(当前点);
    if () 
  }
}

模拟退火法的常用的一些技巧:

模拟退火运行时间比较随机,不太好估计,我们可以使用卡时的方法

while (clock() < 0.8)

如果TLE的话,可以调节终止温度,和衰减系数

 

#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 110;
int n;
PDD q[N];
double ans = 1e9;
//随机一个从l到r的浮点数的函数
double rand(double l, double r)
{
//随机数除以随机数的最大值,得到0到1的数,然后乘上步长加上左端点得到l到r的一个随机数
return (double)rand() / RAND_MAX * (r - l) + l;
}
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
//求一下全局的总的距离和是多少
double calc(PDD p)
{
double res = 0;
for (int i = 0; i < n; i ++ )
res += get_dist(p, q[i]);
ans = min(ans, res);
return res;
}
void simulate_anneal()
{
PDD cur(rand(0, 10000), rand(0, 10000));
for (double t = 10000; t >= 1e-4; t = t * 0.996)
{
//在当前点内随机一个新点
PDD np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t));
double delta = calc(np) - calc(cur);
if (exp(-delta / t) > rand(0, 1)) cur = np;
}
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%lf%lf", &q[i].x, &q[i].y);
for (int i = 0; i < 100; i ++ ) simulate_anneal();
printf("%.0lf\n", ans);
return 0;
}

  

posted @ 2021-06-18 22:34  rookie161  阅读(308)  评论(0编辑  收藏  举报