模拟退火算法-TSP问题

求某些最优化问题的最优解是一个极其困难的任务。这是因为当一个问题变得足够大时,我们需要搜索一个巨大数量的可能解,从而找到最优的解决方案。在这种情况下,就不能指望找到一个最优函数在一个合理的时间内解决问题,应该尝试找到一个近似解。

一个经典的案例是:旅行商问题 ( TSP , Traveling Salesman Problem ) :有N个城市,要求从其中某个问题出发,唯一遍历所有城市,再回到出发的城市,求最短的路线。使用模拟退火算法可以比较快的求出TSP的一条近似最优路径。(和遗传算法求解TSP类似,前面的文章已做介绍)。

模拟退火是什么?
首先,让我们看看模拟退火是如何工作的,以及为什么它是善于解决旅行商问题。模拟退火(Simulated Annealing,简称SA)是一种通用概率算法,用来在一个大的搜寻空间内找寻命题的最优解。该算法是源于对热力学中退火过程的模拟,在某一给定初温下,通过缓慢下降温度参数,使算法能够在多项式时间内给出一个近似最优解。退火与冶金学上的‘退火’相似,而与冶金学的淬火有很大区别,前者是温度缓慢下降,后者是温度迅速下降。我们将热力学的理论套用到统计学上,将搜寻空间内每一点想像成空气内的分子;分子的能量,就是它本身的动能;而搜寻空间内的每一点,也像空气分子一样带有“能量”,以表示该点对命题的合适程度。算法先以搜寻空间内一个任意点作起始:每一步先选择一个“邻居”,然后再计算从现有位置到达“邻居”的概率。

模拟退火的优点
先来说下爬山算法(以下参考:大白话解析模拟退火算法):爬山算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。爬山算法实现很简单,其主要缺点是会陷入局部最优解,而不一定能搜索到全局最优解。如图1所示:假设C点为当前解,爬山算法搜索到A点这个局部最优解就会停止搜索,因为在A点无论向那个方向小幅度移动都不能得到更优的解。爬山法是完完全全的贪心法,每次都鼠目寸光的选择一个当前最优解,因此只能搜索到局部的最优值。


模拟退火其实也是一种贪心算法,但是它的搜索过程引入了随机因素。模拟退火算法以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以图1为例,模拟退火算法在搜索到局部最优解A后,会以一定的概率接受到E的移动。
也许经过几次这样的不是局部最优的移动后会到达D点,于是就跳出了局部最大值A。
模拟退火算法描述:
若J( Y(i+1) )>= J( Y(i) )  (即移动后得到更优解),则总是接受该移动
若J( Y(i+1) )< J( Y(i) )  (即移动后的解比当前解要差),则以一定的概率接受移动,而且这个概率随着时间推移逐渐降低(逐渐降低才能趋向稳定)
这里的“一定的概率”的计算参考了金属冶炼的退火过程,这也是模拟退火算法名称的由来。
根据热力学的原理,在温度为T时,出现能量差为dE的降温的概率为P(dE),表示为:
 P(dE) = exp( dE/(kT) )
其中k是一个常数,exp表示自然指数,且dE<0。这条公式说白了就是:温度越高,出现一次能量差为dE的降温的概率就越大;温度越低,则出现降温的概率就越小。
又由于dE总是小于0(否则就不叫退火了),因此dE/kT < 0 ,所以P(dE)的函数取值范围是(0,1) 。
随着温度T的降低,P(dE)会逐渐降低。我们将一次向较差解的移动看做一次温度跳变过程,我们以概率P(dE)来接受这样的移动。
关于爬山算法与模拟退火,有一个有趣的比喻:
爬山算法:兔子朝着比现在高的地方跳去。它找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。
模拟退火:兔子喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。

接受函数
接受函数决定选择哪一个解决方案,从而可以避免掉一些局部最优解。
首先我们检查如果相邻的解决方案是比我们目前的解决方案好,如果是,我们接受它。否则的话,我们需要考虑的几个因素:
1) 相邻的解决方案有多不好; 2) 当前的温度有多高。在高温系统下更有可能接受较糟糕的解决方案。
这里是简单的数学公式:exp( (solutionEnergy – neighbourEnergy) / temperature ),即上面的 P(dE) = exp( dE/(kT) )
算法过程描述
1) 首先,需要设置初始温度和创建一个随机的初始解。
2) 然后开始循环,直到满足停止条件。通常系统充分冷却,或找到一个足够好的解决方案。
3) 把当前的解决方案做一些小的改变,然后选择一个新的相邻的方案。
4) 决定是否移动到相邻的解决方案。
5) 降低温度,继续循环
样例代码
以TSP问题为例,城市坐标的分布如下所示:

代码以用Java编写。首先创建一个城市类City.java

 

01 package sa;
02  
03 public class City {
04     int x;
05     int y;
06  
07     // 生成一个随机的城市
08     public City(){
09         this.x = (int)(Math.random()*200);
10         this.y = (int)(Math.random()*200);
11     }
12  
13     public City(int x, int y){
14         this.x = x;
15         this.y = y;
16     }
17  
18     public int getX(){
19         return this.x;
20     }
21  
22     public int getY(){
23         return this.y;
24     }
25  
26     // 计算两个城市之间的距离
27     public double distanceTo(City city){
28         int xDistance = Math.abs(getX() - city.getX());
29         int yDistance = Math.abs(getY() - city.getY());
30         double distance = Math.sqrt( (xDistance*xDistance) + (yDistance*yDistance) );
31  
32         return distance;
33     }
34  
35     @Override
36     public String toString(){
37         return getX()+", "+getY();
38     }
39 }

Tour类,代表一个解决方案,即旅行的路径。

01 package sa;
02  
03 import java.util.ArrayList;
04 import java.util.Collections;
05  
06 public class Tour{
07  
08     // 保持城市的列表
09     private ArrayList tour = new ArrayList<City>();
10     // 缓存距离
11     private int distance = 0;
12  
13     // 生成一个空的路径
14     public Tour(){
15         for (int i = 0; i < SimulatedAnnealing.allCitys.size(); i++) {
16             tour.add(null);
17         }
18     }
19  
20     // 复杂路径
21     public Tour(ArrayList tour){
22         this.tour = (ArrayList) tour.clone();
23     }
24  
25     public ArrayList getTour(){
26         return tour;
27     }
28  
29     // Creates a random individual
30     public void generateIndividual() {
31         // Loop through all our destination cities and add them to our tour
32         for (int cityIndex = 0; cityIndex < SimulatedAnnealing.allCitys.size(); cityIndex++) {
33           setCity(cityIndex, SimulatedAnnealing.allCitys.get(cityIndex));
34         }
35         // 随机的打乱
36         Collections.shuffle(tour);
37     }
38  
39     // 获取一个城市
40     public City getCity(int tourPosition) {
41         return (City)tour.get(tourPosition);
42     }
43  
44     public void setCity(int tourPosition, City city) {
45         tour.set(tourPosition, city);
46         // 重新计算距离
47         distance = 0;
48     }
49  
50     // 获得当前距离的 总花费
51     public int getDistance(){
52         if (distance == 0) {
53             int tourDistance = 0;
54             for (int cityIndex=0; cityIndex < tourSize(); cityIndex++) {
55                 City fromCity = getCity(cityIndex);
56                 City destinationCity;
57                 if(cityIndex+1 < tourSize()){
58                     destinationCity = getCity(cityIndex+1);
59                 }
60                 else{
61                     destinationCity = getCity(0);
62                 }
63                 tourDistance += fromCity.distanceTo(destinationCity);
64             }
65             distance = tourDistance;
66         }
67         return distance;
68     }
69  
70     // 获得当前路径中城市的数量
71     public int tourSize() {
72         return tour.size();
73     }
74  
75     @Override
76     public String toString() {
77         String geneString = "|";
78         for (int i = 0; i < tourSize(); i++) {
79             geneString += getCity(i)+"|";
80         }
81         return geneString;
82     }
83 }

最后是算法的实现类,和相应的测试

