浅谈模拟退火
Part 1:何为模拟退火
首先,模拟退火=模拟 退火,它本质上是对冶金学中退火过程的一种模拟.
在某些问题中,我们可以把问题抽象成一个函数.这个函数的最大/最小值就是问题的解
有些问题很美妙,它在问题区间内是单峰函数,直接贪心/三分即可(问题都成这样了那还要模拟退火有什么用呢?)
但是有些问题比较鬼畜,它不是单峰函数,单纯的贪心算法很容易陷入局部最优解而忽略了全局最优解
那么解决办法,也就是模拟退火的思想:在求得一个新解时,如果比当前解优,那就采用新解(这个毋庸置疑)
关键是:
如果新解不如当前解,以一定的概率接受它,放弃局部最优解但是却有可能得到全局最优解
Part 2:模拟退火实现
上面所讲的模拟退火的思想相信大家都理解到了,那么模拟退火究竟如何实现呢?
按照上面所说,以一定的概率接受更"差"的解.但是这个概率是多少呢?
如果概率小了,那么你就有可能陷在局部最优解出不去了
如果概率大了,那么你就有可能放弃了好不容易找到的全局最优解
那么在模拟退火算法中,接受更"差"的解的概率随着温度的降低而降低
那么我们引入温度的概念:
(T)表示当前温度
(T_{s})表示初始温度
(T_{t})表示结束温度
(Delta t in {x;|;xin R,0 < x < 1})表示降温系数,每次用(T = TDelta t)来模拟降温这个过程
那么我们先让(T = T_{s}),每次执行(T = TDelta t),直到(T leq T_{t})为止
如果求得了一个新解,比当前解优则接受(模拟退火本质是贪心),如果不如当前解以 $e^{ frac{Delta E}{T} } $ 的概率接受它,(Delta E)为新解与当前解的差值,(T)为当前温度
上图来自Wiki,从上图可以看出,随着温度的降低,解逐渐稳定下来,并集中在最优解附近
那么如何生成新解呢?
随机生成:
- 坐标系问题:对于坐标系问题,我们可以随机生成一个点
- 序列问题:(random)_(shuffle)或者随机交换两个数
一遍(SA)多半跑不出最优解,如果您是欧皇当我没说,因此我们可以多跑几遍(SA)取最优
SA本质还是贪心,复杂度低到(O(n))级别,因此跑几百次(SA)时间都可以承受
更何况我们还有(ctime)库呢,可以在时间允许的情况下一直跑(SA)
Part 3:模拟退火细节
Q:随机数种子咋办?
A:(srand(time(NULL))),或者东方某神秘八位质数
Q:总是WA?
A:可能是您太非了,也许洗把脸就AC了
当然,也可以更换随机数种子 调大(Delta) 调大(T_{s}) 调小(T_{t}) 多跑几遍SA
Q:交了两页交不过:
A:您大可以打正解
Q:时间复杂度?
A:玄学
Part 4:模拟退火实战
我们以[NOIP2017]宝藏为例
这题实际上是个序列问题,即要求打通的顺序使总代价最小
然后可以套板子了
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 16;
int g[maxn][maxn],n,m;
struct Node{
int d[maxn],deep[maxn];
Node(){
for(int i = 1;i <= n;i++)
d[i] = i,deep[i] = 0;
}
Node(const Node &rhs){
memcpy(d,rhs.d,sizeof(d));
memset(deep,0,sizeof(deep));
}
Node operator = (const Node &rhs){
memcpy(d,rhs.d,sizeof(d));
memset(deep,0,sizeof(deep));
return *this;
}
inline int solve(){//按照打通顺序求代价
int ret = 0;
deep[d[1]] = 1;//第一个打通的节点深度为1
for(int i = 2;i <= n;i++){
int temp = 0x7fffffff;
for(int j = 1;j < i;j++){//枚举由哪一个已经打通的节点打通道路
if(g[d[i]][d[j]] != 0x3f3f3f3f && deep[d[j]] * g[d[i]][d[j]] < temp)
temp = deep[d[j]] * g[d[i]][d[j]],deep[d[i]] = deep[d[j]] + 1;
}
if(temp == 0x7fffffff)return 0x7fffffff;//当前方案不可行可以提前退出了
ret += temp;
}
return ret;
}
};
inline int SA{//SA
const double max_temp = 10000.0;//初始温度
const double delta_temp = 0.98;//降温系数
double temp = max_temp;//当前温度
Node path;//打通顺序
while(temp > 0.1){
Node tpath(path);
swap(tpath.d[rand() % n + 1],tpath.d[rand() % n + 1]);//随机一个新解
double delta = tpath.solve() - path.solve();//求解
if(delta < 0)path = tpath;//如果新解更优,则接受
else if(exp(-delta / temp) * RAND_MAX >= rand())path = tpath;//否则以一定概率接受
temp *= delta_temp;
}
return path.solve();
}
int main(){
srand(19260817);//东方神秘质数
memset(g,0x3f,sizeof(g));
scanf("%d %d",&n,&m);
for(int u,v,d,i = 1;i <= m;i++){
scanf("%d %d %d",&u,&v,&d);
g[u][v] = min(g[u][v],d);
g[v][u] = min(g[v][u],d);
}//存图
int ans = 0x7fffffff;
for(int i = 1;i <= 233;i++)//跑SA,取最优值
ans = min(ans,SA());
printf("%d
",ans);
return 0;
}