首页 > 其他分享 >模拟退火详解

模拟退火详解

时间:2023-09-20 11:27:29浏览次数:42  
标签:double 算法 yy xx 模拟退火 ans 详解

模拟退火学习(030920一上午成果)

目录

前言:

emmmm。
你还在考虑dp死活写不出来吗?
你还在担忧贪心算法的正确性吗?
模拟退火,不要9998,只要你有耐心看完我的博客。
学会模拟退火,去骗分吧!

1. 爬山算法

由来:

想想爬山的过程,要是想爬到最高峰的话,我现在在半途看不到前面更多的山的高度,但我知道离我
最近几座山的高度,我肯定会选择去爬这几座里面最高的那个,剩下的不在看了。这就是爬山,永远找的是当前最优。
于是就会有很明显的问题:全局最优非局部最优。但是可以拿来骗分,换句话说,当达到解的方案数很少时,成功概率较大。
但是真正的题怎么可能方案数较少呢?

2.模拟退火:

算法流程:

可以说是对爬山算法的一点改进吧。
因为我直接舍弃局部不优解太过了,于是呢,就有了下面的情况:
当在当前状态下找到更优秀局部转移时,我们还是让他100%转移,但是在不优秀的局部最优解下,也让他有一定概率转移。

初学(我)的问题

大概明白了算法流程,于是问题来到:

  1. 我该怎么写,怎么应用于其他题上?
  2. 时间复杂度该如何保证?
  3. 我为什么要随机,随机什么?

然后我就去万能的luogu上问了下
这是讨论帖:https://www.luogu.com.cn/discuss/690367?page=1&sort=time-d
由于我也是初学者,对于这几个东西,我只能说,必须要看懂别人的做题思路什么的才明白。

用题来进行理解:

BZOJ1844 Run Away(cqbz的oj上有)

注释暂时贴到了代码里面:

#include<bits/stdc++.h>
using namespace std;
struct node{
	double x;
	double y;
}p[1005];
/*
求距离所有点的最小值最大 
*/
double dis(double  x,double y,double xx,double yy){
	return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}
int n;
double limitx,limity;
double cal(double x0,double y0){
	double ret=1e9;
	for(int i=1;i<=n;i++){
		ret=min(ret,dis(x0,y0,p[i].x,p[i].y));
	}
	return ret;//尝试x0,y0这个坐标下的答案 
}
//以上是对答案的计算部分
const int T0=10000;//初始温度 
const double d=0.98; //降温系数
const double Tl=1e-14;//最终降到的温度 
double ansx,ansy;//最终答案 
double RD(double T){
	return T*(rand()*2-RAND_MAX);
} 
void fire(){
	int times=10;//多做几次退火减少偶然性问题 
	double best=cal(ansx,ansy);//确定自己最初取得的较优解的答案 
	//注意这里best是写在times之外的,用于表示最终答案的最优值 
	while(times--){
		double ans=best;//ans再times循环内,表示是这一次退火的最终答案 
		
		double mayx=ansx;
		double mayy=ansy;
		for(double T=T0;T>=Tl;T*=d){//模拟降温过程 
			double xx=mayx+RD(T);
			double yy=mayy+RD(T);
			//尝试新的答案,新的答案由上一次答案随机得到.
			if(xx>limitx||yy>limity||xx<0||yy<0){
				continue;//不在范围内的可能性直接byebye 
			} 
			double nowans=cal(xx,yy);
			if(best<nowans){//如果答案更好,更新答案组成 (同于对比全局的最优值) 
				best=nowans;
				ansx=xx;
				ansy=yy;//全局的最优秀 
			} 
			if(ans<nowans||exp((ans-nowans)/T)<=1.0*rand()/RAND_MAX){//寻找局部最优,要不然答案优秀直接更新,要不然随机化更新以跳出可能的局部最优解问题 
                ans=nowans;
				mayx=xx;
				mayy=yy;
            }

		}
	}
	
	
} 
//以上是模拟退火 
int main(){
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	srand(time(NULL));
	while(t--){
		
		
		cin >> limitx >> limity >> n;
		ansx=0;
		ansy=0;
		for(int i=1;i<=n;i++){
			cin >> p[i].x >> p[i].y;
			ansx+=p[i].x;
			ansy+=p[i].y;
		}
		ansx/=n;
		ansy/=n;//刚开始时建立一个初始答案,这里取得平均值。其实就是你感觉什么答案可能有点优秀就可以尝试把它当为初值。 
		
		//基本输入 
		fire();
		printf("The safest point is (%.1lf, %.1lf).\n",ansx,ansy);
		
	}
	
}


