• 这是世界上最玄学的算法——模拟退火学习笔记


    [Huge ext{模拟退火学习笔记} ]


    模拟退火是什么?

    模拟退火来自冶金学的专有名词退火。退火是将材料加热后再经特定速率冷却,目的是增大晶粒的体积,并且减少晶格中的缺陷。材料中的原子原来会停留在使内能有局部最小值的位置,加热使能量变大,原子会离开原来位置,而随机在其他位置中移动。退火冷却时速度较慢,使得原子有较多可能可以找到内能比原先更低的位置。

    模拟退火的原理也和金属退火的原理近似:我们将热力学的理论套用到统计学上,将搜寻空间内每一点想像成空气内的分子;分子的能量,就是它本身的动能;而搜寻空间内的每一点,也像空气分子一样带有“能量”,以表示该点对命题的合适程度。算法先以搜寻空间内一个任意点作起始:每一步先选择一个“邻居”,然后再计算从现有位置到达“邻居”的概率。

    可以证明,模拟退火算法所得解依概率收敛到全局最优解。

    演算步骤

    1. 初始化

    生成一个可行的解作为当前解输入迭代过程,并定义一个足够大的数值作为初始温度

    1. 迭代

    迭代过程是模拟退火算法的核心步骤,分为新解的产生和接受新解两部分

    1. 由一个产生函数从当前解产生一个位于解空间的新解;为便于后续的计算和接受,减少算法耗时,通常选择由当前新解经过简单地变换即可产生新解的方法,如对构成新解的全部或部分元素进行置换、互换等,注意到产生新解的变换方法决定了当前新解的邻域结构,因而对冷却进度表的选取有一定的影响。
    2. 计算与新解所对应的目标函数差。因为目标函数差仅由变换部分产生,所以目标函数差的计算最好按增量计算。事实表明,对大多数应用而言,这是计算目标函数差的最快方法。
    3. 判断新解是否被接受,判断的依据是一个接受准则,最常用的接受准则是Metropolis准则:若Δt′<0则接受S′作为新的当前解S,否则以概率exp(-Δt′/T)接受S′作为新的当前解S。
      4.当新解被确定接受时,用新解代替当前解,这只需将当前解中对应于产生新解时的变换部分予以实现,同时修正目标函数值即可。此时,当前解实现了一次迭代。可在此基础上开始下一轮试验。而当新解被判定为舍弃时,则在原当前解的基础上继续下一轮试验。

    注:模拟退火算法与初始值无关,算法求得的解与初始解状态S(是算法迭代的起点)无关;模拟退火算法具有渐近收敛性,已在理论上被证明是一种以概率1收敛于全局最优解的全局优化算法;模拟退火算法具有并行性。

    1. 停止准则

    迭代过程的停止准则:温度T降至某最低值时,完成给定数量迭代中无法接受新解,停止迭代,接受当前寻找的最优解为最终解。

    1. 退火方案

    在某个温度状态T下,当一定数量的迭代操作完成后,降低温度T,在新的温度状态下执行下一个批次的迭代操作。

    • 上述内容摘自wiki百科

    整个流程图如下:

    看不懂上面说的,怎么办?

    没事,第一次看的时候我也看不懂,因此下面我用我的理解来讲述这个欧皇(划去)算法

    和之前一样,我们通过一道题目来理解

    [[JSOI2004]平衡点 ]

    (n)个重物,每个重物系在一条足够长的绳子上。每条绳子自上而下穿过桌面上的洞,然后系在一起。X处就是公共的绳结。假设绳子是完全弹性的(不会造成能量损失),桌子足够高(因而重物不会垂到地上),且忽略所有的摩擦。问绳结X最终平衡于何处。

    注意:桌面上的洞都比绳结X小得多,所以即使某个重物特别重,绳结X也不可能穿过桌面上的洞掉下来,最多是卡在某个洞口处。

    先讲一下正解的思路:

    在平面上确定一个原点,将所有的力在这个平面上正交分解,从而求得所有力的合力的方向。

    然后使用一个半平面切割初始的凸包,形成一个新的凸包,这样操作后凸包的面积会变小,进行多次二分,直到凸包的面积达到精度以内(即小到可以看做一个点),这时任选凸包的一个顶点就是答案。

    若二分次数为(k),算法的时间复杂度为(O(kn))

    至此正解结束

    但这个正解很low,显示不出我们OIer真正的强大(暴力)之处,因此,我们选择模拟退火来AC这道题!

    代码实现

    很明显,我们先开一个结构体储存物体信息

    struct Node
    {
    	int x,y,w;
    }object[2005];
    //储存物体的坐标和重量
    

    然后我们需要一个函数来计算新的系统的能量,因为根据物理学知识,一个系统的能量之和越小则越稳定

    double energy(double x,double y)//计算系统新的能量
    {
    	double r=0,dx,dy;
    	for(register int i=1;i<=n;++i)
    	{
    		dx=x-object[i].x;
    		dy=y-object[i].y;
    		r+=sqrt(dx*dx+dy*dy)*object[i].w;
    	}
    	return r;
    }//根据物理学知识,系统能量之和越小则越稳定
    

    接着就是多次迭代退火,逼近最优值

    void sa()//simulated annealing,模拟退火
    {
    	double t=3000;//将初始温度设为3000°C
    	while(t>eps)//控制精度
    	{
    		double newx=ansx+(rand()*2-RAND_MAX)*t;
    		double newy=ansy+(rand()*2-RAND_MAX)*t;
    		double neww=energy(newx,newy);
    		double delta=neww-answ;
    		if(delta<0)//新的系统能量更小(更稳定)
    		{
    			ansx=newx;
    			ansy=newy;
    			answ=neww;
    		}
    		else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
    		{
    			ansx=newx;
    			ansy=newy;
    		}
    		t*=down;//操作完毕后降温
    	}
    }
    

    然后在主函数中多次退火就行了。

    交代码前需要注意最重要的一步——洗脸!(笑)

    模拟退火是玄学算法,因此需要多交几次才能过

    至于某脸黑的tqr……

    附上AC代码(调了很久才调出来的降温参数)

    #include<bits/stdc++.h>
    #define eps 1e-15
    using namespace std;
    const double down=0.994;
    //降温系数,缓慢降温,关于降温参数的选取……这是个玄学问题
    
    int n;
    double ansx,ansy,answ;
    //最终的答案
    
    struct Node
    {
        int x,y,w;
    }object[2005];
    //储存物体的坐标和重量
    
    double energy(double x,double y)//计算系统新的能量
    {
        double r=0,dx,dy;
        for(register int i=1;i<=n;++i)
        {
            dx=x-object[i].x;
            dy=y-object[i].y;
            r+=sqrt(dx*dx+dy*dy)*object[i].w;
        }
        return r;
    }//根据物理学知识,系统能量之和越小则越稳定
    
    void sa()//simulated annealing,模拟退火
    {
        double t=3000;//将初始温度设为3000°C
        while(t>eps)//控制精度
        {
            double newx=ansx+(rand()*2-RAND_MAX)*t;
            double newy=ansy+(rand()*2-RAND_MAX)*t;
            double neww=energy(newx,newy);
            double delta=neww-answ;
            if(delta<0)//新的系统能量更小(更稳定)
            {
                ansx=newx;
                ansy=newy;
                answ=neww;
            }
            else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
            {
                ansx=newx;
                ansy=newy;
            }
            t*=down;//操作完毕后降温
        }
    }
    
    template<class T>inline void read(T &res)
    {
        T flag=1;char c;
        while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
        while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
    }
    
    int main()
    {
        read(n);
        for(register int i=1;i<=n;++i)
        {
            read(object[i].x);ansx+=object[i].x;
            read(object[i].y);ansy+=object[i].y;
            read(object[i].w);
        }
        ansx/=n;ansy/=n;
        answ=energy(ansx,ansy);
        sa();sa();sa();sa();sa();sa();
        printf("%.3lf %.3lf",ansx,ansy);
        return 0;
    }
    
    

    UPD:我发现0.994的参数,退八次火AC率很高,但不知道为什么,dalao求解qwq

    https://home.cnblogs.com/u/tqr06/

    https://www.cnblogs.com/tqr06/p/10400144.html

  • 相关阅读:
    如何为创建大量实例节省内存?
    4-5
    4-6
    4-4
    4-3
    4-2
    3-11
    4-1
    3-10
    3-8
  • 原文地址:https://www.cnblogs.com/tqr06/p/11222569.html
Copyright © 2020-2023  润新知