欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

模拟退火算法学习小记

程序员文章站 2022-07-12 13:48:08
...

*“不能退火的题答不是好题答”——THUSC2018

前言:

之前听专题的时候就听过退火有两种,一种是真退火,一种是假退火。

当时傻傻地分不清,现在终于是明白了。

问题引入:

费马点问题。

平面上有n个点,求它们的费马点。
费马点的定义为到所有点距离和最短的点。

问题剖析:

费马点是没有稳定算法去求解的。

而且费马点可能有多个。

所以我们只能用近似算法去搞。

真退火:

首先要了解爬山法。

爬山法就是每次往最优的方向走。

缺点是很容易掉坑里。

模拟退火算法以一定的概率接受更劣的方向,所以是可以从坑里爬出来的。

热力学上有这样一条公式,出现能量差dE的概率P(dE)=exp(dE/(kT))

k是一个常数,它和物理学有关,在信息学里我们不考虑。

T是目前的温度,温度会随着时间的流逝而降低。

因此当dE相同时,时间越久,走劣的概率就会越小,这非常符合正常人贪心的逻辑。

注意每次移动的距离也要随着时间的流逝而减少。

明白原理后,就是代码实现,要知道调参。

假退火:

绝对不往劣的方向走。

???

这不是爬山法吗?

“注意每次移动的距离也要随着时间的流逝而减少。”

假退火易于实现,且有些时候比真退火还强(可能是我不会调参),实在是水分切题、撵爆正解的必备算法!

Code(真退火):

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#define db long double
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 1e3 + 5;

const db eps = 1e-8, delta = 0.99;

int move[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1 ,0}};
int n;

struct node {
    db x, y;
} a[N];

db dis(db x, db y, db u, db v) {
    return sqrt((x - u) * (x - u) + (y - v) * (y - v));
}

db solve(db x, db y) {
    db sum = 0;
    fo(i, 1, n) sum += dis(x, y, a[i].x, a[i].y);
    return sum;
}

db x, y;

int rand(int x, int y) {
    return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}

int main() {
    srand((unsigned) time (NULL));
    scanf("%d", &n);
    fo(i, 1, n) {
        scanf("%Lf %Lf", &a[i].x, &a[i].y);
        x += a[i].x, y += a[i].y;
    }
    x /= n; y /= n;
    for(db T = 100; T >= eps; T *= delta) {
        fo(cc, 1, 10) fo(p, 0, 3) {
            db nx = x + T * move[p][0], ny = y + T * move[p][1];
            db d1 = solve(x, y), d2 = solve(nx, ny);
            if(d2 < d1) {
                x = nx, y = ny;
            } else {
                if(exp((d1 - d2) / T) > (db) rand(1, 10000) / 10000) {
                    x = nx; y = ny;
                }
            }
        }
    }
    printf("%.2Lf", solve(x, y));
}

Code(假退火):

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#define db long double
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 1e3 + 5;

const db eps = 1e-8, delta = 0.98;

int move[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1 ,0}};
int n;

struct node {
    db x, y;
} a[N];

db dis(db x, db y, db u, db v) {
    return sqrt((x - u) * (x - u) + (y - v) * (y - v));
}

db solve(db x, db y) {
    db sum = 0;
    fo(i, 1, n) sum += dis(x, y, a[i].x, a[i].y);
    return sum;
}

db x, y;

int rand(int x, int y) {
    return (RAND_MAX * rand() + rand()) % (y - x + 1) + x;
}

int main() {
    srand((unsigned) time (NULL));
    scanf("%d", &n);
    fo(i, 1, n) {
        scanf("%Lf %Lf", &a[i].x, &a[i].y);
        x += a[i].x, y += a[i].y;
    }
    x /= n; y /= n;
    for(db T = 1000; T >= eps; T *= delta) {
        fo(cc, 1, 10) {
            fo(p, 0, 3) {
                db nx = x + T * move[p][0], ny = y + T * move[p][1];
                db d1 = solve(x, y), d2 = solve(nx, ny);
                if(d2 < d1) {
                    x = nx, y = ny;
                }
            }
        }
    }
    printf("%.2Lf", solve(x, y));
}