模拟退火算法(Simulated Annealing,SA)

        好久没有写博客了,打了很多比赛有的时候又没有补题,昨天进一步了解了模拟退火算法,在这里写下我的总结。

        初中的时候就听说过模拟退火的大名,直至上个学期还不会用模拟退火,看大佬碰见题目就说模拟退火觉得很牛,所以我也来学习一下模拟退火算法。

        在网上搜模拟退火能搜出很多概念,什么物理学上的退火,然后内能什么都出来了,我这里写下我的理解。

        模拟退火,顾名思义就是用程序来模拟一个高温度的,慢慢降温下来成稳定态的算法。而在程序里确实也是这样的,首先设置温度T很高,然后设置循环次数(循环次数由起始温度T和退火系数r组成,还与eps有关,一般在1e-12都行),一般就是用while循环

double T=4000,r=0.995;
while (T>eps)
{
    //....
}

        在这期间,我们要给当前的最优解一个扰动(这个扰动关于T,当温度很高的时候扰动很大,但是温度低的时候就趋于稳定)

double T=4000,r=0.995;
while (T>eps)
{
    double nowx=ansx+(rand()*2-RAND_MAX)*T;//随机产生新的答案 
    double nowy=ansy+(rand()*2-RAND_MAX)*T;//以JSOJ平衡点为例
    /*
        judge
    */
    T*=r;
}

        对于每次答案,与原先进行比较,设置delta=f(tmp)-f(ans),假设我们需要求一个最大值,如果delta大于(或大于等于)0的话,那么这个tmp我们接受概率就是1,这个很好理解,因为你的f(x)值比我之前搜到的还要大,那么我肯定接受,重点在下面。

        如果对于每次的tmp,delta<0,也就是比我们之前搜到的答案还小,那么我们接不接受呢?这里注意,是可能要接受的,因为万一这时候不是最优点,但是接受之后它下一次进行扰动,或者几次之后,它就能达到最高点呢;如果这个时候直接放弃,那么很可能最后的答案就只在极值而不是最值处,只会在它附近轻微扰动,这肯定是不可取的。

        所以模拟退火要求以一定概率接受,也就是如下:

         delta 前面是否加负号依据于是否是求最大值还是最小值。

         例如下列while循环的后面

while(T>eps)
{
    /*
    */
    delta=tmp-ans;
    if (delta<0) ans=tmp,x=nowx;
    else if (exp(-delta/T)*RAND_MAX>rand())//以一定概率接受这个解
        ans=tmp,x=nowx;
    T*=0.995;
}

 

简单题目例子:Problem - 2899 (hdu.edu.cn)

        给出y,求f(x)在x在[0,100]中的最小值。

        按照当时学习二分、三分的时候方法,这个题应该是那么做,先求导,再求等于0的时候就是极值点,然后求最值。

        那么这个题目用模拟退火来写的话就可以用,而且时间也不是很多。

#include<stack>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
#include<deque>
#include<vector>
#include<iostream>
#include<map>
#include<unordered_map>
#include<set>
#include<iomanip>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define ll long long
#define endl "\n"
#define debug(a) cout<<#a<<"="<<a<<endl;
#define eps 1e-15
using namespace std;
ll GCD(ll a,ll b){while(b^=a^=b^=a%=b);return a;}
const int inf=0x3f3f3f3f;
double y;
inline double f(double x)
{
    return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-y*x;
}
double T,ans,nowx,tmp,x;
inline void SA()
{
    double delta;
    while (T>eps)
    {
        nowx=(rand()*2-RAND_MAX)*T;
        while (abs(nowx)>=100) nowx/=10;  
        nowx+=x;
        if (nowx>100) nowx-=100;
        if (nowx<0) nowx+=100;
        //这里不知道如何求得在[0,100]之间的小数,就暴力求解了
        tmp=f(nowx);
        delta=tmp-ans;
        if (delta<0) ans=tmp,x=nowx;
        else if (exp(-delta/T)*RAND_MAX>rand())
            ans=tmp,x=nowx;
        T*=0.995;
    }
}
inline void Case_Test()
{
    cin>>y;
    T=4000;ans=(double)inf;
    for (int i=1;i<=5;i++) SA();
    cout<<fixed<<setprecision(4)<<ans<<endl;
}

signed main() 
{
    #ifndef ONLINE_JUDGE
		    freopen("IO\\in.txt","r",stdin);
		    freopen("IO\\out.txt","w",stdout);
            clock_t start, end;
            start = clock();
    #endif
    IOS
    srand(1e9+7);
    int _=1;
    cin>>_;
    
    while (_--)
    {
        Case_Test();
    }

    #ifndef ONLINE_JUDGE
        end = clock();
        cout << endl << "Runtime: " << (double)(end - start) / CLOCKS_PER_SEC << "s\n";
    #endif
}

        还有一个很经典的例题,待补 JSOJ平衡点

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值