模拟退火
模拟退火算法(Simulated Annealing)完全指南
一份从原理到C++实现的完整教程
📖 目录
1. 算法概述
模拟退火(Simulated Annealing,SA)是一种概率型全局优化算法,灵感来源于金属热处理中的退火工艺:
金属加热到高温后缓慢冷却,原子逐渐排列成能量最低的晶格结构 → 达到稳定状态
算法将这一物理过程映射到数学优化中:
| 物理退火 | 模拟退火算法 |
|---|---|
| 🔬 粒子状态 | 💡 问题的解 |
| ⚡ 系统能量 | 📊 目标函数值(代价/损失) |
| 🌡️ 温度 | 🎛️ 控制参数 T |
| ❄️ 缓慢冷却 | 📉 温度逐渐降低 |
| ✨ 最低能量状态 | 🏆 找到全局最优解 |
2. 核心原理
2.1 Metropolis 准则
模拟退火的核心是 Metropolis 接受准则:
给定当前解 x,新解 x_new,能量差 ΔE = E(x_new) - E(x)
接受新解的概率 P:
├── 如果 ΔE < 0 (新解更优) → P = 1 (必然接受)
└── 如果 ΔE ≥ 0 (新解更差) → P = exp(-ΔE / T) (概率接受)
关键洞察:
- ⬆️ 高温时:P 较大,更容易接受差解 → 全局探索
- ⬇️ 低温时:P 较小,主要接受优解 → 局部精炼
2.2 退火过程
温度高 ──────────────────────────→ 温度低
│ │
广泛探索全局空间 局部精细搜索
接受大量差解 几乎只接受优解
│ │
└────────── 逐渐降温过渡 ──────────────┘
3. 算法流程图
flowchart TD
Start([开始]) --> Init[初始化参数<br/>T₀, T_min, α, L]
Init --> GenInit[随机生成初始解 x_current<br/>计算能量 E_current]
GenInit --> SaveBest[保存最优解<br/>x_best = x_current<br/>E_best = E_current]
SaveBest --> CheckTemp{温度 T > T_min?}
CheckTemp -->|否| Output([输出最优解])
CheckTemp -->|是| Loop[内循环迭代 L 次]
Loop --> GenNew[扰动生成新解 x_new<br/>计算 E_new]
GenNew --> CalcDelta[计算 ΔE = E_new - E_current]
CalcDelta --> Decision{ΔE < 0?}
Decision -->|是| Accept[接受新解<br/>x_current = x_new<br/>E_current = E_new]
Decision -->|否| Prob[计算接受概率<br/>P = exp(-ΔE/T)]
Prob --> Rand{random < P?}
Rand -->|是| Accept
Rand -->|否| Reject[拒绝新解]
Accept --> UpdateBest{更新最优?}
Reject --> UpdateBest
UpdateBest -->|是| Save[更新 x_best, E_best]
UpdateBest -->|否| LoopEnd[内循环结束?]
Save --> LoopEnd
LoopEnd -->|否| GenNew
LoopEnd -->|是| Cool[降温: T = T × α]
Cool --> CheckTemp
Output --> End([结束])
4. C++完整实现
4.1 问题定义
求解函数 f(x) = x² + 4·sin(3x) 在区间 [-5, 5] 上的最小值。
#include <iostream>
#include <cmath>
#include <random>
#include <chrono>
#include <iomanip>
// ============================================================
// 目标函数:f(x) = x^2 + 4*sin(3x)
// ============================================================
double objective(double x) {
return x * x + 4.0 * std::sin(3.0 * x);
}
4.2 模拟退火类
class SimulatedAnnealing {
private:
// ----- 算法参数 -----
double T_init; // 初始温度
double T_min; // 终止温度
double alpha; // 冷却率 (0 < alpha < 1)
int L; // 每个温度的迭代次数
double lower_bound; // 变量下界
double upper_bound; // 变量上界
std::mt19937 rng; // Mersenne Twister 随机数生成器
public:
// ----- 构造函数 -----
SimulatedAnnealing(double T0, double Tmin, double a, int iterations,
double lb, double ub)
: T_init(T0), T_min(Tmin), alpha(a), L(iterations),
lower_bound(lb), upper_bound(ub) {
// 使用高精度时钟作为随机种子
auto seed = std::chrono::steady_clock::now().time_since_epoch().count();
rng.seed(static_cast<unsigned>(seed));
}
// ----- 生成 [0,1) 均匀随机数 -----
double random_uniform() {
std::uniform_real_distribution<double> dist(0.0, 1.0);
return dist(rng);
}
// ----- 生成 [lb, ub] 均匀随机数 -----
double random_range(double lb, double ub) {
std::uniform_real_distribution<double> dist(lb, ub);
return dist(rng);
}
// ----- 扰动生成新解(自适应步长) -----
double generate_neighbor(double x, double temperature) {
// 步长随温度自适应:高温时大步探索,低温时小步精炼
double scale = 0.3 * (upper_bound - lower_bound) * (temperature / T_init + 0.1);
double dx = random_range(-scale, scale);
double x_new = x + dx;
// 边界反射处理(比截断更利于探索)
if (x_new < lower_bound) {
x_new = lower_bound + (lower_bound - x_new);
}
if (x_new > upper_bound) {
x_new = upper_bound - (x_new - upper_bound);
}
// 极少数情况反射后仍越界,直接截断
if (x_new < lower_bound) x_new = lower_bound;
if (x_new > upper_bound) x_new = upper_bound;
return x_new;
}
// ----- 执行优化(返回最优值,通过引用返回最优解) -----
double optimize(double& best_x) {
// 1. 随机初始化
double x_current = random_range(lower_bound, upper_bound);
double e_current = objective(x_current);
double x_best = x_current;
double e_best = e_current;
double T = T_init;
int iteration_count = 0;
std::cout << "开始模拟退火优化...\n";
std::cout << "初始解: x = " << x_current << ", f(x) = " << e_current << "\n\n";
// 2. 主循环:降温过程
while (T > T_min) {
for (int iter = 0; iter < L; ++iter) {
iteration_count++;
// 生成新解
double x_new = generate_neighbor(x_current, T);
double e_new = objective(x_new);
double delta_e = e_new - e_current;
// Metropolis 准则
if (delta_e < 0) {
// ✅ 更优解:必然接受
x_current = x_new;
e_current = e_new;
} else {
// 🎲 更差解:概率接受
double p = std::exp(-delta_e / T);
if (random_uniform() < p) {
x_current = x_new;
e_current = e_new;
}
}
// 更新全局最优
if (e_current < e_best) {
x_best = x_current;
e_best = e_current;
}
}
// 降温
T *= alpha;
// 每10次降温输出一次状态(可选)
static int log_counter = 0;
if (++log_counter % 10 == 0) {
std::cout << "T = " << std::setw(10) << T
<< " | 当前最优 x = " << std::setw(8) << x_best
<< " | f(x) = " << std::setw(10) << e_best << "\n";
}
}
std::cout << "\n优化完成!共迭代 " << iteration_count << " 次\n";
best_x = x_best;
return e_best;
}
};
4.3 主函数
int main() {
// ----- 参数配置 -----
double T0 = 100.0; // 初始温度(足够高以充分探索)
double Tmin = 1e-6; // 终止温度
double alpha = 0.97; // 冷却率(0.95~0.99 常用)
int L = 200; // 每个温度迭代次数
double lb = -5.0; // 搜索空间下界
double ub = 5.0; // 搜索空间上界
// ----- 创建优化器并运行 -----
SimulatedAnnealing sa(T0, Tmin, alpha, L, lb, ub);
double best_x;
double best_f = sa.optimize(best_x);
// ----- 输出结果 -----
std::cout << "\n========================================\n";
std::cout << "🏆 优化结果:\n";
std::cout << " 最优解 x = " << best_x << "\n";
std::cout << " 最小值 f(x) = " << best_f << "\n";
std::cout << "========================================\n";
// 验证(理论最小值约在 x ≈ -1.3 和 x ≈ 3.4 附近)
std::cout << "\n验证: f(" << best_x << ") = " << objective(best_x) << "\n";
return 0;
}
5. 参数调优指南
5.1 核心参数表
| 参数 | 符号 | 作用 | 常见范围 | 调优建议 |
|---|---|---|---|---|
| 初始温度 | T₀ | 控制初期接受差解的概率 | 100 ~ 10000 | 🔥 越高探索能力越强,但收敛更慢 |
| 冷却率 | α | 降温速度 (0<α<1) | 0.85 ~ 0.99 | 📉 越接近1搜索越精细,耗时越长 |
| 内循环次数 | L | 每温度下采样次数 | 50 ~ 500 | 🔄 与问题维度成正比 |
| 终止温度 | T_min | 停止条件 | 1e-6 ~ 1e-8 | ❄️ 越小结果越精确,计算成本越高 |
| 扰动步长 | σ | 新解生成范围 | 自适应 | 📏 早期大步探索,后期小步精炼 |
5.2 冷却调度策略
// 1. 指数冷却(最常用)
T = T * alpha; // alpha ∈ [0.8, 0.99]
// 2. 线性冷却
T = T - delta; // delta = (T0 - Tmin) / max_iter
// 3. 对数冷却(降温更慢,适合高精度)
T = T0 / (1 + log(1 + iter));
5.3 参数选择经验法则
问题维度低(≤10) → L=100~300, α=0.95~0.99
问题维度高(>10) → L=500~2000, α=0.99~0.999
要求高精度 → T_min=1e-8, α=0.99
要求快速收敛 → T_min=1e-4, α=0.9
6. 优缺点分析
✅ 优点
| 优势 | 说明 |
|---|---|
| 🎯 全局搜索能力强 | 能有效跳出局部最优 |
| 📝 实现简单 | 核心代码不足200行 |
| 🔧 通用性强 | 不依赖梯度信息,适用于任意目标函数 |
| 📊 适用广泛 | 支持离散、连续、组合优化问题 |
| 🔄 并行友好 | 可同时运行多个副本 |
❌ 缺点
| 劣势 | 说明 |
|---|---|
| 🐢 收敛速度慢 | 相比梯度方法,需要更多评估次数 |
| 🎛️ 参数敏感 | 性能高度依赖参数调优 |
| ⚠️ 不保证最优 | 只能以高概率接近全局最优 |
| 📈 高维问题吃力 | 维度 > 100 时效率显著下降 |
7. 应用场景
7.1 经典应用领域
| 领域 | 典型问题 |
|---|---|
| 🗺️ 组合优化 | TSP(旅行商问题)、背包问题、图着色 |
| 🧠 机器学习 | 神经网络权重优化、超参数搜索 |
| 🔌 电子工程 | 电路布局设计、VLSI布线 |
| 🧬 生物信息学 | 蛋白质结构预测、基因序列比对 |
| 📅 运筹调度 | 作业车间调度、航班排程 |
| 🏗️ 工程设计 | 结构优化、参数标定 |
7.2 TSP 问题示例(伪代码)
// 旅行商问题(TSP)的解表示:城市访问顺序
struct TSPSolution {
vector<int> tour; // 城市排列
double cost; // 总路径长度
};
// 扰动操作:2-opt 交换
TSPSolution generate_neighbor(const TSPSolution& current) {
TSPSolution neighbor = current;
int i = random(0, n-1);
int j = random(i+1, n);
reverse(neighbor.tour.begin() + i, neighbor.tour.begin() + j);
neighbor.cost = calculate_cost(neighbor.tour);
return neighbor;
}
8. 改进变种
8.1 常见变种
| 变种名称 | 核心改进 | 适用场景 |
|---|---|---|
| 自适应模拟退火 | 动态调整步长和冷却率 | 通用,减少调参难度 |
| 快速模拟退火 | 使用 Cauchy 分布(重尾)扰动 | 高维问题,跳跃更大 |
| 并行模拟退火 | 多副本并行 + 信息交换 | 多核/集群环境 |
| 量子退火 | 量子隧穿效应加速 | 量子计算平台 |
8.2 自适应步长实现
double generate_neighbor_adaptive(double x, double T, double T0) {
// 步长随温度自适应
double sigma = sigma_max * (T / T0) + sigma_min * (1 - T / T0);
std::normal_distribution<double> dist(0.0, sigma);
return x + dist(rng);
}
9. 运行示例
9.1 编译与运行
# 编译(需要 C++17 或更高)
g++ -std=c++17 -O2 simulated_annealing.cpp -o sa
# 运行
./sa
9.2 预期输出
开始模拟退火优化...
初始解: x = 2.34567, f(x) = 6.78901
T = 90.0 | 当前最优 x = -1.2345 | f(x) = -2.3456
T = 80.0 | 当前最优 x = -1.2890 | f(x) = -2.4123
T = 70.0 | 当前最优 x = -1.3123 | f(x) = -2.4567
... (中间过程省略) ...
T = 1.2e-5 | 当前最优 x = -1.3245 | f(x) = -2.4689
优化完成!共迭代 32000 次
========================================
🏆 优化结果:
最优解 x = -1.32456
最小值 f(x) = -2.46891
========================================
验证: f(-1.32456) = -2.46891

浙公网安备 33010602011771号