1. 引言

本教程将介绍模拟退火算法(Simulated Annealing),并通过旅行商问题(Traveling Salesman Problem, TSP)的示例实现来展示其应用。

2. 模拟退火算法

模拟退火算法是一种用于解决大规模搜索空间问题的启发式算法。其灵感来源于冶金学中的退火技术——通过加热和可控冷却材料来改变其属性。

该算法的核心机制是:在探索解空间时,随着系统温度的降低,接受较差解的概率逐渐减小。以下动画展示了模拟退火算法寻找最优解的过程:

模拟退火算法寻优过程

从动画中可以看出:

  • 高温时算法在更广的解空间中搜索全局最优解
  • 随着温度降低,搜索范围逐渐缩小
  • 最终收敛到全局最优解

算法的关键参数包括:

  • 迭代次数:模拟的终止条件
  • 初始温度:系统的起始能量
  • 冷却速率:系统温度降低的百分比
  • 最低温度:可选的终止条件
  • 模拟时间:可选的终止条件

3. 旅行商问题

旅行商问题(TSP)是现代计算机科学中最著名的优化问题之一。简单来说,它是在图中寻找节点间最优路线的问题,总旅行距离可作为优化标准。更多TSP细节可参考这里

4. Java模型设计

为解决TSP问题,我们需要两个模型类:CityTravelCity类用于存储图中节点的坐标:

@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;
    // ...
}

模拟开始前,我们生成随机城市顺序并计算初始距离,将其保存为bestDistancecurrentSolution

主模拟循环如下:

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();
}

每步模拟中:

  1. 随机交换两个城市
  2. 计算新距离currentDistance
  3. 若新距离更优则更新bestDistance
  4. 否则根据玻尔兹曼概率分布决定是否接受较差解:
    • Math.exp((bestDistance - currentDistance) / t) < Math.random()则回退交换
    • 否则保留新顺序(避免陷入局部最优)

最后每步按冷却速率降低温度:

t *= coolingRate;

模拟结束后返回找到的最优解。

参数选择建议:

  • ✅ 小规模解空间:降低初始温度、提高冷却速率(节省时间且不影响质量)
  • ✅ 大规模解空间:提高初始温度、降低冷却速率(更多局部极小值需规避)
  • ⚠️ 确保足够时间从高温冷却到低温

踩坑提示: 正式模拟前务必用小规模问题调参,能显著提升最终效果。调参案例可参考此文

6. 总结

本教程快速介绍了模拟退火算法,并成功解决了旅行商问题。这个简单算法在特定优化问题中表现强大,值得掌握。

完整实现代码可在GitHub获取。