爬山算法 x 模拟退火

爬山算法(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)=ekTΔ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)=6x7+8x6+7x3+5x2yx在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)/dis
T;
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]均分数据 题解

正解非模拟退火的题,但可以练练模拟退火随机序列

洛谷P3878 [TJOI2010]分金币题解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值