【算法】模拟退火

2023-11-08

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既可以变小,也可以变大,所以我们正负都要进行一个跳转,算出变换后的点dx
        double 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 就是 yi
        double p = fabs(e[i].x - x); // x0 - xi
        double q = fabs(e[i].y - y); // y0 - yi
        ans += (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);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【算法】模拟退火 的相关文章

  • 哪些 iomanip 操纵器具有“粘性”?

    我最近在创建一个stringstream由于我错误地假设std setw 会影响每次插入的字符串流 直到我明确更改它 然而 插入后它总是被取消设置 With timestruct with value of Oct 7 9 04 AM st
  • 即使定义了其他主键,实体框架 6 也会创建 Id 列

    我将 DataObject 定义为 public class SensorType EntityData PKs public string CompanyId get set public string ServiceId get set
  • 请求的资源不支持 HTTP 方法“GET”

    我的路线配置正确 并且我的方法具有装饰标签 我仍然收到 请求的资源不支持 HTTP 方法 GET 消息 System Web Mvc AcceptVerbs GET POST System Web Mvc HttpGet public st
  • 在静态断言和运行时错误之间自动选择

    我有一个执行除法并检查对齐的宏 define BYTES TO WORDS x CHECK ALIGNMENT x 2 x 2 我想实施CHECK ALIGNMENT作为一个总是返回 1 的宏 并且如果满足以下条件则触发错误x不除以 2 宏
  • System.MissingMethodException:找不到方法?

    以前工作的 ASP NET WebForms 应用程序现在抛出此错误 System MissingMethodException 找不到方法 The DoThis方法位于同一个类上 它应该可以工作 我有一个这样的通用处理程序 public
  • 如何使用 saxon 将文档类型参数传递给 xslt?

    对于发送原子数据类型将使用类似 transformer SetParameter new QName customXml new XdmAtomicValue true 如何将 XML Node 作为参数从 C 传递给 XSLT 你能帮我么
  • Ruby 解释器嵌入到 C 代码中

    我只是尝试书中的一个简单例子 我有一个 sum rb 文件 class Summer def sum max raise Invalid maximum max if max lt 0 max max max 2 end end 还有一个
  • C++ 中的字符串到 LPCWSTR

    我正在尝试从字符串转换为 LPCWSTR 我使用多位 1 例如 LPCWSTR ToLPCWSTR string text LPCWSTR sw LPCWSTR text c str return sw 2 返回中文字符 LPCWSTR T
  • 获取光标相对于控件的位置 - C#

    我想获取鼠标相对于鼠标指针所在控件的位置 这意味着当我将光标置于控件的起点 左上角 时 它应该给出 0 0 我正在使用以下代码 private void panel1 MouseMove object sender MouseEventAr
  • 模拟 EF core dbcontext 和 dbset

    我正在使用 ASP NET Core 2 2 EF Core 和 MOQ 当我运行测试时 我收到此错误 消息 System NotSupportedException 非虚拟 可在 VB 中重写 成员上的设置无效 x gt x Movies
  • 输入缓冲区刷新

    考虑下面的代码 include
  • C# 列表框 ObservableCollection

    我正在尝试使用 ListBox DataSource ObservableCollection 但是我不知道如何在 OC 更新时让列表框自动更新 我可以在 OC 上挂接 CollectionChanged 事件 但是我需要对列表框执行什么操
  • 如何从c++调用python

    我是Python新手 我尝试像这样从 C 调用 python 脚本 在 Raspberry Pi 中 std string pythonCommand python Callee py a b int res system pythonCo
  • MSBuild 将动态生成的文件复制为项目依赖项的一部分

    我有一个自定义 msbuild 任务 它正在生成一些输出文件到 ProjectA 的输出目录 TargetDir 当前的代码是这样的
  • 如何检查是否发生溢出? [复制]

    这个问题在这里已经有答案了 可能的重复 检测 C C 中整数溢出的最佳方法 https stackoverflow com questions 199333 best way to detect integer overflow in c
  • C#中如何将委托转换为对象?

    我正在使用反射类来调用其他 dll 上的一些方法 方法的参数之一是委托类型 我想通过使用反射来调用这个方法 所以我需要将函数参数作为对象数组传递 但我找不到任何关于 如何将委托转换为对象 提前致谢 委托是一个对象 只需像平常一样创建预期的委
  • 如何在OpenGL中像这样绘制连接的带状线

    我想用以下方式绘制一系列连接线 GL LINE STRIP 我尝试过自己编写代码 但没有得到想要的结果 所以我来到这里 帮助我找出我错在哪里 这里我只给出我的draw 函数 glBegin GL LINE STRIP glVertex2f
  • TypeScript 中 C# 类虚拟成员的等效项

    因此 在 C 中 当我创建模型类和延迟加载内容时 我会执行以下操作 public int User ID get set public int Dept ID get set 然后在我的班级稍远一点的地方 我像这样弹出我的虚拟 public
  • 可选参数代码在 .NET 3.5 中编译。为什么?

    这段代码在 VS 2010 的框架 3 5 项目中编译正常 我三次检查过 public LoggingClient string uri net msmq localhost logging 为什么 我在 C 4 规范中没有看到任何内容 文
  • 为什么没有参数的函数(与实际函数定义相比)可以编译?

    我刚刚看到某人的 C 代码 我很困惑为什么要编译它 有两点我不明白 The 函数原型与实际函数定义相比没有参数 中的参数函数定义没有类型 include

