模拟退火
模拟退火
模拟退火时有三个参数,分别是初始温度T0 、降温系数Δ (delta)、终止温度 Tk 。
其中,T0 是一个比较大的数,Δ 是一个略小于 1 的正数,Tk 是一个略大于 0 的正数。
我们先让温度 T=T0 ,然后每次降温时T=T⋅Δ ,直到 T≤Tk 为止。
我们探讨一下SA的玄学调参。
Q:答案不是最优的怎么办?
A:有以下几种方法:调大Δ (delta)、调大T0 、调小Tk ,以及多跑几遍SA。
当您的程序跑的比较快时,可以选择多跑几遍SA,或者调大 Δ ,从而增大得到最优解的概率。
调大T0 和调小Tk 也可以,而且时间并不会增大太多。
大致过程如下:
可以看出,随着温度的降低,解逐渐稳定下来,并逐渐集中在最优解附近。
如何生成新解
- 坐标系内:随机生成一个点,或者生成一个向量。
- 序列问题: random\_shufflerandom_shuffle 或者随机交换两个数。
- 网格问题:可以看做二维序列,每次交换两个格子即可。
P1337 [JSOI2004]平衡点 / 吊打XXX
模板代码:
1 #include <bits/stdc++.h> 2 #define delta 0.996 // 徐徐降温 3 4 using namespace std; 5 6 struct node { 7 int x,y,w; 8 }object[2005];// 存物体的坐标和重量 9 int n; 10 double ansx,ansy,answ; // 最终答案 11 12 double energy( double x, double y ){//根据题意变化, 能量总和越小越稳定( 即越接近正确答案 ) 13 double r=0,dx,dy; 14 for ( int i=1; i<=n; i++ ) { 15 dx = x-object[i].x; 16 dy = y-object[i].y; 17 r += sqrt(dx*dx+dy*dy)*object[i].w; 18 } 19 return r; 20 } 21 22 void sa(){ 23 double t = 3000; // 温度足够高 24 while ( t>1e-15 ) { 25 double ex = ansx + (rand()*2-RAND_MAX)*t; // 随机产生新答案. 26 double ey = ansy + (rand()*2-RAND_MAX)*t; 27 double ew = energy(ex,ey); 28 double de = ew - answ; 29 if ( de<0 ) { // 新答案能量低,更稳定 30 ansx = ex; 31 ansy = ey; 32 answ = ew; 33 } 34 else if ( exp(-de/t)*RAND_MAX>rand() ) { // 否则根据多项式概率接受 35 ansx = ex; 36 ansy = ey; 37 } 38 t *= delta; 39 } 40 } 41 42 void solve() // 多跑几遍退火,增加得到最优解的概率 43 { 44 sa();sa();sa();sa(); 45 } 46 47 int main() 48 { 49 cin >> n; 50 for ( int i=1; i<=n; i++ ) { 51 scanf("%d %d %d",&object[i].x,&object[i].y,&object[i].w); 52 ansx += object[i].x; 53 ansy += object[i].y; 54 } 55 ansx/=n; ansy/=n; // 以平均值作为初始答案 56 answ = energy(ansx,ansy); 57 solve(); 58 printf("%.3f %.3f\n",ansx,ansy); 59 60 return 0; 61 }