码迷,mamicode.com
首页 > 其他好文 > 详细

浅谈模拟退火

时间:2019-01-20 11:46:33      阅读:150      评论:0      收藏:0      [点我收藏+]

标签:null   种子   rac   否则   eof   href   mem   swap   模拟退火   

蒟蒻最近学习了模拟退火,与各位巨佬来分享一下自己的见解,不足之处望多指教

浅谈模拟退火

Part 1:何为模拟退火

首先,模拟退火=模拟 退火,它本质上是对冶金学中退火过程的一种模拟.

在某些问题中,我们可以把问题抽象成一个函数.这个函数的最大/最小值就是问题的解

有些问题很美妙,它在问题区间内是单峰函数,直接贪心/三分即可(问题都成这样了那还要模拟退火有什么用呢?)

但是有些问题比较鬼畜,它不是单峰函数,单纯的贪心算法很容易陷入局部最优解而忽略了全局最优解

那么解决办法,也就是模拟退火的思想:在求得一个新解时,如果比当前解优,那就采用新解(这个毋庸置疑)

关键是:

如果新解不如当前解,以一定的概率接受它,放弃局部最优解但是却有可能得到全局最优解

Part 2:模拟退火实现

上面所讲的模拟退火的思想相信大家都理解到了,那么模拟退火究竟如何实现呢?

按照上面所说,以一定的概率接受更"差"的解.但是这个概率是多少呢?

如果概率小了,那么你就有可能陷在局部最优解出不去了

如果概率大了,那么你就有可能放弃了好不容易找到的全局最优解

那么在模拟退火算法中,接受更"差"的解的概率随着温度的降低而降低

那么我们引入温度的概念:

$T$表示当前温度

$T_{s}$表示初始温度

$T_{t}$表示结束温度

$\Delta t \in {x;|;x\in R,0 < x < 1}$表示降温系数,每次用$T = T\Delta t$来模拟降温这个过程

那么我们先让$T = T_{s}$,每次执行$T = T\Delta t$,直到$T \leq T_{t}$为止

如果求得了一个新解,比当前解优则接受(模拟退火本质是贪心),如果不如当前解以$e^{\frac{\Delta E}{kT}} $的概率接受它,$\Delta E$为新解与当前解的差值,$T$为当前温度,$k$为随机数

技术分享图片

上图来自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\n",ans);
    return 0;
}

浅谈模拟退火

标签:null   种子   rac   否则   eof   href   mem   swap   模拟退火   

原文地址:https://www.cnblogs.com/colazcy/p/10294011.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!