Java中的旅行商问题

2025/04/10

1. 简介

在本教程中,我们将学习模拟退火算法,并展示基于旅行商问题(TSP)的示例实现。

2. 模拟退火

模拟退火算法是一种解决具有大量搜索空间问题的启发式算法。

灵感和名称源自冶金学中的退火;它是一种涉及材料加热和控制冷却的技术。

一般来说,模拟退火算法在探索解决方案空间并降低系统温度时,会降低接受较差解决方案的概率。以下动图显示了使用模拟退火算法寻找最佳解的机制:

可以看出,当系统温度较高时,该算法使用更大的解范围来搜索全局最优解。随着温度降低,搜索范围逐渐缩小,直到找到全局最优解。

该算法有几个可用的参数:

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

必须仔细选择这些参数的值,因为它们可能会对流程的性能产生重大影响。

3. 旅行商问题

旅行商问题(TSP)是现代世界最著名的计算机科学优化问题。

简单来说,这是一个在图中节点之间寻找最优路径的问题,总行程距离可以作为优化标准之一。有关TSP的更多详细信息,请参阅此处

4. Java模型

为了解决TSP问题,我们需要两个模型类,即City和Travel。在第一个模型中,我们将存储图中节点的坐标:

@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(..)逻辑负责计算城市之间的距离。

以下代码负责对旅行推销员的行程进行建模,让我们从生成行程中城市的初始顺序开始:

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问题。简单回顾一下,目标是找到前往所有城市的最短距离。

为了启动进程,我们需要提供3个主要参数,即StartingTemperature、numberOfIterations和CoolingRate:

public double simulateAnnealing(double startingTemperature, int numberOfIterations, double coolingRate) {
    double t = startingTemperature;
    travel.generateInitialTravel();
    double bestDistance = travel.getDistance();

    Travel currentSolution = travel;
    // ...
}

在开始模拟之前,我们生成城市的初始(随机)顺序并计算总行程距离。由于这是第一次计算的距离,我们将其与currentSolution一起保存在bestDistance变量中。

下一步我们启动主模拟循环:

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,如果新计算出的currentDistance小于bestDistance,我们将其保存为最佳。

否则,我们检查概率分布的玻尔兹曼函数是否低于0-1范围内的随机选取值,如果是,则撤销城市的交换;如果不是,我们保留城市的新顺序,因为这可以帮助我们避免陷入局部极小值。

最后,在模拟的每个步骤中,我们通过提供的冷却速率降低温度:

t *= coolingRate;

模拟之后,我们返回使用模拟退火找到的最佳解决方案。

请注意有关如何选择最佳模拟参数的几个提示

  • 对于较小的解空间,最好降低起始温度并提高冷却速率,因为这将减少模拟时间,而不会损失质量
  • 对于更大的解空间,请选择更高的起始温度和较小的冷却速率,因为会有更多的局部最小值
  • 始终提供足够的时间来模拟系统从高温到低温的过程

在开始主要模拟之前,不要忘记花一些时间对较小问题实例进行算法调整,因为这将有助于改善最终结果。本文以模拟退火算法的调整为例进行了说明。

6. 总结

在本快速教程中,我们学习了模拟退火算法,并解决了旅行商问题。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章