模拟退火核心循环需三层嵌套:外层控温度衰减,中层控每温迭代次数,内层扰动邻域并按Metropolis准则接受新解;关键在温度调度、邻域操作与接受判据的节奏匹配,参数须针对具体问题手调。

模拟退火在C++里怎么写核心循环
模拟退火不是标准库算法,得自己实现温度衰减、邻域扰动和接受概率逻辑。关键不在“怎么写”,而在“怎么控制退火节奏”——温度下降太快容易卡在局部最优,太慢又浪费时间。
典型结构是三层嵌套:for (int t = 0; t 控制外层迭代,while (T > T_min) 管温度,内层用 for (int i = 0; i 在同一温度下采样多个邻居。别把三者混成一层 for,否则退火行为不可控。
常见错误:用固定步长更新温度,比如 T *= 0.999。这在初期降温过缓,后期又骤降。更稳妥的是按迭代次数做非线性衰减:T = T0 / log(1 + t) 或 T = T0 * pow(0.99, t),前者更平滑,后者更常用。
怎么设计邻域操作(neighbor move)才不破坏解的合法性
组合优化问题的解通常是排列、子集或分配方案,邻域操作一旦生成非法状态(如TSP中重复访问城市、背包超重),整个搜索就失效。不能靠“拒绝非法解”来兜底——那会让有效探索率暴跌。
邻域设计必须内置约束保障:
- 对排列类问题(如TSP、作业调度),用
swap、reverse_subsequence、insert这类保序操作,天然维持合法性 - 对0-1背包,避免直接随机翻转某位——可能超重;改用“找一个已选物品移除 + 找一个未选但能装下的加入”,确保容量始终满足
- 对图划分或聚类,邻域应限制为“移动单个节点到另一组”,而非全量重分配
示例:TSP中两个城市索引 i、j 随机选取后,交换路径中对应位置即可:std::swap(solution[i], solution[j]);,不用检查是否重复。
std::exp((old_cost - new_cost) / T) 这个接受概率为什么常返回0或1
浮点精度问题在这里很致命。old_cost - new_cost 是负数(即新解更差)时,差值绝对值稍大,exp(-100) 就是 3.7e-44,在 double 下基本等于 0;而 exp(10) 是 22026,但概率不能超 1,所以实际要取 std::min(1.0, std::exp(...))。
更隐蔽的问题是温度 T 太小导致指数溢出或下溢。建议加保护:
std::rand()
- 记录最近
N次接受的解中目标函数的标准差,持续小于1e-5且温度低于T0 * 0.01,大概率已稳定 - 维护一个“最佳解冻结计数器”:每次更新全局最优时清零,否则递增;超过
max_stall_iter(比如 10000)就停
注意:不要在每次迭代都调用 std::cout 打印当前最优,I/O 会拖慢百倍以上。用秒级定时器抽样输出,比如 if (clock() - last_print > CLOCKS_PER_SEC) { ...; last_print = clock(); }。
真正难的不是写出退火框架,而是让邻域操作、温度调度、接受判据三者节奏匹配——同一套代码换一个问题,参数几乎都要重调。别迷信“通用参数”,先用小规模实例手调 T0 和衰减率,再放大规模。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











