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. 总结
在本快速教程中,我们学习了模拟退火算法,并解决了旅行商问题。
Post Directory
