1. 简介

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

2. 模拟退火

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

灵感和名字来自冶金退火;它是一种涉及材料加热和受控冷却的技术。

通常,模拟退火在探索解空间并降低系统温度时会降低接受更差解决方案的可能性。以下动画显示了使用模拟退火算法查找最佳解决方案的机制:

python 旅游推销员问题 旅行推销员算法_python 旅游推销员问题

正如我们可能观察到的,该算法使用更宽的解范围和系统的高温,寻找全局最优。在降低温度的同时,搜索范围会变小,直到找到全局最佳值。

该算法有几个参数可以使用:

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

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

3. 旅行推销员问题

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

简单来说,这是一个在图中节点之间找到最佳路由的问题。总行驶距离可以是优化标准之一。有关 TSP 的更多详细信息,请查看此处

4. Java模型

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

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

}

城市类的构造函数允许我们创建城市的随机位置。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 问题。只是一个快速提醒,目标是找到旅行所有城市的最短距离。

为了启动过程,我们需要提供三个主要参数,即启动温度迭代次数冷却速率

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

在模拟的每个步骤中,我们按行进顺序随机交换两个城市。

此外,我们计算当前距离。如果新计算的当前距离低于最佳距离,我们将其保存为最佳距离

否则,我们检查概率分布的玻尔兹曼函数是否低于 0-1 范围内的随机选择值。如果是,我们将恢复城市的交换。如果没有,我们保持城市的新秩序,因为它可以帮助我们避免局部最小值。

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

t *= coolingRate;

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

请注意有关如何选择最佳仿真参数的一些提示:

  • 对于较小的解决方案空间,最好降低起始温度并提高冷却速率,因为它将减少仿真时间,而不会降低质量
  • 对于较大的解决方案空间,请选择较高的起始温度和较小的冷却速率,因为会有更多的局部最小值
  • 始终提供足够的时间从系统的高温到低温进行仿真

在开始主要模拟之前,不要忘记花一些时间使用较小的问题实例进行算法调整,因为它将改善最终结果。例如,本文显示了模拟退火算法的调整。

6. 结论

在这个快速教程中,我们能够了解模拟退火算法,并解决了旅行推销员问题。这有望表明,当应用于某些类型的优化问题时,这种简单的算法是多么方便。