抽象的模拟退火算法(Simulated Annealing,SA)学习笔记
介绍#
首先希望大家点赞评论,谢谢!
模拟退火是一种随机算法,由为了使模拟退火能被使用,必须满足答案只与顺序有关。
这是一种随机性算法,适用于普通爆搜用不了的情况。其抽象在要不断的取随机数,所以这种算法最核心的地方在于乱取随机数,直到达到最优解。
听上去很抽象是吗?但是它的随机也是有规律的随机,我们将不断的重复退火的过程,单次退火后随机转移的概率会越来越低。这就是 SA 从不稳定到稳定的过程。
算法过程#
我们要有个初始温度 ,热力学满分的童鞋都知道, 越高的时候,粒子也就越不稳定。
OI-WIKI 上的描述稍显专业,个人觉得本文所述更易理解一点。大家也可以参考
以排列最优问题为例,SA 的单次迭代,会随机选择两个初始排列的数,交换它们。调用 get
函数做计算:有两种情况,在较之前已有情况更优时,直接修改答案,并且保留已经交换的数。如果不优,那么以一定的概率保持之前修改了的状态,这个概率是多少呢?我们设 为此次的答案, 为目前最优的答案。那么这里 ,即能量差,则有: 的转移概率。
单词迭代完成后, 要乘以一个小于 的固定系数,即“退火”,等待下一次迭代。
迭代到什么时候结束呢?迭代当 达到一个十分接近 的值时结束。
模拟退火模板#
单说可能会有点抽象,一下是模板代码:
double Rand() {
// 可以获得小于等于 1 的随机数
return (double)rand() / RAND_MAX;
}
void sa() {
double t = 1000; // 可以根据需要修改
while (t > 1e-12) { // 可以根据需要修改
// 随机转移
double tmp = get(); // 自设的计算函数
if (tmp < ans) {
ans = tmp; // 默认转移,并设 ans 为目前最优解
} else if (exp((ans - tmp) / t) > Rand()){
// 随机下来无法转移,撤销转移
}
t *= 0.996; // 可以根据需要修改
}
}
卡时技巧#
为了保证答案准确性,一般要多做很多次 SA,以下代码可以保证不超时的情况下达到正确率最大化:
while((double)clock() / CLOCKS_PER_SEC <= time_limit) sa();
// time_limit 为题目限时,建议预留 100ms - 200ms,防止出现超时
习题#
最近在做油滴扩展,本是一道可以暴力水过的题,但还是给大家看一下代码,Luogu P1378:
#include <bits/stdc++.h>
#define rty printf("Yes\n");
#define RTY printf("YES\n");
#define rtn printf("No\n");
#define RTN printf("NO\n");
#define rep(v,b,e) for(int v=b;v<=e;v++)
#define repq(v,b,e) for(int v=b;v<e;v++)
#define rrep(v,e,b) for(int v=b;v>=e;v--)
#define rrepq(v,e,b) for(int v=b;v>e;v--)
#define stg string
#define vct vector
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
void solve() {
}
const double pi = 3.141592653589793;
const double e = 2.718281;
int n, zx, yx, sy, xy;
int objx[10], objy[10];
double radius[10];
double ans = 0;
double dis(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}
double Rand() {
return (double)rand() / RAND_MAX;
}
double get() {
double sum = 0;
rep(i, 1, n) {
radius[i] =
min(
{
abs(yx - objx[i]),
abs(zx - objx[i]),
abs(sy - objy[i]),
abs(xy - objy[i])
}
);
repq(j, 1, i) {
double di = dis(objx[i], objy[i], objx[j], objy[j]);
if (di >= radius[j]) {
radius[i] = min(radius[i], di - radius[j]);
} else radius[i] = 0;
}
sum += radius[i] * radius[i] * pi;
}
return abs(yx - zx) * abs(sy - xy) - sum;
}
void sa() {
double t = 1000;
while (t > 1e-12) {
int a = rand() % n + 1;
int b = rand() % n + 1;
swap(objx[a], objx[b]); swap(objy[a], objy[b]);
double tmp = get();
if (tmp < ans) {
ans = tmp;
} else if (pow(e, (ans - tmp) / t) < Rand()){
swap(objx[a], objx[b]); swap(objy[a], objy[b]);
}
t *= 0.996;
}
}
main() {
// int t; cin >> t; while (t--) solve();
srand(time(NULL));
cin >> n;
cin >> zx >> sy >> yx >> xy;
if (yx > zx) swap(yx,zx);
if (sy > xy) swap(sy, xy);
rep(i, 1, n) cin >> objx[i] >> objy[i];
ans = get();
while((double)clock() / CLOCKS_PER_SEC <= 0.9) sa();
printf("%.0lf\n", ans);
return 0;
}
还有一些习题,详见 OI-WIKI。
最后#
感谢大家的阅读,希望能点赞评论支持一下,谢谢!
作者:2044-space-elevator
出处:https://www.cnblogs.com/2044-space-elevator/articles/18350551
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战