【算法】模拟退火

article/2025/9/21 0:12:03

文章目录

      • 1.模拟退火介绍
        • 1.1模拟退火的可行性
        • 1.2退火模型
      • 2.详解退火
        • 2.1退火过程
        • 2.2各变量说明
          • 2.2.1关于接收概率
      • 3.退火模拟求根号n的值
      • 4.洛谷POJ-2420

1.模拟退火介绍

​ 模拟退火是模拟物理上退火方法,通过N次迭代(退火),逼近函数的上的一个最值(最大或者最小值)。比如在下面函数去逼近最大值C点
在这里插入图片描述

1.1模拟退火的可行性

​ 模拟退火算法得益于材料统计力学的研究成果。

​ 鉴于固体的退火原理,当固体的温度很高的时候,内能比较大,固体的内部粒子处于快速无序运动,当温度慢慢降低的过程中,固体的内能减小,粒子的慢慢趋于有序,最终,当固体处于常温时,内能达到最小,此时,粒子最为稳定

​ 如果用粒子的能量定义材料的状态,退火算法用一个简单的数学模型描述了退火的过程。假设材料在状态i下的能量为E(i),那么材料在温度T时从状态i进入状态j就应该遵从以下规律:

(1)如果E(j)<=E(i),那么级接收该状态。

(2)如果E(j)>E(i),那么就以一定的概率进入该状态:exp{(E(i)-E(j))/KT}

K为物理学中的玻尔兹曼常数,T为材料的温度