随机推荐

  • 简单了解机器学习(Machine Learning)

    首先 什么是机器学习 笼统来讲 机器学习是通过让机器去学习从而帮助人类做出决定 人类可以说在任何时刻 做任何事情时都在面临着无数的决定 从小的决定 晚饭吃什么 穿哪双鞋 喝什么饮料 到大的决定 专业选什么 工作选什么 定居在哪里 等 我们所
  • 【关于笔记软件的感受、期望,以及初期预案】

    市场 现状 首先看格式 md比较轻量级 支持这个格式的软件也比较广泛 生产力决定生产关系 就目前需要记录的内容和频率数量来看 个人感觉用这个格式承载笔记是最合适的 文字方面 图画多媒体编辑除外 包括在项目中也会用它来做简介 语雀 飞书等等的
  • matlab矩阵分割示例

    下面介绍了使用mat2cell函数把矩阵分割为我们想要的形状 1 先产生一个6x6的随机矩阵作为被分割矩阵 2 比如想把矩阵a分割为4块 也就分为4个3x3的矩阵 方法如下 b mat2cell a 3 3 3 3 运行结果如下 再比如c
  • (一)Android布局时资源文件使用

    一 Android布局时菜单资源文件使用 Android Menu资源的使用 菜单分为三种 OptionsMenu 选项菜单 ContextMenu 上下文菜单 SubMenu 子菜单 OptionsMenu默认情况下是在点击Menu键后出
  • Python 开发桌面应用居然如此简单

    我们都知道 Python 可以用来开发桌面应用 一旦功能开发完成 最后打包的可执行文件体积大 并且使用 Python 开发桌面应用周期相对较长 假如想快速开发一款 PC 端的桌面应用 推荐使用 Aardio Python 搭配的方式进行开发
  • Session(服务端会话跟踪技术)

    开发工具与关键技术 IDEA 撰写时间 2022 10 18 服务端会话跟踪技术 将数据保存到服务端 javaEE 提供HttpSession接口 来实现一次会话的多次请求间数据共享功能 注意 Session 是基于Cookie实现的 Se
  • iOS--Runloop

    Runloop概述 一般来说 一个线程一次只能执行一个任务 执行完成后线程就会退出 就比如之前学OC时使用的命令行程序 执行完程序就结束了 而runloop目的就是使线程在执行完一次代码之后不会结束程序 而是使该线程处于一种休眠的状态 等待
  • 力扣刷题——数组(c++)

    题目 给你一个数组 nums 和一个值 val 你需要 原地 移除所有数值等于 val 的元素 并返回移除后数组的新长度 不要使用额外的数组空间 你必须仅使用 O 1 额外空间并 原地 修改输入数组 元素的顺序可以改变 你不需要考虑数组中超
  • va_list、va_start、va_arg、va_end宏的使用

    当你的函数的参数个数不确定时 就可以使用上述宏进行动态处理 这无疑为你的程序增加了灵活性 Example CString AppendString CString str1 一个连接字符串的函数 参数个数可以动态变化 LPCTSTR str
  • 特斯拉Model 3 Key Card里的黑科技

    特斯拉Model 3给用户提供了三种解锁电动车的姿势 遥控钥匙 可选 需付费购买 手机APP蓝牙解锁 以及 Key Card 钥匙卡片 其中Key Card作为手机蓝牙钥匙的备份方案 以应对手机没电了 忘带了 APP故障 车机蓝牙故障等上不
  • Python实现逻辑回归(LogisticRegression)完整过程

    最近正在做的项目正好利用到了逻辑回归 所以正好系统的学习了下 本篇博文把自己的学习笔记 项目思路及代码都记录下来 它的计算原理很多网站和书籍都有介绍 就不在这班门弄斧了 主要还是记录自己如何实现 一 逻辑回归简介 Logistic Regr
  • matlab中未定义与 ‘cell‘ 类型的输入参数相对应的运算符 ‘+‘ 的解决方案

    在函数文件中写入以下内容 function re fun a b varargin if nargin 2 re a b elseif nargin 3 c varargin 1 re a b c else error wrong end
  • 排序法 C语言常考的十大排序法 数列、字符的排序

    通过对近各大试卷题型分析 总结出 对于数据排序的十大方法 希望对大家有所帮助 方法一 冒泡排序法 升序排序法 方法二 选择排序法 方法三 插入排序法 方法四 希尔排序法 Shell Sort 方法五 归并排序法 方法六 快速排序法 交换排序
  • 一文搞懂Python时间序列预测(步骤,模板,python代码)

    预测包括 数值拟合 线性回归 多元回归 时间序列 神经网络等等 对于单变量的时间序列预测 模型有AR MA ARMA ARIMA 综合来说用ARIMA即可表示全部 数据和代码链接 数据和Jupyter文件 以预测美国未来10年GDP的变换情
  • PyTorch指定GPU训练 CUDA_VISIBLE_DEVICES

    方法一 import os import torch os environ CUDA VISIBLE DEVICES 4 5 方法二 CUDA VISIBLE DEVICES 4 python py
  • FPGA零基础学习之Vivado-LED流水灯实验

    FPGA零基础学习之Vivado LED流水灯实验 本系列将带来FPGA的系统性学习 从最基本的数字电路基础开始 最详细操作步骤 最直白的言语描述 手把手的 傻瓜式 讲解 让电子 信息 通信类专业学生 初入职场小白及打算进阶提升的职业开发者
  • QT播放音频方法

    首先需要包含的头文件包含 include
  • opencv 07 用Hausdorff距离做形状匹配(shape_example) vs2015

    01 资源 OpenCV自带的行人检测demo opencv samples cpp shape example cpp shape example cpp可以图形形状相似对比 通过判断Hausdorff距离的结果做出最匹配判断 Hausd
  • Schedule

    Part1背景 定时任务 在我们实际开发中经常会用到 比如 Linux 的 Corntab Django 的 Django celery Django corntab 等 但是这些工具和框架总有某些不合适的地方 比如不灵活 笨重等 今天我们
  • 【算法】模拟退火

    文章目录 1 模拟退火介绍 1 1模拟退火的可行性 1 2退火模型 2 详解退火 2 1退火过程 2 2各变量说明 2 2 1关于接收概率 3 退火模拟求根号n的值 4 洛谷POJ 2420 1 模拟退火介绍 模拟退火是模拟物理上退火方法