Fork me on GitHub 返回顶部

模拟退火算法

算法由来

对于一个非晶体的固体,
我们可以通过将其加热到某一温度,使得其分子活化,
在徐徐降温,使其分子排布趋向有序。
我们也可以通过模拟固体降温的过程,不断的靠向最优值。

用途

这是一个用于求解一些最优化问题的随机算法。

算法流程

首先,因为这是一个随机算法,为了保险,我们多做几次。
下面我们只考虑某一次的算法过程:

  • 我们先选定一个初值(初始的答案)\(Ans_0\)
  • 我们再选定一个初始的温度\(T\)和每次的降温幅度\(D\)
  • 我们考虑每一次降温之后,生成一个新解,

对于生成一个新解,我们不太可能直接在可行域中随机,这显然不太科学。
我们只考虑当前解关于温度\(T\)的“邻域”,即变化范围和\(T\)正相关。
这也是模拟退火温度越低,越稳定的原因之一。

  • 一般上都是从当前解通过一些简单变换得到新解\(Ans_1\)

此时,我们考虑设计一个估价函数\(f(x)\)表示将\(x\)作为答案的优劣程度,
习惯上\(f(x)\)越大,\(x\)作为答案越不优秀。

  • 有了估价函数,我们就可以考虑是否接受新解了:

    1. 如果\(f(Ans_1) < f(Ans_2)\),那么直接接受。
    2. 如果\(f(Ans_1) > f(Ans_2)\),此时我们需要一定概率的接受劣解,
      借此“跳出”局部最优解,达到全局最优解,具体如下:

      根据\(Metropolis\)准则,粒子在温度\(T\)时趋于平衡的概率为\(exp(-\Delta E/(kT))\)
      其中\(\Delta E\)为当前解与新解之间的“内能差”,\(k\)\(Boltzmann\)常数,当作\(1\)就好,
      那么,我们生成一个劣解之后,就有\(exp(-\frac{f(Ans_1)-f(Ans_2)}{T})\)的概率接受它了。
      这就是温度\(T\)越低,接受劣解的概率越小,越稳定。

  • 最后我们设置一个温度下限,当温度达到这个值之后就输出答案。

  • 那么我们就可以求出近似最优解了。

模板代码(\(HDU2899\))

#include <bits/stdc++.h>  
  
using namespace std;

typedef double db;

db Y;
db func(db x, db res = 0.0) {
  res += 5.0 * x * x;
  res += 7.0 * x * x * x;
  res += 8.0 * x * x * x * x * x * x;
  res += 6.0 * x * x * x * x * x * x * x;
  return res - Y * x;
}

const db lim = RAND_MAX;
db P() { return (db)(rand()) / lim; }
db Sgn[2] = {1, -1};
db Delta(db T) { return Sgn[rand() & 1] * T * P(); }

const db eps = 1e-10, D = 0.95;
db calc() {
  db Ans_0 = P() * 100.0, F_0 = func(Ans_0);
  for(db T = 101.0; T > eps; T *= D) {
    db Ans_1 = Ans_0 + Delta(T), F_1 = func(Ans_1);
    if(Ans_1 < 0.0 || Ans_1 > 100.0) continue;
    if(F_1 < F_0 || P() < exp((F_0 - F_1) / T)) Ans_0 = Ans_1, F_0 = F_1;
  }
  return Ans_0;
}

int main() {
  srand(time(0));
  int T = 0;
  scanf("%d", &T);
  while(T--) {
    scanf("%lf", &Y);
    db Ans = 1e15;
    for(int i = 1; i <= 100; i++)
      Ans = min(Ans, func(calc()));
    printf("%.4lf\n", Ans);
  }
  return 0;
}
posted @ 2020-04-16 15:24  tacmon  阅读(332)  评论(0编辑  收藏  举报