1.2退火模型

  • 退火算法是一种循环算法
  • 需要先设定一个初始的问题T【需要一个高温,模拟粒子从高温向低温退火】
  • 每次循环进行一次退火
  • 然后降低的温度,我们通过让和一个“降温系数”(一个接近1的小数,比如0.999)相乘,达到慢慢降低温度的效果,直到接近于0(我们用来代表一个接近0的数(比如0.00001)。

代码架构

double T=2000;	    //表示初始的温度为0.99
double dt=0.999;	//表示降温系数
double eps=1e-14;	//相对于1的负14次方,就是最后退出循环的温度while(T<eps)
{//------------------//进行一次退火操作//------------------T*=dt;		 //降低温度
}

2.详解退火

​ 我们要求的目标无法是两个:自变量x和对应的目标函数最值f(x)

2.1退火过程

  • 先随机找一点x0,这也是初始粒子。【注意要随机】

在这里插入图片描述

  • 找到对应的目标函数值

在这里插入图片描述

  • 进行退火操作

​ 刚才我们说了x0相当于是一个粒子,所以我们会进行一个无序运动,也就是向左或者向右随机移动:移动的幅度当前的温度T有关。

​ 温度T越大,移动的幅度越大。温度T越小,移动的幅度就越小。这是在模拟粒子无序运动的状态。

  • 接收更好的状态

在这里插入图片描述

​ 假设我们移动到了x1处,那么这个点对应的f(x1)很明显答案是优于(大于)当前的f(x0)。

在这里插入图片描述

  • 因此我们将答案进行更新。也就是将初始值进行替换:x0=x1,f(x0)=f(x1)。这是一种贪心的思想。

  • 以一定概率接受(Accept)更差的状态

为什么需要按照一定的概率接受更差的状态。因为可能在一个较差的状态旁边会出现一个更加高的山峰

在这里插入图片描述

如果我们鼠目寸光,只盯着右半区,很容易随着温度的下降、左右跳转幅度的减小迷失自己,最后困死在小山丘中。

而我们如果找到了左边山峰的低点,以一定的概率接受了它(概率大小和温度以及当前的值的关键程度有关),会在跳转幅度减少之前,尽可能找到最优点

那么我们以多少的概率去接受它呢?我们用一个公式表示:exp{ Δf / KT }

注意

- 如果随机后的函数值如果结果更好,我们一定选择它(即x0=x1,f(x0)=f(x1));
- 随机后的函数值如果结果更差,我们以exp{ Δf /  KT }的概率接受它
- e是自然对数,约等于2.71。我们可以把右上角这一坨 Δf /  KT值看成一个整体x:

而exp{x}的函数图像为:

在这里插入图片描述

而如果需要用exp{x}表示一个概率,那么x【Δf / KT】的取值范围只能为负数。

负数部分的值域是在(0,1)开区间内,x越小,越接近0,越大越靠近1。

2.2各变量说明

K其实是个物理学玻尔兹曼常数,我们在代码中不会用到

T就是当前的温度。所以实际上这个分母就是T,K当做1使用。

Δf:

  • 我们想要函数来代表一个概率值,一定要让它的值域属于(0,1),所以Δf / KT必须是个负数。但是在我们的模拟中KT一定是正数,那么Δf必须是个负数
  • Δf 就是当前解的函数值与目标解函数值之差
  • 现在我们求一个函数的最大值,如果f(x0)<f(x1),那就说明结果变好了,我们肯定选择它
  • 如果f(x0)>f(x1),那就说明结果变差了,我们需要概率选择它,因此Δf=-(f(x0)-f(x1));

在这里插入图片描述

2.2.1关于接收概率

关于接受概率e{x}和KT以及Δf的关系:

当T越大时(温度越高),由于是Δf一个负数,所以Δf / KT算出来的值会越大。比如-1 / 1000 等于 -0.001,很明显-0.001 > -1,由于e{x}是个单调递增函数,所以温度越高时,接受的概率就越大。

Δf越小【负的越多】,说明答案越糟糕,那么接受的概率就越小。

①对rand()函数

  1. rand()函数可以默认拿到[0,32767]内的随机整数
  2. RAND_MAX = 32767,可以看作常量。本质是宏定义: #define RAND_MAX 32767
  3. rand() * 2 的范围是[0,32767 * 2]
  4. rand() * 2 - RAND_MAX 的范围是[-32767, 32767]

②对exp()函数

  1. exp(x)代表e的x次方

③关于exp((df - f) / T) * RAND_MAX > rand()

  1. 目的是要概率接受,但是exp{x}是个准确值,所以从理论上我们可以生成一个(0,1)的随机数,如果exp{x}比(0,1)这个随机数要大,那么我们就接受。
  2. 但是由于rand()只能产生[0,32767]内的随机整数,化成小数太过麻烦。所以我们可以把左边乘以RAND_MAX(也就是把概率同时扩大32767倍),效果就等同exp{x}于比(0,1)了。
double T = 20000; //代表开始的温度
double dT = 0.999; //代表系数delta T
double eps = 1e-14; //相当于0.0000000000000001//用自变量计算函数值,这里可能存在多个自变量对应一个函数值的情况,比如f(x,y)
double func(int x, ... ) {//这里是对函数值进行计算double ans = .......return ans;
}
//原始值
double x = rand(); //x0取随机值
double f = func(x,...); //通过自变量算出f(x0)的值
while(T > eps) {//--------------//这里是每一次退火的操作//x1可以左右随机移动,幅度和温度T正相关,所以*T//注意这里移动可以左右移动,但是也可以单向移动//关于rand()详细见开头注的①double dx = x+(2*rand() - RAND_MAX) * T; //让x落在定义域内,如果没在里面,就重新随机。// ================while(x > ? || x < ? ...) { //保证x在作用域中double dx = (2*rand() - RAND_MAX) * T; }// ================//求出f(x1)的值double df = func(dx);//这里需要具体问题具体分析,我们要接受更加优秀的情况。可能是df < f(比如求最小值)if(f < df) {f = df; x = dx;  [...,y = dy;] // 接受,替换值,如果多个自变量,那么都替换}//否则概率接受,注意这里df-f也要具体问题具体分析。//详细见开头注的②③else if(exp((df - f) / T) * RAND_MAX > rand()) {f = df; x = dx;  [...y = dy;] // 接受,替换值,如果多个自变量,那么都替换}//--------------T = T * dT; //温度每次下降一点点, T * 0.999
}
//最后输出靠近最优的自变量x值,和函数值f(x)
cout << x << " " << f << endl;

3.退火模拟求根号n的值

我们试图通过退火找出一个值x0,使得x0的平方的值更加接近于n

所以我们的目标函数就可以知道:f(x)=|x0^2-n|;

//n代表我们最后函数要逼近的值
double n;
//x表示我们随机产生的那个数的平方和n的靠近程度
double func(double x) {return fabs(x * x - n);
}

退火函数SA()

double T = 20000; //初始温度,初始温度主要是看题目开始需要跳转的幅度。
double dT = 0.995; //变化率,这里需要速度稍微慢一点,写0.999 或者 0.997都可以,但是越靠近1速度就越慢 
const double eps = 1e-14; //10的-14次方已经非常小了,写这个大概率没问题
void SA() {//首先随机生成一个点x0,这里我用0代替。double x = 0;//算出x平方和n的差距f(x0)double f = func(x);while(T > eps) {//这里x0既可以变小,也可以变大,所以我们正负都要进行一个跳转,算出变换后的点dxdouble dx = x + (2 * rand() - RAND_MAX) * T;//但是请注意,dx很明显要保证 >= 0才行,因为算术平方根的定义域是>=0,因此小于0就重新随机while(dx < 0) {dx = x + (2 * rand() - RAND_MAX) * T;}//算出变换后的点dx的平方和n的差距,f(dx)double df = func(dx);//这里就是关键的地方了,很明显我们需要算出来的function值越小,自变量x更加接近那个根号值。//所以如果新来的值df 比 f更小,我们百分百接受if(df < f) {//注意更新所有变量f = df; x = dx;}//否则我们概率接受,这里的需要写 f - df了,因为这样才是负值。负值说明我们并不是贪心接受的,他是不太好的值。else if(exp((f - df)/T) * RAND_MAX > rand()) {//注意更新所有变量f = df; x = dx;}//温度下降一下T *= dT;}printf("%.8lf",x);
}

4.洛谷POJ-2420

题目描述

给出平面上N(N<=100)个点,你需要找到一个这样的点,使得这个点到N个点的距离之和尽可能小。输出这个最小的距离和(四舍五入到最近的整数)。

Input输入

第一行N,接下来N行每行两个整数,表示N个点

Output输出

一行一个正整数,最小的距离和。

Sample Input样例输入
4
0 0
0 10000
10000 10000
10000 0
Sample Output样例输出
28284

在这里插入图片描述

将所有的点存放入一个结构体point数组中

const int N = 1e5 + 5;
struct Point {double x, y;
}e[N];//main函数中输入N个点
int main() 
{cin >> n;for(int i = 1; i <= n; i++) scanf("%lf%lf", &e[i].x, &e[i].y);
}

关键的函数

//输入我们随机生成的点A 坐标 x0, y0
double func(double x, double y) {double ans = 0;for(int i = 1; i <= n; i++) {double xx = e[i].x, yy = e[i].y; // xx 就是 xi ,yy 就是 yidouble p = fabs(e[i].x - x); // x0 - xidouble q = fabs(e[i].y - y); // y0 - yians += (sqrt((p * p + q * q))); // ans加上到随机点A到点i的距离}return ans;
}

然后套用上面的公式,我们写出SA()模拟退火

double T = 2000; //初始温度
double dT = 0.993; //变化率,这里需要速度稍微慢一点,写0.995 或者 0.997都可以,但是越靠近1速度就越慢 
const double eps = 1e-14; //10的-14次方已经非常小了,写这个大概率没问题
void SA() {//随机生成点A(x0,y0),这里我直接赋值为了0。当然也可以写rand(),差别都不大。double x = 0, y = 0;//生成的点A(x0,y0)对应的函数值f(x0, y0)double f = func(x, y);while(T > eps) {//这里对x和y同时进行一个偏移,很明显在一个平面中,上下左右都可以移动,所以我们选择了rand() * 2 - RAND_MAX//对于每个点,我们的偏移幅度都要 * T,温度越高,偏移量越大double dx = x + (rand() * 2 - RAND_MAX) * T;double dy = y + (rand() * 2 - RAND_MAX) * T;//算出偏移后的点B(dx, dy)对应的函数值f(dx, dy)double df = func(dx, dy);//这里就是关键的地方了,很明显我们需要算出来的function值越小,就更加优秀//所以如果新来的值df 比 f更小,我们百分百接受if(df < f) {//注意更新所有变量f = df; y = dy; x = dx;}//否则我们概率接受,这里的需要写 f - df了,因为这样才是负值else if(exp((f - df) / T) * RAND_MAX > rand()) {//注意更新所有变量f = df; y = dy; x = dx;}//降低温度T = T * dT;}//输出答案printf("%.f\n", f);
}

http://chatgpt.dhexx.cn/article/5icj6yTM.shtml

相关文章

模拟退火算法的原理+应用

本文转自https://www.cnblogs.com/ranjiewen/p/6084052.html 模拟退火算法 著名的模拟退火算法&#xff0c;它是一种基于蒙特卡洛思想设计的近似求解最优化问题的方法。 一点历史——如果你不感兴趣&#xff0c;可以跳过 美国物理学家 N.Metropolis 和同仁在1953年发表研究复杂…

优化算法——模拟退火算法

模拟退火算法原理 爬山法是一种贪婪的方法&#xff0c;对于一个优化问题&#xff0c;其大致图像(图像地址)如下图所示&#xff1a; 其目标是要找到函数的最大值&#xff0c;若初始化时&#xff0c;初始点的位置在 C C C处&#xff0c;则会寻找到附近的局部最大值 A A A点处&a…

模拟退火算法详细讲解(含实例python代码)

模拟退火算法详细讲解&#xff08;含实例python代码&#xff09; &#xff08;一&#xff09;模拟退火算法简介&#xff08;二&#xff09;模拟退火算法原理&#xff08;三&#xff09;退火过程中参数控制&#xff08;四&#xff09;算法步骤&#xff08;五&#xff09;实例分析…

模拟退火算法(SA)

一、算法简介 模拟退火算法(SA) 一种模拟物理退火过程而设计的优化算法 初始处于一个高温状态下&#xff0c;然后逐渐退火&#xff0c;在每个温度下慢慢冷却&#xff0c;最终达到物理基态&#xff08;相当于算法找到最优解&#xff09; 求解TSP问题(旅行商问题)、最值问题、全…

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

这是一篇关于模拟退火算法的总结博客&#xff0c;包括算法思想&#xff0c;算法步骤&#xff0c;Python实现&#xff0c;MATLAB实现&#xff0c;算法进阶等。 目录 1 算法思想2 算法步骤3 SA解函数最小值&#xff08;python实现&#xff09;4 SA解旅行商问题&#xff08;MATLA…

退火算法(Annealing)简介与详解

模拟退火算法&#xff08;Simulated Annealing&#xff0c;SA&#xff09; 秒懂爬山算法&#xff08;Hill Climbing&#xff09;退火算法 详解算法来源数学推导来源&#xff1a;metropolis准则&#xff08;蒙特卡洛准则&#xff09; 算法流程算法优劣例题 秒懂 爬山算法&#…

Instrumentation框架分析及其使用

本文旨在从Android系统源码出发,简单梳理Instrumentation框架的行为及逻辑结构,供有兴趣的同学一起学习 从am instrument谈起 am instrument命令的执行 我们知道,命令行运行Android测试的命令是adb shell am instrument,这个命令是如何调起我们的测试代码来进行测试的呢,…

android.app.instrumentation解析

已经在Android SDK中学习了很多关于JUnit的内容&#xff0c;但是感觉一直有几个问题没有解决&#xff08;不知道大家是否有同样的感受&#xff09;JUnit的测试都自动化的&#xff0c;完全是不需要任何操作的&#xff0c;有2个问题我一直都还没有找到答案&#xff0c;这2个问题如…

Java探针 Instrumentation

背景&#xff1a;假如我们想打印出某些系统->某些类->某些方法的执行耗时&#xff0c;方式有很多&#xff0c;但是想要无侵入的做到这一点&#xff0c;只有Java探针一种方式。这也是很多调用链系统依赖的技术基础。 什么是Java探针 通俗来讲&#xff0c;就是Java提供的一…

opentelemetry-java-instrumentation翻译

原文地址&#xff1a;原文 OpenTelemetry Instrumentation for Java 这个项目提供了一个Java agent JAR&#xff0c;这个jar可以attached to任何Java8的应用上&#xff0c;动态地注入&#xff08;inject&#xff09;字节码从大量的流行库和框架&#xff08;popular libraries …

Instrument

使用Instruments 里面的Automation&#xff0c;可以对iOS进行自动化测试。 参考这篇文章&#xff1a;http://www.codeproject.com/KB/iPhone/UI_Automation_Testing.aspx 我用的是xcode4.2。 在这里下载修改好的项目&#xff0c;xcode4.2下用的&#xff1a;http://download.csd…

android Instrumentation

Android提供了一系列强大的测试工具&#xff0c;它针对Android的环境&#xff0c;扩展了业内标准的JUnit测试框架。尽管你可以使用JUnit测试Android工程&#xff0c;但Android工具允许你为应用程序的各个方面进行更为复杂的测试&#xff0c;包括单元层面及框架层面。 Android测…

Instrumentation 应用简介

引用&#xff1a; java-instrumentation 引用&#xff1a;Instrumentation 新功能 简介 java Instrumentation指的是可以用独立于应用程序之外的代理&#xff08;agent&#xff09;程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态&#…

AndroidStudio单元测试——instrumentation

前言&#xff1a;这几天老大要我搞代码自动测试&#xff0c;eclispe的已经解决了&#xff0c;可他们都是用android studio&#xff0c;所以要在android studio 上重新试验&#xff0c;这个有难度啊&#xff0c;android studio国内资料极少&#xff0c;更不要说单元测试了。goog…

Android Instrumentation 简介

Instrumentation 简介 APIs && Source code 官方APIs地址(需要翻墙)Source code Instrumentation 特点 该框架基于JUnit&#xff0c;因此既可以直接使用Junit 进行测试&#xff0c;也可以使用Instrumentation 来测试Android 组件其为Android 应用的每种组件提供了测…

冲突域和广播域?

如何理解冲突域和广播域&#xff1f; 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Switch。交…

广播域与冲突域

广播域与冲突域 一个集线器&#xff08;中继器&#xff09;连接的网络成为冲突域&#xff0c;因为每台主机都连接在了同一条线路上&#xff0c;所以传送信息时会冲突。 冲突域是基于第一层(物理层) 而交换机的本质是一个多借口网桥&#xff0c;就是说由交换机组成的网络中&…

冲突域广播域

网络互连设备可以将网络划分为不同的冲突域、广播域。但是&#xff0c;由于不同的网络互连设备可能工作在OSI模型的不同层次上。因此&#xff0c;它们划分冲突域、广播域的效果也就各不相同。如中继器工作在物理层&#xff0c;网桥和交换机工作在数据链路层&#xff0c;路由器工…

广播域和冲突域问题

该图中有几个冲突域几个广播域&#xff1f; 解答&#xff1a; 1、两个广播域&#xff0c;七个冲突域。 这样的&#xff1a;集线器属于物理层&#xff0c;所有接口同属于一个冲突域、一个广播域&#xff1b;交换机属于数据链路层&#xff0c;每个接口是一个单独的冲突域…

冲突域和碰撞域的理解

如何理解冲突域和广播域&#xff1f; 转载 冲突域&#xff1a; 【定义】在同一个冲突域中的每一个节点都能收到所有被发送的帧。简单的说就是同一时间内只能有一台设备发送信息的范围。 【分层】基于OSI的第一层物理层 【设备】第二层设备能隔离冲突域&#xff0c;比如Swi…