AcWing算法进阶课 搜索 模拟退火法
涉及到最优化问题都可以尝试使用模拟退火法,比如DP问题、计算几何问题、贪心问题。
模拟退火法是一个经过优化的随机化算法,有很大概率找到最优解。
在使用这个算法的时候,题目应该具有一定的连续性。(函数的连续性比较强,有多峰的话,都可以尝试一下)
温度(步长) : 初始温度(由题目的搜索空间范围确定),终止温度(温度降到什么时候程序会停下来),衰减系数(取一个比较接近1的从0到1的小数,采用指数型衰减,这个参数需要自己手调)
每次随机的时候,从当前点开始,向它一个区域内随机。温度越大,我们所考虑的当前点的邻域(范围)就越大,
温度是一个不断降低的过程,每次随机的区间是不断缩小的,最终会固定住点的位置。
以取最小值点为例:
随机选择一个点:f(新点) - f(当前点) = ΔE
情况1:ΔE < 0 ,则跳到新点(能量值降低了,意味着当前点一定不是最低点,当前点一定不可选,则跳到新点)
情况2:ΔE > 0, 则以一定概率跳到新点,同时ΔE的值越小,跳过去的概率就越大,Δ的值越大,跳过去的概率就越小。
(如果Δ大于0,说明我们找的新点的能量值大于当前点,没有当前点能量值低,但是考虑到存在多个极值点,而我们找的是最小值,所以,我们以一定的概率跳到新的点,如果ΔE的值越小,说明两个点的能量值越接近,那么我们跳到新点的概率就越大,如果新点的能量比当前点的能量大了很多的话,那基本上就不需要跳过去了,也就是以很小的概率跳过去,取值的经验为:e^(-ΔE/T)(如果ΔE的值小于0,那么概率就是一个大于1的数,可以直接跳过去,若ΔE的值大于0,那么我们的取值就是一个0~1之间的数,而且如果E越大的话,我们的取值就会越小。
我们在操作的过程中,温度会不断降低,那么每次随机的区域就会越来越小,温度越低,同时往外乱跳的概率就会越小。最终,当我们的温度降到了终止温度的时候,程序就可以停下来了。
模拟退火法可以解决连续性较强的函数多峰问题的,当然也有可能最后只收敛到了一个局部最优解,为了降低收敛到局部最优解的概率,模拟退火整个的过程一般会迭代若干次,比如做100次,做100次意思是我们随机100个初值点。
模拟退火法流程:
void simulate_anneal() { 随意一个初值点 for (T = 初始温度; T > 终止温度; T = T * 终止系数) { 在当前点周围随机一个点 Δ = f(新点) - f(当前点); if () } }
模拟退火法的常用的一些技巧:
模拟退火运行时间比较随机,不太好估计,我们可以使用卡时的方法
while (clock() < 0.8)
如果TLE的话,可以调节终止温度,和衰减系数
#include <bits/stdc++.h> #define x first #define y second using namespace std; typedef pair<double, double> PDD; const int N = 110; int n; PDD q[N]; double ans = 1e9; //随机一个从l到r的浮点数的函数 double rand(double l, double r) { //随机数除以随机数的最大值,得到0到1的数,然后乘上步长加上左端点得到l到r的一个随机数 return (double)rand() / RAND_MAX * (r - l) + l; } double get_dist(PDD a, PDD b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } //求一下全局的总的距离和是多少 double calc(PDD p) { double res = 0; for (int i = 0; i < n; i ++ ) res += get_dist(p, q[i]); ans = min(ans, res); return res; } void simulate_anneal() { PDD cur(rand(0, 10000), rand(0, 10000)); for (double t = 10000; t >= 1e-4; t = t * 0.996) { //在当前点内随机一个新点 PDD np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t)); double delta = calc(np) - calc(cur); if (exp(-delta / t) > rand(0, 1)) cur = np; } } int main() { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%lf%lf", &q[i].x, &q[i].y); for (int i = 0; i < 100; i ++ ) simulate_anneal(); printf("%.0lf\n", ans); return 0; }