001 package sa;
002  
003 import java.util.ArrayList;
004 import java.util.List;
005  
006 public class SimulatedAnnealing {
007  
008     public static List<City> allCitys = new ArrayList<City>();
009  
010     //计算 接受的概率
011     public static double acceptanceProbability(int energy, int newEnergy, double temperature) {
012         // 如果新的解决方案较优,就接受
013         if (newEnergy < energy) {
014             return 1.0;
015         }
016         return Math.exp((energy - newEnergy) / temperature);
017     }
018  
019     public static void main(String[] args) {
020         // 创建所有的城市城市列表
021         init();
022         Tour best = sa();
023         System.out.println("Final solution distance: " + best.getDistance());
024         System.out.println("Tour: " + best);
025     }
026  
027     //返回近似的 最佳旅行路径
028     private static Tour sa() {
029         // 初始化温度
030         double temp = 10000;
031  
032         // 冷却概率
033         double coolingRate = 0.003;
034  
035         // 初始化的解决方案
036         Tour currentSolution = new Tour();
037         currentSolution.generateIndividual();
038  
039         System.out.println("Initial solution distance: " + currentSolution.getDistance());
040  
041         // 设置当前为最优的方案
042         Tour best = new Tour(currentSolution.getTour());
043  
044         // 循环知道系统冷却
045         while (temp > 1) {
046             // 生成一个邻居
047             Tour newSolution = new Tour(currentSolution.getTour());
048  
049             // 获取随机位置
050             int tourPos1 = (int) (newSolution.tourSize() * Math.random());
051             int tourPos2 = (int) (newSolution.tourSize() * Math.random());
052  
053             City citySwap1 = newSolution.getCity(tourPos1);
054             City citySwap2 = newSolution.getCity(tourPos2);
055  
056             // 交换
057             newSolution.setCity(tourPos2, citySwap1);
058             newSolution.setCity(tourPos1, citySwap2);
059  
060             // 获得新的解决方案的花费
061             int currentEnergy = currentSolution.getDistance();
062             int neighbourEnergy = newSolution.getDistance();
063  
064             // 决定是否接受新的 方案
065             if (acceptanceProbability(currentEnergy, neighbourEnergy, temp) > Math.random()) {
066                 currentSolution = new Tour(newSolution.getTour());
067             }
068  
069             // 记录找到的最优方案
070             if (currentSolution.getDistance() < best.getDistance()) {
071                 best = new Tour(currentSolution.getTour());
072             }
073  
074             // 冷却
075             temp *= 1-coolingRate;
076         }
077         return best;
078     }
079  
080     private static void init() {
081         City city = new City(60200);
082         allCitys.add(city);
083         City city2 = new City(180200);
084         allCitys.add(city2);
085         City city3 = new City(80180);
086         allCitys.add(city3);
087         City city4 = new City(140180);
088         allCitys.add(city4);
089         City city5 = new City(20160);
090         allCitys.add(city5);
091         City city6 = new City(100160);
092         allCitys.add(city6);
093         City city7 = new City(200160);
094         allCitys.add(city7);
095         City city8 = new City(140140);
096         allCitys.add(city8);
097         City city9 = new City(40120);
098         allCitys.add(city9);
099         City city10 = new City(100120);
100         allCitys.add(city10);
101         City city11 = new City(180100);
102         allCitys.add(city11);
103         City city12 = new City(6080);
104         allCitys.add(city12);
105         City city13 = new City(12080);
106         allCitys.add(city13);
107         City city14 = new City(18060);
108         allCitys.add(city14);
109         City city15 = new City(2040);
110         allCitys.add(city15);
111         City city16 = new City(10040);
112         allCitys.add(city16);
113         City city17 = new City(20040);
114         allCitys.add(city17);
115         City city18 = new City(2020);
116         allCitys.add(city18);
117         City city19 = new City(6020);
118         allCitys.add(city19);
119         City city20 = new City(16020);
120         allCitys.add(city20);
121     }
122 }

输出:

1 Initial solution distance: 2122
2 Final solution distance: 981
3 Tour: |180, 100|180, 60|200, 40|160, 20|100, 40|60, 20|20, 20|20, 40|60, 80|100, 160|80, 180|60, 200|20, 160|40, 120|100, 120|120, 80|200, 160|180, 200|140, 180|140, 140|

和遗传算法类似,该算法也是概率算法,结果为近似和不确定的。

参考:http://www.theprojectspot.com/tutorial-post/simulated-annealing-algorithm-for-beginners/6

http://www.cnblogs.com/heaad/archive/2010/12/20/1911614.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值