数学建模常用算法-模拟退火算法

一、模拟退火算法

模拟退火的灵感来源于物理中的 "退火过程"------ 将金属加热到高温后,缓慢冷却,金属原子会在热能作用下自由运动,逐渐形成能量最低的稳定结构。算法将这一过程抽象为数学模型:

  • "温度 T":对应物理中的温度,控制算法探索新解的 "自由度"------ 温度越高,越容易接受较差的解,帮助跳出局部最优;温度越低,越倾向于接受更优解,逐渐收敛到稳定解。
  • "能量 E":对应优化问题中的目标函数值(如函数值、路径长度等),算法的目标是找到 "能量最低" 的状态(即目标函数的最优解)。
  • "冷却策略":温度随迭代逐步降低的规则,决定了算法的探索效率和收敛速度。

二、核心原理:Metropolis 接受准则

模拟退火算法的核心是Metropolis 接受准则------ 它决定了算法是否接受一个新生成的解,即使这个解比当前解更差。具体规则如下:

  1. 设当前解为\(x\),对应的目标函数值为\(f(x)\);新生成的解为\(x_1\),对应的函数值为\(f(x_1)\)。
  1. 若新解更优(如求最小值时\(f(x_1) < f(x)\)),则直接接受新解,更新当前解为\(x_1\)。
  1. 若新解较差(如求最小值时\(f(x_1) > f(x)\)),则以概率 \(P\)接受新解:

\(P = \exp\left( \frac{f(x) - f(x_1)}{T} \right)\)

    • 温度\(T\)越高,\(P\)越大,越容易接受较差解;
    • 温度\(T\)越低,\(P\)越小,越难接受较差解;
    • 当\(T \to 0\)时,\(P \to 0\),算法仅接受更优解,收敛到稳定状态。

三、代码逐行解析:从框架到细节

结合你提供的模拟退火算法框架,我们逐部分拆解代码逻辑,理解每一步的作用和设计思路。

1. 算法参数初始化

复制代码
double T = 2000; // 初始温度:控制初始探索范围

double dT = 0.99; // 温度衰减系数:控制冷却速度

double eps = 1e-14; // 温度下限:算法终止条件
  • 初始温度\(T\):通常设为较大值(如 2000、1000),保证初始阶段有足够的 "自由度" 探索解空间,避免过早陷入局部最优。
  • 衰减系数\(dT\):一般取 0.95~0.99 之间的小数,系数越接近 1,温度下降越慢,探索越充分,但迭代次数越多;系数越小,冷却越快,可能导致探索不充分。
  • 温度下限\(eps\):当温度低于该值时,算法基本不再接受较差解,此时可终止迭代,输出当前最优解。

2. 目标函数定义

复制代码
// 用自变量计算函数值,支持单变量或多变量(如f(x,y,z))

double func(int x, ... ) {

// 此处根据具体问题实现目标函数计算

double ans = ....... // 例:若求f(x)=x²的最小值,ans = x*x;

return ans;

}
  • 这是算法的 "核心输入",需根据具体建模问题定义。例如:
    • 若求解函数\(f(x) = x^2 - 4x + 5\)的最小值,func需返回\(x^2 - 4x + 5\);
    • 若解决 TSP 问题,func需计算当前路径的总长度(输入为城市序列,输出为路径和)。
  • 支持多变量优化(如\(f(x,y)\)),只需在参数列表中添加变量(如func(int x, int y, ...)),后续新解生成和更新时同步处理即可。

3. 初始解生成

复制代码
// 生成初始解(随机值)

double x = rand(); // 初始自变量x0(单变量情况)

double f = func(x,...); // 初始解对应的目标函数值f(x0)
  • 初始解通常随机生成(利用rand()函数),保证解的随机性,避免初始位置对结果的影响。
  • 若问题有明确的解空间限制(如\(x \in [0, 100]\)),需在初始生成时添加约束(如x = rand() % 101),或后续通过定义域检查修正。

4. 核心迭代:退火过程

