模拟退火

模拟退火算法(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

📚 延伸阅读


posted @ 2026-06-22 22:07  十七code  阅读(19)  评论(0)    收藏  举报