回顾上面的问题:

对于Q1
  1. 我认为退火的过程很明显嘛,就是让T一直变小并且变小的幅度逐渐减小噻。
    但是这个T到底有什么用?(代码里面有关rand的都没写注释嘛)
    于是就有了一下几个小问题:

Q1.1: 代码上exp((ans-nowans)/T)<=1.0*rand()/RAND_MAX是什么意思?
ans:这个东西呢,前面如oiwiki内所说是e的(ΔE/T)次方,表示接受局部较差解的概率。
但又不完全是概率,因为这个概率要随机,于是嘛就有了后面的rand这个东西。

Q1.2: 代码上:double xx=mayx+RD(T);double yy=mayy+RD(T);为什么这么写?
ans:其实T的意义还可用于控制当前最优解下最新最优解的范围,随着T的减小,这个范围也就不断减小,最后趋于稳定,得到最终答案。
但是还是要看概率,所以加个times来尽量避免这种情况。

Q1.3: RD函数的用途
ans:用于控制某一温度下的最优解delta。

对于Q2:

先上结论:时间复杂度在模拟退火中是难以保证的,我们要做到的是:能算就算,尽量多算(以保证答案尽量ok)。
这个时间是和T0,Tl,D,times有关系的。(相信大家也看得出来某个变量变大变小对全局的影响)。
计算的话,大家应该是算得出来的。’
关键在于时间复杂度太高时,这几个变量的取舍。
好的于是又有了一下的分枝问题:
Q2.1:T0的取舍
ans:你的取舍范围肯定是要覆盖到全部值域的,因此你的T0不能特别小,建议开到ans范围限制左右。

Q2.2:times的取舍
ans:大概在5到10次左右就好了,反正在保证Tl和D尽量细的情况下,扒点他们的时间过来。
注:比如说前者用了1e8次计算,你可以稍微让前者粗糙到1e7次计算然后给times10次。
Q2.3:D和lT的取舍(也可以说是一种两者间的博弈)
D想要尽力靠近1,lT希望尽力靠近0。
考虑D的作用:每次让T减小,同时也让接受坏解的概率/选择范围减小。
考虑lT的作用:感觉就是一个下届。
看到上面两者......感觉肯定是优先满足D 再lT呗.
D给个0.98,T0给个10000,LT给个1e-14差不多。
(但还是有概率会G,所以有T组询问的还是尽量别想这种方法吧)

Q2的补充:

你会发现其实每一次我都要用n的时间计算当前随机出来的答案是多少对吧。
这个时间复杂度也要计算哦。
其实呢,我个人觉得,大不了牺牲D到0.95,LT 到1e-6。(这就不是靠运气而是靠寿命了)

对于Q3

我认为在代码中和Q1中,您看的应该差不多清楚了。
这里就(补充)一点:
祝您在考试中遇到模拟退火时rp++。
哦不,最好是++rp。

标签:double,算法,yy,xx,模拟退火,ans,详解
From: https://www.cnblogs.com/linghusama/p/17716814.html

