模拟退火算法学习小记

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

前言:

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

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

问题引入:

费马点问题。

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

问题剖析:

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

而且费马点可能有多个。

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

真退火:

首先要了解爬山法。

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

缺点是很容易掉坑里。

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

热力学上有这样一条公式,出现能量差 d E 的概率 P ( d E ) = e x p ( d E / ( k T ) )

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

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

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

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

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

假退火:

绝对不往劣的方向走。

???

这不是爬山法吗?

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

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

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));
}

猜你喜欢

转载自blog.csdn.net/cold_chair/article/details/81007119