复制代码
while(T > eps) { // 温度未降到下限,持续迭代

// 步骤1:生成新解x1(幅度与温度正相关)

double dx = (2*rand() - RAND_MAX) * T;

// 解释:(2*rand() - RAND_MAX)生成[-RAND_MAX, RAND_MAX]的随机数,

// 除以RAND_MAX后范围变为[-1, 1],再乘以T,使新解的探索幅度随温度降低而减小。

// 步骤2:定义域检查(可选,根据问题需求添加)

while(x + dx > 上限 || x + dx < 下限) { // 若新解超出可行域

dx = (2*rand() - RAND_MAX) * T; // 重新生成新解,直到符合要求

}

double x1 = x + dx; // 最终合法的新解

// 步骤3:计算新解的目标函数值

double f1 = func(x1, ...); // f1 = f(x1)

// 步骤4:Metropolis接受准则------判断是否接受新解

// 情况1:新解更优(此处假设求最小值,即f1 < f)

if(f1 < f) {

x = x1; // 更新自变量为新解

f = f1; // 更新目标函数值为新值

// 若为多变量(如x,y),需同步更新:y = y1;

}

// 情况2:新解较差,按概率接受

else {

// 计算接受概率P = exp((f - f1)/T)(求最小值时,f - f1为负数,P∈(0,1))

double prob = exp((f - f1) / T);

// 生成[0,1)的随机数,若小于P则接受新解

if(prob * RAND_MAX > rand()) {

x = x1;

f = f1;

}

}

// 步骤5:冷却温度------按衰减系数降低温度

T = T * dT;

}
  • 新解生成逻辑:新解的探索幅度与温度\(T\)正相关,这是模拟退火的关键设计 ------ 高温时大步探索,低温时精细调整,既保证全局搜索能力,又兼顾局部收敛效率。
  • 定义域检查:若问题对解有明确范围限制(如资源分配问题中 "人数不能为负"),必须添加此步骤,否则可能生成无效解,导致算法出错。
  • 接受准则细节:需根据 "求最大值" 或 "求最小值" 调整公式:
    • 求最小值:较差解的接受概率为\(P = \exp((f - f1)/T)\)(\(f1 > f\),分子为负,\(P < 1\));
    • 求最大值:较差解的接受概率为\(P = \exp((f1 - f)/T)\)(\(f1 < f\),分子为负,\(P < 1\))。

5. 输出最优解

复制代码

cout << "最优自变量x:" << x << endl;

cout << "最优目标函数值f(x):" << f << endl;

  • 迭代结束后,输出当前找到的最优解(自变量\(x\))和对应的目标函数值\(f(x)\)。
  • 由于算法存在随机性(初始解、新解生成均为随机),建议多次运行算法,取多次结果中的最优值,提高结果的可靠性。

四、参数调优:让算法更高效

模拟退火算法的性能很大程度上依赖参数设置,以下是常见的调优技巧:

  1. 初始温度\(T\):
    • 若\(T\)过小:初始探索不充分,易陷入局部最优;
    • 若\(T\)过大:迭代次数过多,算法效率低;
    • 调优建议:可先设较大值(如 1000、2000),观察迭代次数,再根据时间限制调整。
  1. 衰减系数\(dT\):
    • 若\(dT\)过小(如 0.8):温度下降快,探索不充分;
    • 若\(dT\)过大(如 0.995):迭代次数多,耗时久;
    • 调优建议:多数问题取 0.98~0.99,平衡探索效率和收敛速度。
  1. 温度下限\(eps\):
  • 一般设为\(1e-10 \sim 1e-15\),无需频繁调整 ------ 当温度低于此值时,算法已基本收敛,继续迭代意义不大。
  1. 迭代次数
  • 若问题复杂(如多变量、解空间大),可增加 "固定迭代次数" 作为额外终止条件(如while(T > eps && iter < 1e6)),避免算法超时。

五、应用场景:模拟退火能解决哪些问题?

模拟退火算法的适用范围极广,尤其适合数学建模中 "复杂、多局部最优" 的优化问题,常见场景包括:

  1. 函数极值求解:单变量或多变量函数的全局最大值 / 最小值(如\(f(x,y) = x^2 + y^2 - 2x\)的最小值)。
  2. 组合优化问题
  • 旅行商问题(TSP):寻找访问所有城市且路径最短的路线;
  • 背包问题:在重量限制下,选择物品使总价值最大;
  • 图着色问题:用最少颜色给图的顶点着色,相邻顶点颜色不同。
  1. 工程优化问题:资源分配、生产调度、参数优化(如机器学习模型的超参数调优)。

六、实战案例:用模拟退火求解函数极值

