如有不对,不吝赐教
TSP问题是属于NP-Hard类问题,这类问题在当前的传统算法中没有较好的解决方案,但是我们可以利用计算智能方面的一些算法来得到这类问题的较优解,这里介绍模拟退火算法及其java实现代码。
首先要知道模拟退火算法的来源。模拟退火算法其实来源于物理里面的分子热力学,当大量分子被先加热到高温后,开始冷却,最后大多数分子的能量是属于一个较低的能量态的。
于是我们可以将TSP问题和这里进行类比,TSP中的每一个解就是一个分子,将每一个解的路径的长度类比为分子的能量,那么我们的较优解就是在降温后能量低的分子中。
所以现在的问题就是模拟分子退火的过程,显然,我们不可能将所有的解都列出来(那样就和传统算法没啥区别了),而模拟退火的过程和传统思维不一样,它任选一个分子,然后让这个分子随机变化,变成另一个分子(也就是随机生成一个新解),它以一定几率选取这个分子作为新的分子去再次转化成另一个分子(也就是在新解的基础上再去生成新解)(这个概率满足Metropolis准则),那么这样一变,就可以想象成在分子的能量分布曲线上任意选取点,到最后的降温完成后就有较大的概率跳到能量低点。
下面先介绍Metropolis准则:
P就是选取这个分子的概率
Metropolis准则有着这三个个特性:
1.一旦当有更优解生成,那么更优解必定被选中。
2.在没有找到更优解的情况下,随着温度的降低,这个P是逼近于0的。
3.温度较高的时候,接受新解的概率与能量差基本无关。
这两个特性意味着啥呢?
第一个:绝对不会漏掉更优解,如果一个算法在计算过程中出现了更优解却没有选取,那么这个算法是失败的。
第二个:这个可以保证最后的分子的变化范围是收敛于那个区域的最优解的,因为当温度较低的时候,例如T<1的时候,只要能量差为1,概率就为37%,但是在TSP问题中,路径的长度一般是几十甚至几百几千,所以这个能量差为1的情况基本上是不会发生的。
第三个:既然能量较高时,接受新解的概率与能量差基本无关(一般T的初始温度为路径长的两个数量级以上),那么就说明前一段时间的解的搜索是具有跨度性的,不会被局限在某一个极小值点上,因此得出的较优解就不会被限制在一开始的区间上。
然后就是降温方式,降温方式有三种:
(1)景点模拟退火算法:
(2)快速模拟退火算法:
(3)多项式退火算法(我瞎取的名字,忘了叫啥):
其中的α为一接近一的常数
本次退火算法选择第三种,便于计算。
然后就是生成新解的问题了,新解的生成有两种方法:
下面用f代表将数字映射为某个城市的函数。
(1) 生成两个随机数,调用f函数,得到两个城市,随后交换两个城市,生成新解。
(2) 生成三个随机数,调用f函数,得到三个城市,即为v1,v2,v3,然后交换v1->v2和v2->v3之间的所有城市,生成新解。
本次选用第一种新解生成算法。
那么问题来了,也许有的人会问:那么你怎么能保证生产的新解一定是一个回路呢?(换句话说就是你怎么能保证新解中的城市之间必定有道路)
对于这个问题,很多给出的方法就是限定输入图为完全图,仔细一琢磨,这不是扯淡嘛,总有图不是完全图的嘛。然后我给出了下面的解决方案(别人想没想到我不知道,反正这是我在写AI报告中的改进自己瞎想的,也不知道对不对):
我们可以将图中不连接的城市之间用一条不影响结果的路径连接,也就是赋予两个不相连的城市一定权值,避免了特殊处理,而这个权值的下限是e_max*“N” ,这样如若生成的路径的权值大于e_max*“N” ,那么这条路径必定不成环。只需在最终的结果判断中就可以得出结论,无需在中途生成新解的时候判断新解是否为哈密顿环。
如果有老铁发现错误就不要吝啬自己的说教能力啦。
下面就是代码的实现:
package tsp;
import java.util.Scanner;
public class SimulatedAnnealing {
static final double initialTemp=10000.0;
static final double coolingRate=0.003;
public static void main(String[] args) {
int numberOfCities; //the number of the test cities ,5 or 10 or20
int i,j;
int[][] distance;
double nowTemp=initialTemp;
Scanner in=new Scanner(System.in);
numberOfCities=in.nextInt();
distance=new int[numberOfCities][];
for(i=0;i<numberOfCities;i++) {
distance[i]=new int[numberOfCities];
for(j=0;j<numberOfCities;j++)
distance[i][j]=in.nextInt();
} //input test data
Tour currentSolution=new Tour(numberOfCities);
currentSolution.generateIndividual();
System.out.println("Initial solution distance:"+currentSolution.getDistance(distance));
System.out.println("Tour"+currentSolution); //give the initial path
Tour best=new Tour(currentSolution.getTour(),numberOfCities);
while(nowTemp>1) {
Tour newSolution=new Tour(currentSolution.getTour(),numberOfCities);
int tourPos1=(int) ((newSolution.tourSize()-1)*Math.random());
int tourPos2=(int) ((newSolution.tourSize()-1)*Math.random());
int swapCity1=newSolution.getCity(tourPos1);
int swapCity2=newSolution.getCity(tourPos2); //produce the random two cities
newSolution.setCity(tourPos2, swapCity1);
newSolution.setCity(tourPos1, swapCity2); //exchange random two cities
int currentEnergy=currentSolution.getDistance(distance);
int neighbourEnergy=newSolution.getDistance(distance);
if(acceptOrNot(currentEnergy,neighbourEnergy,nowTemp))
currentSolution=new Tour(newSolution.getTour(),numberOfCities);
if(currentSolution.getDistance(distance)<best.getDistance(distance))
best=new Tour(currentSolution.getTour(),numberOfCities);
nowTemp*=1-coolingRate;
} //simulated annealing
System.out.println("Final solution distance: "+best.getDistance(distance));
System.out.println("Tour: "+best);
in.close();
}
/*
* follow the Metropolis principle ,decide to accept the new solution or not
*/
public static boolean acceptOrNot(int oldEnergy,int newEnergy,double temperature) {
if(newEnergy<oldEnergy)
return true;
else
return Math.exp((oldEnergy-newEnergy)/temperature)>Math.random();
}
}
package tsp;
import java.util.ArrayList;
import java.util.Collections;
public class Tour {
private ArrayList<Integer> tour;
private int distance;
private int tourSize;
/*
*produce a new default tour sequence
*/
public Tour(int tourSize){
this.tour=new ArrayList<>();
this.tourSize=tourSize;
for(int i=0;i<this.tourSize;i++)
this.tour.add(null);
}
/*
*produce a new tour sequence with the sequence given
*/
public Tour(ArrayList tour,int tourSize) {
this.tour=new ArrayList<>();
this.tour=(ArrayList) tour.clone();
this.tourSize=tourSize;
}
public ArrayList getTour() {
return this.tour;
}
/*
*randomly produce a tour sequence as the begin sequence
*/
public void generateIndividual() {
for(int cityIndex=0;cityIndex<tourSize;cityIndex++)
tour.set(cityIndex,cityIndex);
Collections.shuffle(tour); //produce a random sequence
}
/*
*get the city in the tourPosition
*/
public int getCity(int tourPosition) {
return tour.get(tourPosition);
}
/*
*set the tourPosition in sequence with the city given
*/
public void setCity(int tourPosition,int city) {
tour.set(tourPosition, city);
distance=0;
}
/*
*get the whole distance of the tour sequence
*/
public int getDistance(int[][] dis) {
if(0==distance) {
int tourDistance=0;
int cityIndex;
for(cityIndex=0;cityIndex<tourSize()-1;cityIndex++) {
tourDistance+=dis[tour.get(cityIndex)][tour.get(cityIndex+1)];
}
tourDistance+=dis[tour.get(cityIndex)][tour.get(0)];
distance=tourDistance;
}
return distance;
}
/*
*return the tour sequence size(the number of all cities)
*/
public int tourSize() {
return this.tourSize;
}
/*
*the method that make the sequence to a string
*/
@Override
public String toString() {
String geneString="";
for(int i=0;i<tourSize()-1;i++) {
geneString+=tour.get(i)+"->";
}
geneString+=tour.get(tourSize()-1);
return geneString;
}
}
给出几个测试数据以及测试结果:
1)五个城市:
0 1 57 20 81
1 0 59 49 36
57 59 0 90 82
20 49 90 0 75
81 36 82 75 0
2)十个城市:
0 1 57 20 81 59 49 36 90 82
1 0 75 18 86 71 52 31 2 10
57 75 0 37 16 17 99 45 13 1
20 18 37 0 2 38 54 58 61 61
81 86 16 2 0 17 67 46 36 7
59 71 17 38 17 0 61 79 80 52
49 52 99 54 67 61 0 31 88 73
36 31 45 58 46 79 31 0 96 93
90 2 13 61 36 80 88 96 0 54
82 10 1 61 7 52 73 93 54 0
3)二十个城市:
0 1 57 20 81 59 49 36 90 82 75 18 86 71 52 31 2 10 37 16
1 0 17 99 45 13 1 2 38 54 58 61 61 17 67 46 36 7 61 79
57 17 0 80 52 31 88 73 96 93 54 15 47 24 86 22 78 85 99 99
20 99 80 0 62 40 27 30 84 3 38 10 68 7 2 92 28 28 59 69
81 45 52 62 0 84 73 49 21 75 47 46 95 75 12 60 39 74 61 58
59 13 31 40 84 0 37 16 23 43 80 52 99 75 35 18 66 50 7 70
49 1 88 27 73 37 0 51 16 95 15 91 70 31 43 8 97 69 16 88
36 2 73 30 49 16 51 0 82 59 20 19 82 48 16 51 73 41 29 57
90 38 96 84 21 23 16 82 0 69 76 72 48 13 37 84 4 52 67 43
82 54 93 3 75 43 95 59 69 0 11 95 92 55 35 48 38 85 32 46
75 58 54 38 47 80 15 20 76 11 0 28 98 30 74 57 20 76 84 40
18 61 15 10 46 52 91 19 72 95 28 0 51 89 4 99 58 6 54 20
86 61 47 68 95 99 70 82 48 92 98 51 0 84 63 66 21 84 13 12
71 17 24 7 75 75 31 48 13 55 30 89 84 0 75 32 94 29 34 15
52 67 86 2 12 35 43 16 37 35 74 4 63 75 0 74 84 71 60 75
31 46 22 92 60 18 8 51 84 48 57 99 66 32 74 0 26 15 1 7
2 36 78 28 39 66 97 73 4 38 20 58 21 94 84 26 0 81 85 22
10 7 85 28 74 50 69 41 52 85 76 6 84 29 71 15 81 0 12 56
37 61 99 59 61 7 16 29 67 32 84 54 13 34 60 1 85 12 0 2
16 79 99 69 58 70 88 57 43 46 40 20 12 15 75 7 22 56 2 0
然后就是测试结果以及期间的能量的变化趋势:
参考文献:
1.测试数据来源:《模拟退火算法解决TSP问题》(百度文档)https://wenku.baidu.com/view/1a6975448f9951e79b89680203d8ce2f0066651d?qq-pf-to=pcqq.c2c
2.Simulated Annealing for beginners http://www.theprojectspot.com/tutorial-post/simulated-annealing-algorithm-for-beginners/6