爬山算法(Hill Climbing)
学习模拟退火前了解爬山算法是非常必要的
爬山算法依照一个很简单的贪心思路
每次在当前作为最优解的点附近随机一个新的点
比较这个新的点是否比当前更优,若是则更新
这个算法有一个比较明显的缺点,例如下图
我们想要的最终答案是A
但若当前记录的最优解是B
局部最优解B附近谷底的点又显然不能作为更优解更新答案
所以爬山算法最终得到的将有可能只是局部最优解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-by0VKprA-1600411161618)(https://s33.postimg.cc/7e178swcv/exm.png)]
模拟退火(SA——Simulated Annealing)
为了解决上述爬山算法的不足
模拟退火算法应用了以一定概率接受一个非更优解的思路
加入当前记录的最优解为B
随机到的下一个点为C
爬山算法直接否定了这个点,但模拟退火决定以一定概率去接受
这样得到最终全局最优解A的概率就大大增加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gAwnMM5c-1600411161620)(https://s33.postimg.cc/gd0gd5ve7/exm2.png)]
关于如何确定接受概率,就应用到了金属退火原理
在温度为
T
T
T的情况下
出现一次能量差为
Δ
E
\Delta E
ΔE的降温的概率为
P
(
Δ
E
)
=
e
Δ
E
k
∗
T
P(\Delta E)=e^{\frac{\Delta E}{k*T}}
P(ΔE)=ek∗TΔE
这个要如何应用到OI中呢
我们设定一个初始温度
T
0
T_0
T0和最小温度
T
m
i
n
T_{min}
Tmin,以及降温系数
d
e
l
t
a
delta
delta
每次降温就是令
T
∗
=
d
e
l
t
a
T*=delta
T∗=delta
当温度下降到
T
m
i
n
T_{min}
Tmin时算法结束
假设在温度
T
T
T时记录的最优解为
F
(
x
)
F(x)
F(x)
随机一个
x
x
x附近的点
x
1
x_1
x1并计算他的函数值
F
(
x
1
)
F(x_1)
F(x1)
他们的差作为退火的能量差,即
Δ
E
=
F
(
x
1
)
−
F
(
x
)
\Delta E=F(x_1)-F(x)
ΔE=F(x1)−F(x)
若
Δ
E
>
=
0
\Delta E>=0
ΔE>=0,说明这个新的点更优,直接更新
若
Δ
E
<
0
\Delta E<0
ΔE<0,我们就以
P
(
Δ
E
)
P(\Delta E)
P(ΔE)的概率接受这个非更优解
因为
Δ
E
<
0
\Delta E<0
ΔE<0,所以
P
(
Δ
E
)
P(\Delta E)
P(ΔE)的取值范围是
(
0
,
1
)
(0,1)
(0,1)
显然温度
T
T
T越小,接受的概率也越小
爬山算法:兔子朝着比现在高的地方跳去。它找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。
模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。
下面给出模拟退火伪代码
/*
F(x)在状态x时的评价函数值
x , nx 当前状态 与 新的状态
delta: 用于控制降温的快慢
T: 系统的温度,系统初始应该要处于一个高温的状态
T_0 , T_min :初始温度 与 温度的下限,当温度T从T_0降温到T_min,算法结束
*/
T=T_0
while(T>T_min)
{
nx=RADN(x);//在当前状态x附近随机一个新的状态
dE=F(nx)-F(x); //能量差
if(dE>=0) x=nx;//直接接受更优的移动
else if( exp( dE/T ) > random(0,1) ) x=nx;//以一定概率接受非更优的移动
//(exp是c++库函数) exp( dE/T )随温度降低而减小
T=T*delta; //降温退火 ,0<delta<1
//delta越大,降温越慢, 反之delta越小,降温越快
//若delta过大,则搜索到全局最优解的可能会较高,但搜索的过程也就较长。
//若delta过小,则搜索的过程会很快,但最终可能会达到一个局部最优值
}
要注意接受非更优解时只是接受点的移动,当前记录最优解的值不用更新
爬山算法与模拟退火的应用
HDU-2899 Strange fuction
求函数 F ( x ) = 6 ∗ x 7 + 8 ∗ x 6 + 7 ∗ x 3 + 5 ∗ x 2 − y ∗ x F(x) = 6 * x^{7} +8*x^6+7*x^3+5*x^2-y*x F(x)=6∗x7+8∗x6+7∗x3+5∗x2−y∗x在0 <= x <=100内的最小值
//模拟退火
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
typedef double dd;
#define T 100//初始温度
#define delta 0.999//降温系数
#define eps 1e-8//温度下限
int read()
{
int x=0,f=1;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return x*f;
}
int Q;
dd y,dd dx[2]={1.0,-1.0};
dd qpow(dd a,int k){ dd res=1.0; while(k>0){ if(k&1)res*=a; a*=a; k>>=1;} return res;}
dd F(dd x){ return 6.0*qpow(x,7)+8.0*qpow(x,6)+7.0*qpow(x,3)+5.0*qpow(x,2)-y*x;}
int main()
{
Q=read();
while(Q--)
{
scanf("%lf",&y);
dd t=T,x=100.0,ans=F(x);
while(t>eps)
{
dd nx=-1.0;
while(nx<0||nx>100) nx=x+dx[rand()%2]*t;
dd dE=ans-F(nx);
if(dE>=0) x=nx,ans=F(nx);
else if( exp(dE/t) > ((dd)rand()/(dd)RAND_MAX) ) x=nx;
//以一定概率接收非更优解
t*=delta;
}
printf("%.4lf\n",ans);
}
return 0;
}
//爬山算法主要代码
Q=read();
while(Q--)
{
scanf("%lf",&y);
dd t=T,r=delta,x=100.0;
while(t>eps)
{
dd nx=-1.0;
while(nx<0||nx>100) nx=x+dx[rand()%2]*t;
if(F(nx)<F(x)) x=nx;
t*=r;
}
printf("%.4lf\n",F(x));
}
POJ-2420 A Star not a Tree?
Q:在坐标系内给定n个点的坐标 ,求一个点使其到这n个点距离和最小(类似于费马点)
假设当前记录解坐标
(
x
,
y
)
(x,y)
(x,y),温度为
T
T
T
以
(
x
,
y
)
(x,y)
(x,y)为圆心,
T
T
T为半径作圆
在这个圆上随机任意一点作为下一个点判断
由于温度不断下降,随即范围也会逐渐缩小
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;
#define T 10000
#define delta 0.98
#define eps 1e-8
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=1010;
int n;
struct node{dd x,y;}rem[maxn];
dd qsum(dd x,dd y)
{
dd res=0;
for(int i=1;i<=n;++i)
{
dd tx=x-rem[i].x,ty=y-rem[i].y;
tx*=tx; ty*=ty;
res+=sqrt(tx+ty);
}
return res;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;++i)
scanf("%lf%lf",&rem[i].x,&rem[i].y);
dd t=T,r=delta,x=rem[1].x,y=rem[1].y;
while(t>eps)
{
dd rnd=rand()%360+1;
dd nx=x+cos(rnd)*t,ny=y+sin(rnd)*t;
dd de=qsum(x,y)-qsum(nx,ny);
if(de>0) x=nx,y=ny;
t*=r;
}
printf("%.0lf\n",qsum(x,y));
}
return 0;
}
POJ-2069 Super Star
在三维坐标系中给出n个点,用一个球体包含这n个点,求最小半径
假设当前温度为T
在n个点中找到与当前记录解
(
x
,
y
,
z
)
(x,y,z)
(x,y,z)最远的点
(
x
0
,
y
0
,
z
0
)
(x_0,y_0,z_0)
(x0,y0,z0)
假设他们距离为
d
i
s
dis
dis,那么另
(
x
,
y
,
z
)
(x,y,z)
(x,y,z)向其移动
x+=(x0-x)/disT;
y+=(y0-y)/disT;
z+=(z0-z)/dis*T;
由于温度不断减小,每次移动的范围也逐渐减小
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;
#define T 100
#define delta 0.98
#define eps 1e-8
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=50;
int n;
struct node{dd x,y,z;}rem[maxn];
dd dx[2]={1.0,-1.0};
dd dist(dd x,dd y,dd z,int k)
{
dd tx=x-rem[k].x,ty=y-rem[k].y,tz=z-rem[k].z;
tx*=tx; ty*=ty; tz*=tz;
return sqrt(tx+ty+tz);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
for(int i=1;i<=n;++i)
scanf("%lf%lf%lf",&rem[i].x,&rem[i].y,&rem[i].z);
dd t=T,r=100.0,ans=1e9;
dd x=rem[1].x,y=rem[1].y,z=rem[1].z;
while(t>eps)
{
int mx=1;
for(int i=1;i<=n;++i)
if(dist(x,y,z,i)>dist(x,y,z,mx)) mx=i;
dd dis=dist(x,y,z,mx);
ans=min(ans,dis);
x+=(rem[mx].x-x)/dis*t;
y+=(rem[mx].y-y)/dis*t;
z+=(rem[mx].z-z)/dis*t;
t*=delta;
}
printf("%.5lf\n",ans);
}
return 0;
}
BZOJ2428 ||洛谷P2503[HAOI2006]均分数据
把n个数分为m组,求每组数和的最小方差
[HAOI2006]均分数据 题解
正解非模拟退火的题,但可以练练模拟退火随机序列