1. 引言
本教程将介绍模拟退火算法(Simulated Annealing),并通过旅行商问题(Traveling Salesman Problem, TSP)的示例实现来展示其应用。
2. 模拟退火算法
模拟退火算法是一种用于解决大规模搜索空间问题的启发式算法。其灵感来源于冶金学中的退火技术——通过加热和可控冷却材料来改变其属性。
该算法的核心机制是:在探索解空间时,随着系统温度的降低,接受较差解的概率逐渐减小。以下动画展示了模拟退火算法寻找最优解的过程:
从动画中可以看出:
- 高温时算法在更广的解空间中搜索全局最优解
- 随着温度降低,搜索范围逐渐缩小
- 最终收敛到全局最优解
算法的关键参数包括:
- 迭代次数:模拟的终止条件
- 初始温度:系统的起始能量
- 冷却速率:系统温度降低的百分比
- 最低温度:可选的终止条件
- 模拟时间:可选的终止条件
3. 旅行商问题
旅行商问题(TSP)是现代计算机科学中最著名的优化问题之一。简单来说,它是在图中寻找节点间最优路线的问题,总旅行距离可作为优化标准。更多TSP细节可参考这里。
4. Java模型设计
为解决TSP问题,我们需要两个模型类:City
和Travel
。City
类用于存储图中节点的坐标:
@Data
public class City {
private int x;
private int y;
public City() {
this.x = (int) (Math.random() * 500);
this.y = (int) (Math.random() * 500);
}
public double distanceToCity(City city) {
int x = Math.abs(getX() - city.getX());
int y = Math.abs(getY() - city.getY());
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
}
City
类的构造函数随机生成城市坐标,distanceToCity()
方法计算城市间距离。
Travel
类用于建模旅行商路线。首先是生成初始城市顺序:
public void generateInitialTravel() {
if (travel.isEmpty()) {
new Travel(10);
}
Collections.shuffle(travel);
}
我们还需要在模拟退火算法中搜索更优解时,随机交换两个城市的方法:
public void swapCities() {
int a = generateRandomIndex();
int b = generateRandomIndex();
previousTravel = new ArrayList<>(travel);
City x = travel.get(a);
City y = travel.get(b);
travel.set(a, y);
travel.set(b, x);
}
如果新解未被算法接受,需要回退交换操作:
public void revertSwap() {
travel = previousTravel;
}
最后是计算总旅行距离的方法(作为优化标准):
public int getDistance() {
int distance = 0;
for (int index = 0; index < travel.size(); index++) {
City starting = getCity(index);
City destination;
if (index + 1 < travel.size()) {
destination = getCity(index + 1);
} else {
destination = getCity(0);
}
distance += starting.distanceToCity(destination);
}
return distance;
}
现在进入核心部分:模拟退火算法的实现。
5. 模拟退火实现
在以下实现中,我们将用模拟退火算法解决TSP问题(目标是找到遍历所有城市的最短距离)。需要提供三个关键参数:startingTemperature
(初始温度)、numberOfIterations
(迭代次数)和coolingRate
(冷却速率):
public double simulateAnnealing(double startingTemperature,
int numberOfIterations, double coolingRate) {
double t = startingTemperature;
travel.generateInitialTravel();
double bestDistance = travel.getDistance();
Travel currentSolution = travel;
// ...
}
模拟开始前,我们生成随机城市顺序并计算初始距离,将其保存为bestDistance
和currentSolution
。
主模拟循环如下:
for (int i = 0; i < numberOfIterations; i++) {
if (t > 0.1) {
//...
} else {
continue;
}
}
循环持续指定的迭代次数,并添加温度≤0.1时终止的条件(低温时优化差异已不明显,可节省时间)。
算法核心逻辑:
currentSolution.swapCities();
double currentDistance = currentSolution.getDistance();
if (currentDistance < bestDistance) {
bestDistance = currentDistance;
} else if (Math.exp((bestDistance - currentDistance) / t) < Math.random()) {
currentSolution.revertSwap();
}
每步模拟中:
- 随机交换两个城市
- 计算新距离
currentDistance
- 若新距离更优则更新
bestDistance
- 否则根据玻尔兹曼概率分布决定是否接受较差解:
- 若
Math.exp((bestDistance - currentDistance) / t) < Math.random()
则回退交换 - 否则保留新顺序(避免陷入局部最优)
- 若
最后每步按冷却速率降低温度:
t *= coolingRate;
模拟结束后返回找到的最优解。
参数选择建议:
- ✅ 小规模解空间:降低初始温度、提高冷却速率(节省时间且不影响质量)
- ✅ 大规模解空间:提高初始温度、降低冷却速率(更多局部极小值需规避)
- ⚠️ 确保足够时间从高温冷却到低温
踩坑提示: 正式模拟前务必用小规模问题调参,能显著提升最终效果。调参案例可参考此文。
6. 总结
本教程快速介绍了模拟退火算法,并成功解决了旅行商问题。这个简单算法在特定优化问题中表现强大,值得掌握。
完整实现代码可在GitHub获取。