zoukankan      html  css  js  c++  java
  • 浅谈模拟退火

    浅谈模拟退火

    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;
    }
    
    
    
  • 相关阅读:
    测试及开发中应当要考虑的安全问题
    31、Python之会话管理cookie和session
    Scrapy爬虫框架入门
    30、Python之web框架django进阶篇
    装饰器函数
    函数初始后续与进阶
    函数初识
    文件操作
    日本语自学
    编码,集合
  • 原文地址:https://www.cnblogs.com/colazcy/p/10294011.html
Copyright © 2011-2022 走看看