以 "求解\(f(x) = x^2 - 4x + 5\)的最小值" 为例,我们完善代码并运行:

复制代码
#include <iostream>

#include <cmath>

#include <cstdlib>

#include <ctime>

using namespace std;

// 目标函数:f(x) = x² -4x +5

double func(double x) {

return x*x - 4*x + 5;

}

int main() {

srand((unsigned int)time(NULL)); // 初始化随机种子,保证每次运行结果不同

// 1. 参数初始化

double T = 2000.0; // 初始温度

double dT = 0.99; // 衰减系数

double eps = 1e-14; // 温度下限

double x = rand() % 100; // 初始解x0(随机取0~99的整数)

double f = func(x); // 初始函数值

// 2. 退火迭代

while(T > eps) {

// 生成新解x1

double dx = (2.0 * rand() - RAND_MAX) / RAND_MAX * T; // 范围[-T, T]

double x1 = x + dx;

// 计算新解的函数值

double f1 = func(x1);

// Metropolis准则

if(f1 < f) { // 新解更优,接受

x = x1;

f = f1;

} else { // 按概率接受较差解

double prob = exp((f - f1)/T);

if(prob * RAND_MAX > rand()) {

x = x1;

f = f1;

}

}

// 冷却温度

T *= dT;

}

// 3. 输出结果

cout << "函数f(x) = x² -4x +5的最优解:" << endl;

cout << "最优x值:" << x << endl;

cout << "最小函数值:" << f << endl;

// 理论最优解:x=2,f(x)=1,实际运行结果会接近此值

return 0;

}
  • 运行结果:由于随机性,每次输出的\(x\)可能略有不同(如 1.999999 或 2.000001),但函数值会接近理论最小值 1,验证了算法的有效性。

七、总结

模拟退火算法是数学建模中 "化繁为简" 的利器 ------ 它不依赖问题的具体结构,只需定义目标函数和解的生成规则,就能有效跳出局部最优,寻找全局最优解。掌握它的核心原理(Metropolis 准则)、代码框架和参数调优技巧,能让你在面对复杂优化问题时更有底气。

当然,模拟退火也有局限性(如随机性导致结果不稳定、大规模问题效率低),实际建模中可结合 "遗传算法""粒子群优化" 等算法,或通过多次运行取最优值来弥补。希望这篇文章能帮你真正学会模拟退火,在建模竞赛中披荆斩棘!

相关推荐
551只玄猫5 小时前
【模块1 建立认知1】为什么金融数据不能用“普通机器学习”?
人工智能·机器学习·数学建模·金融·数据科学·金融建模
Allen_LVyingbo7 小时前
《狄拉克符号法50讲》习题与解析(上)
开发语言·人工智能·python·数学建模·量子计算
551只玄猫7 小时前
Why Financial Data Cannot Be Modeled with “Standard Machine Learning”
算法·机器学习·数学建模·金融·数据科学·英文·金融建模
AI科技星1 天前
v=c 物理理论核心参数转换表达式大全
开发语言·线性代数·算法·数学建模·平面
AI科技星1 天前
张祥前统一场论中两个电荷定义的统一性解析
开发语言·线性代数·算法·数学建模·平面
做cv的小昊1 天前
【TJU】研究生应用统计学课程笔记(1)——第一章 数理统计的基本知识(1.1 数理统计的基本内容、1.2 数理统计的基本概念)
笔记·线性代数·考研·数学建模·ai·矩阵·概率论
做cv的小昊2 天前
【TJU】应用统计学——第五周作业(3.1 假设检验的基本思想、3.2 单个正态总体参数的假设检验)
学习·线性代数·机器学习·数学建模·矩阵·概率论·tju
飞舞哲2 天前
含模型不确定性的机械臂神经网络状态反馈自适应控制
人工智能·神经网络·数学建模
smppbzyc2 天前
2026认证杯挑战赛A题【 水系电解液配方】原创论文讲解
数学建模·认证杯数学建模·认证杯·2026认证杯·认证杯数学建模竞赛·2026认证杯数学建模
smppbzyc3 天前
2026认证杯数学建模A题【 水系电解液配方】原创论文讲解(含完整python代码)
数学建模·数学建模竞赛·认证杯·2026认证杯·2026认证杯数学建模竞赛·认证杯数学建模竞赛