模拟退火算法详解

博客食用更佳bossbaby's blog

模拟退火算法(Simulate Anneal,SA)是一种通用概率演算法,用来在一个大的搜寻空间内找寻命题的最优解。模拟退火是由\(S.Kirkpatrick, C.D.Gelatt\)\(M.P.Vecchi\)在1983年所发明的。\(V.Cern\)\(yacute\)在1985年也独立发明此演算法。模拟退火算法是解决\(TSP\)问题的有效方法之一。

\(TSP\)是啥我们等会再解释(就是一道例题,给个link:\(TSP\),有兴趣的童鞋可以先看着)

模拟退火的出发点是基于物理中固体物质的退火过程与一般组合优化问题之间的相似性。模拟退火算法是一种通用的优化算法,其物理退火过程由加温过程、等温过程、冷却过程这三部分组成。
---引自《百度百科》

关于物理呢,本蒟蒻就不做过多的解释了.
算法原理就是一个物体,在降温的过程中,根据热力学规律并结合计算机对离散数据的处理,在温度为\(T\)时,出现能量差为\(\Delta E\)的降温的概率为\(P(\Delta E)\)这个\(P\)函数我们在下一个部分给大家解释.

算法解析

(现在我们要求这个函数图像的最小值)
附图:
模拟退火

要开始写这个算法,我们就要引入一个叫做\(Metropolis\)接受准则的玩意儿了.
(英语大佬们不要把它当成那个大都会了...)
\(P=1(\Delta E>0)\)
\(P=exp(-\frac{\Delta E}{kT})(\Delta E<0)\)
显然如果 $ \Delta E$ 为正的话转移是一定会成功的, 但是对于 \(\Delta E < 0\) 我们则以上式中计算得到的概率接受这个新解.
然后我们维护温度 \(T\) 即可. 这里我们有三个参数: 初温 \(T_b\) , 降温系数 \(D\) , 终温 \(T_e\)
一般 \(T_b\) 是个比较大的数,取\(1000000\), \(D\) 是个接近 \(1\) 但是小于 \(1\) 的值,一般取\(0.97\), \(T_e\) 是个接近 \(0\) 的正值, 一般取\(1^{-14}\)\(1e-14\).
首先让温度 \(T=T_b\) , 然后进行一次转移尝试, 然后让 \(T*=D\).
\(T<T_e\) 时模拟退火过程结束, 当前解作为最优解.

转移

转移是整个模拟退火算法的重头戏.它通过当前温度进行一定程度的扰动,产生新解.其实扰动也并不复杂,当时学习这种算法时就不懂扰动是什么.它其实就是当前温度\(T_0\),乘以一个随机数\(R\)加在原解上得到新解.很多同学可能看不懂.现在我们假设估价函数为\(f(x)\),\(x\)为原解,我们要让函数值最小.那么新解就是\(x_1=x+T_0*R(R \in [-1,0) \cup (0,1])\)那么\(\Delta E=f(x)-f(x_1)\)此处千万不要把\(x\)\(x_1\)记反了,要不然这样使用\(Metrospolis\)准则就会出错.我们在一次模拟退火完成后,可以再多来几次怎么
生成R呢,有些同学可能会有问题,具体我们可以用

(double)(rand()-rand())/RAND_MAX

记得要用

srand(time(NULL))

初始化

例题

现在,你应该已经了解了模拟退火算法了
这里有几道例题

  • \(TSP\)问题

没错,就是文章开头提到的那个\(TSP\)问题
具体请百度

  • 平衡点

具体自己可以在洛谷上看
附带代码

#include<bits/stdc++.h>
#define LD long double
using namespace std;

/***** 模拟退火控制 *****/
const LD D=0.97,EPS=1e-14;
int times=10;
/***** ============ *****/

int n;
int w[1010],x[1010],y[1010];
LD bx=0,by=0;
LD cur_ans,new_ans,best;
inline LD Rand(){                //产生-1到1闭区间(除去0)的随机数
    return (LD)(rand()-rand())/((LD)RAND_MAX);
}
LD calc(LD cx,LD cy){        //估价函数
    LD ret=0;
    for(int i=1;i<=n;i++){
        LD dx=cx-x[i],dy=cy-y[i];
        ret+=sqrt(dx*dx+dy*dy)*w[i];
    }
    return ret;
}
int main(){
    srand(time(NULL));
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x[i]>>y[i]>>w[i];
        bx+=x[i];by+=y[i];
    }
    bx/=n;by/=n;                  //初始解
    best=cur_ans=calc(bx,by);
    while(times--){              //控制多次退火
        cur_ans=best;
        LD cx=bx,cy=by;
        for(LD T=1000000;T>EPS;T*=D){       //模拟退火
            LD nx=cx+T*Rand(),ny=cy+T*Rand();
            new_ans=calc(nx,ny);
            if(best>new_ans){                //更新最优解
                best=new_ans;
                bx=nx,by=ny;
            }
            if(cur_ans>new_ans||exp((cur_ans-new_ans)/T)>(LD)rand()/RAND_MAX){               //更新当前解并转移
                cur_ans=new_ans;
                nx=cx,ny=cy;
            }
        }
    }
    cout<<fixed<<setprecision(3)<<bx<<" "<<by<<endl;
    return 0;
} 
posted @ 2019-05-30 20:39  bossbaby  阅读(12947)  评论(3编辑  收藏  举报