好久没有写博客了,打了很多比赛有的时候又没有补题,昨天进一步了解了模拟退火算法,在这里写下我的总结。
初中的时候就听说过模拟退火的大名,直至上个学期还不会用模拟退火,看大佬碰见题目就说模拟退火觉得很牛,所以我也来学习一下模拟退火算法。
在网上搜模拟退火能搜出很多概念,什么物理学上的退火,然后内能什么都出来了,我这里写下我的理解。
模拟退火,顾名思义就是用程序来模拟一个高温度的,慢慢降温下来成稳定态的算法。而在程序里确实也是这样的,首先设置温度T很高,然后设置循环次数(循环次数由起始温度和退火系数
组成,还与
有关,一般在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大于(或大于等于)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平衡点