模拟退火算法学习小记
程序员文章站
2022-07-12 13:48:08
...
*“不能退火的题答不是好题答”——THUSC2018
前言:
之前听专题的时候就听过退火有两种,一种是真退火,一种是假退火。
当时傻傻地分不清,现在终于是明白了。
问题引入:
费马点问题。
平面上有n个点,求它们的费马点。
费马点的定义为到所有点距离和最短的点。
问题剖析:
费马点是没有稳定算法去求解的。
而且费马点可能有多个。
所以我们只能用近似算法去搞。
真退火:
首先要了解爬山法。
爬山法就是每次往最优的方向走。
缺点是很容易掉坑里。
模拟退火算法以一定的概率接受更劣的方向,所以是可以从坑里爬出来的。
热力学上有这样一条公式,出现能量差的概率
k是一个常数,它和物理学有关,在信息学里我们不考虑。
T是目前的温度,温度会随着时间的流逝而降低。
因此当相同时,时间越久,走劣的概率就会越小,这非常符合正常人贪心的逻辑。
注意每次移动的距离也要随着时间的流逝而减少。
明白原理后,就是代码实现,要知道调参。
假退火:
绝对不往劣的方向走。
???
这不是爬山法吗?
“注意每次移动的距离也要随着时间的流逝而减少。”
假退火易于实现,且有些时候比真退火还强(可能是我不会调参),实在是水分切题、撵爆正解的必备算法!
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));
}
推荐阅读