相关文章

  • MySQL篇:第九章_详解流程控制结构
    流程控制结构系统变量一、全局变量作用域:针对于所有会话(连接)有效,但不能跨重启查看所有全局变量SHOWGLOBALVARIABLES;查看满足条件的部分系统变量SHOWGLOBALVARIABLESLIKE'%char%';查看指定的系统变量的值SELECT@@global.autocommit;为某个系统变量赋值SET@@glo......
  • 提升工作效率的秘密武器:常用Shell脚本详解
    检测网卡流量,并按规定格式记录在日志中#!/bin/bash########################################################检测网卡流量,并按规定格式记录在日志中#规定一分钟记录一次#日志格式如下所示:#2019-08-1220:40#ens33input:1234bps#ens33output:1235bps###......
  • InnoDB锁详解(共享/排他锁、意向锁、记录锁、间隙锁、临键锁、插入意向锁、自增锁)
    原文地址:两万字详解InnoDB的锁-掘金(juejin.cn)1.为什么需要加锁?为什么需要加锁呢?在日常生活中,如果你心情不好想静静,不想被比别人打扰,你就可以把自己关进房间里,并且反锁。同理,对于MySQL数据库来说的话,一般的对象都是一个事务一个事务来说的。所以,如果一个事务内,正在写某......
  • RCC时钟详解
    目录一.STM32时钟树1.这里主要熟悉SYSCLK的时钟流程即可.(主要是配置好锁相环)二.核心寄存器分析1.RCC_CR时钟控制寄存器2.RCC_CFGR时钟配置寄存器3.其他寄存器三.RCC外设驱动1.操作寄存器方式驱动1.固件库方式驱动四.RCC外设驱动总结一.STM32时钟树1.这里主要熟悉SYSCL......
  • Eclipse Java注释模板设置详解
    设置注释模板的入口:Window->Preference->Java->CodeStyle->CodeTemplate然后展开Comments节点就是所有需设置注释的元素啦。现就每一个元素逐一介绍:文件(Files)注释标签:/***@Title:${file_name}*@Package${package_name}*@Description:${todo}(用一句话描......
  • 详解Spring缓存注解@Cacheable、@CachePut和@CacheEvict
    详解Spring缓存注解@Cacheable、@CachePut和@CacheEvict的使用简介在大型的应用程序中,缓存是一项关键技术,用于提高系统的性能和响应速度。Spring框架提供了强大的缓存功能,通过使用缓存注解可以轻松地集成缓存机制到应用程序中。本文将详细介绍Spring框架中的@Cacheable、@CachePu......
  • 详解RAID6种磁盘阵列模式
    所谓RAID就是RedundantArrayofIndependentDisk的缩写,中文意思是“独立冗余磁盘阵列”,简单来说就是一种利用多个硬盘来提高系统对磁盘的读写速度及其数据安全系数的一种技术。RAID技术开始一般用于服务器或大型工作站上面,但随着RAID技术的不断成熟,现在不少的家用PC的主板都内置......
  • React hooks详解
    importReact,{useEffect,useState}from'react';hook是react16.8的新增特性,他可以让你不在编写class的情况下shiystate以及react的特性Hooks的出现,首先解决了以下问题:告别了令人疑惑的生命周期告别类组件中烦人的this告别繁重的类组件,回归到了熟悉的函数组件reac......
  • [转]C#Invoke和BeginInvoke应用详解
    最近,在研究Invoke的使用,但是真的是一头雾水,网上看了很多资料,感觉还是看不懂,因为对于入门级的小白,想像不出Invoke的应用场景,更谈不上如何用了?1、Invoke到底是什么?Invoke的本质只是一个方法,方法一定是要通过对象来调用的。一般来说,Invoke其实用法只有两种情况:Control的Invoke......
  • 【MyAndroid】AndroidManifest.xml合并规则详解和注意事项
    APK或AndroidAppBundle文件只能包含一个AndroidManifest.xml文件,但AndroidStudio项目可以包含多个清单文件,这些清单文件由主源代码集、build变体和导入的库提供。因此,在构建应用时,Gradle构建系统会将所有清单文件合并成一个清单文件打包到应用中。清单合并工具遵循某些......