模拟退火算法原理及求解TSP问题的Java实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tyhj_sf/article/details/53447731

转载请注明出处:

原理

退火的物理含义概述

模拟退火算法来源于固体退火原理,在热力学上,退火(annealing)现象指物体逐渐降温的物理现象,温度愈低,物体的能量状态会低;够低后,液体开始冷凝与结晶,在结晶状态时,系统的能量状态最低。大自然在缓慢降温(亦即,退火)时,可“找到”最低能量状态:结晶。但是,如果过程过急过快,快速降温(亦称「淬炼」,quenching)时,会导致不是最低能态的非晶形。

如下图所示,首先(左图)物体处于非晶体状态。我们将固体加温至充分高(中图),再让其徐徐冷却,也就退火(右图)。加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小(此时物体以晶体形态呈现)。
这里写图片描述

组合优化问题与金属退火的类比

这里写图片描述

本算法适用的组合最优化问题建模

组合最优化问题包含如下要素:

1.定义域:x∈D,D为所有状态的空间,x为某一状态
2.目标函数 : f(x);
3.约束方程:g(x)≥0;
4.最优解z=min{f(x)| g(x)≥0, x∈D };

模拟退火算法流程:

step1:先设定好初始温度t0=最高温度tMax, 随机选定一个初始状态i,计算f(i);
step2:若在当前温度下达到内层循环的退出条件,则转step3执行;否则,从邻域N(i)中随机选择一个状态j, 并计算出exp((f(i) - f(j))/temperature),
若exp((f(i) - f(j))/temperature)>random(0, 1), 则重复执行step2;
step3: 若温度t满足退出条件,则转step2执行;

模拟退火算法伪代码:

/*
* J(y):在状态y时的评价函数值
* Y(i):表示当前状态
* Y(i+1):表示新的状态
* r: 用于控制降温的快慢
* T: 系统的温度,系统初始应该要处于一个高温的状态
* T_min :温度的下限,若温度T达到T_min,则停止搜索
*/
while( T > T_min )
{
  dE = J( Y(i+1) ) - J( Y(i) ) ; 

  if ( dE >=0 ) //表达移动后得到更优解,则总是接受移动
       Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
  else
  {
       // 函数exp( dE/T )的取值范围是(0,1) ,dE/T越大,则exp( dE/T )也
       if ( exp( dE/T ) > random( 0 , 1 ) )
           Y(i+1) = Y(i) ; //接受从Y(i)到Y(i+1)的移动
  }
      T = r * T ; //降温退火 ,0<r<1 。r越大,降温越慢;r越小,降温越快
  /*
  * 若r过大,则搜索到全局最优解的可能会较高,但搜索的过程也就较长。若r过小,则搜索的过程会很快,但最终可能会达到一个局部最优值
  */
  i ++ ;
}

模拟退火算法数学模型:

模拟退火算法数学模型可以描述为,在给定结构后,模拟退火过程是从一个状态到另一状态的随机游动:

应用于求解TSP问题及Java实现

TSP问题建模

TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

TSP问题是一个组合优化问题。该问题可以被证明具有NPC计算复杂性。TSP问题可以分为两类,一类是对称TSP问题(Symmetric TSP),另一类是非对称问题(Asymmetric TSP)。所有的TSP问题都可以用一个图(Graph)来描述:

V={c1, c2, …, ci, …, cn},i = 1,2, …, n,是所有城市的集合.ci表示第i个城市,n为城市的数目;

E={(r, s): r,s∈ V}是所有城市之间连接的集合;

C = {crs: r,s∈ V}是所有城市之间连接的成本度量(一般为城市之间的距离);

如果crs = csr, 那么该TSP问题为对称的,否则为非对称的。

一个TSP问题可以表达为:

求解遍历图G = (V, E, C),所有的节点一次并且回到起始节点,使得连接这些节点的路径成本最低。

Java版实现的源码

1.创建City类,用于对要去的各个城市建模;

City.java

public class City {
    int x;
    int y;

    // Constructs a randomly placed city
    public City(){
        this.x = (int)(Math.random()*200);
        this.y = (int)(Math.random()*200);
    }

    // Constructs a city at chosen x, y location
    public City(int x, int y){
        this.x = x;
        this.y = y;
    }

    // Gets city's x coordinate
    public int getX(){
        return this.x;
    }

    // Gets city's y coordinate
    public int getY(){
        return this.y;
    }

    // Gets the distance to given city
    public double distanceTo(City city){
        int xDistance = Math.abs(getX() - city.getX());
        int yDistance = Math.abs(getY() - city.getY());
        double distance = Math.sqrt( (xDistance*xDistance) + (yDistance*yDistance) );
        return distance;
    }

    @Override
    public String toString(){
        return getX()+", "+getY();
    }
}

2.创建Tour类,对旅行路线进行建模;

Tour.java

public class Tour {
    /** Holds our citiesList of cities*/
    private ArrayList<City> citiesList ; // Cache
    private int distance = 0;

    public Tour() {
        // TODO Auto-generated constructor stub
        citiesList = new ArrayList<City>();
    }


    /** 
     * Constructs a citiesList from another citiesList
     * */
    public Tour(ArrayList<City> tour){
        citiesList = new ArrayList<City>();
        for (City city : tour) {
            this.citiesList.add(city);
        }

    }

    /** Returns citiesList information*/
    public ArrayList<City> getCitiesList(){
        return citiesList;
    }

    /** Creates a random individual*/
    public Tour generateIndividual() {
        // Loop through all our destination cities and add them to our citiesList
        for (int cityIndex = 0; cityIndex < citiesList.size(); cityIndex++) {
          setCity(cityIndex, this.getCity(cityIndex));
        }
        // Randomly reorder the citiesList
        Collections.shuffle(citiesList);
        return this;
    }

    /**Create new neighbour tour*/
    public Tour generateNeighourTour(){
        Tour newSolution = new Tour(this.citiesList);
        // Get a random positions in the tour
        int tourPos1 = (int) (newSolution.numberOfCities() * Math
                .random());
        int tourPos2 = (int) (newSolution.numberOfCities() * Math
                .random());
        // Get the cities at selected positions in the tour
        City citySwap1 = newSolution.getCity(tourPos1);
        City citySwap2 = newSolution.getCity(tourPos2);

        // Swap them
        newSolution.setCity(tourPos2, citySwap1);
        newSolution.setCity(tourPos1, citySwap2);
        return newSolution;
    }

    /** Gets a city from the citiesList*/
    public City getCity(int tourPosition) {
        return (City)citiesList.get(tourPosition);
    }

    /** Sets a city in a certain position within a citiesList*/
    public void setCity(int tourPosition, City city) {
        citiesList.set(tourPosition, city);
        // If the tours been altered we need to reset the fitness and distance
        distance = 0;
    }

    public Tour addCity(City city) {
        citiesList.add(city);
        return this;
    }

    public ArrayList<City> getAllCities() {
        return citiesList;
    }

    /** Gets the total distance of the citiesList*/
    public int getDistance(){
        if (distance == 0) {
            int tourDistance = 0;
            // Loop through our citiesList's cities
            for (int cityIndex=0; cityIndex < numberOfCities(); cityIndex++) {
                // Get city we're traveling from
                City fromCity = getCity(cityIndex);
                // City we're traveling to
                City destinationCity;
                // Check we're not on our citiesList's last city, if we are set our
                // citiesList's final destination city to our starting city
                if(cityIndex+1 < numberOfCities()){
                    destinationCity = getCity(cityIndex+1);
                }
                else{
                    destinationCity = getCity(0);
                }
                // Get the distance between the two cities
                tourDistance += fromCity.distanceTo(destinationCity);
            }
            distance = tourDistance;
        }
        return distance;
    }

    /** Get number of cities on our citiesList*/
    public int numberOfCities() {
        return citiesList.size();
    }

    @Override
    public String toString() {
        String geneString = "|";
        for (int i = 0; i < numberOfCities(); i++) {
            geneString += getCity(i)+"|";
        }
        return geneString;
    }
}

3.关键算法实现部分;

simulated annealing.java

public class SimulatedAnnealing {

    /**Set initial temp*/
    private double currentTemperature = 5000;
    /**minimal temperature to cool*/
    private double minTemperature = 0.0001;
    private double internalLoop = 1000;
    /**Cooling rate*/
    private double coolingRate = 0.001;
    /** Initialize intial solution*/
    private Tour currentSolution ;

    /**
     * set a random initial tour
     * 
     * */
    public void initTour() {
        Tour tour = new Tour();
        tour.addCity(new City(60, 200))
            .addCity(new City(180, 200))
            .addCity(new City(80, 180))
            .addCity(new City(140, 180))
            .addCity(new City(20, 160))
            .addCity(new City(100, 160))
            .addCity(new City(200, 160))
            .addCity(new City(140, 140))
            .addCity(new City(40, 120))
            .addCity(new City(100, 120))
            .addCity(new City(180, 100))
            .addCity(new City(60, 80))
            .addCity(new City(120, 80))
            .addCity(new City(180, 60))
            .addCity(new City(20, 40))
            .addCity(new City(100, 40))
            .addCity(new City(200, 40))
            .addCity(new City(20, 20))
            .addCity(new City(60, 20))
            .addCity(new City(160, 20));
        currentSolution = tour.generateIndividual();
        System.out.println("Initial solution distance: " + currentSolution.getDistance());
    }

    /**
     * iterate for getting the best Tour
     * @return best tour
     * */
    public Tour anneal() {
        DynamicDataWindow ddWindow=new DynamicDataWindow("模拟退火算法收敛过程");
        ddWindow.setY_Coordinate_Name("所有路径和 ");
        ddWindow.setVisible(true);
        long tp=0;

        Tour bestSolution = new Tour(currentSolution.getCitiesList());
        Tour newSolution = null;
        // Loop until system has cooled
        while (currentTemperature > minTemperature) {
            for (int i = 0; i < internalLoop; i++) {
                //get a solution from neighbour
                newSolution=currentSolution.generateNeighourTour();
                // Get energy of solutions
                int currentEnergy = currentSolution.getDistance();
                int neighbourEnergy = newSolution.getDistance();

                // Decide if we should accept the neighbour
                if (acceptanceProbability(currentEnergy, neighbourEnergy,
                        currentTemperature) > Math.random()) {
                    currentSolution = new Tour(newSolution.getCitiesList());
                }

                // Keep track of the best solution found
                if (currentSolution.getDistance() < bestSolution.getDistance()) {
                    bestSolution = new Tour(currentSolution.getCitiesList());
                }
            }
            // Cool system
            currentTemperature *= 1-coolingRate;

            long millis=System.currentTimeMillis();
            if (millis-tp>300) {
                tp=millis;
                ddWindow.addData(millis, bestSolution.getDistance());
            }
            try {
                Thread.sleep(10L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return bestSolution;
    }

    /**
     * 
     *  Calculate the acceptance probability
     **/
    private double acceptanceProbability(int energy, int newEnergy, double temperature) {
        // If the new solution is better, accept it
        if (newEnergy < energy) {
            return 1.0;
        }
        // If the new solution is worse, calculate an acceptance probability
        return Math.exp((energy - newEnergy) / temperature);
    }

    public static void main(String[] args) {
        SimulatedAnnealing sa=new SimulatedAnnealing();
        sa.initTour();
        Tour besTour = sa.anneal();
        System.out.println("Final solution distance: " +besTour.getDistance());
        System.out.println("Tour: " + besTour);
    }
}

4.运行过程可视化显示代码这里就不再贴出了,有需要的可以去这里下载完整源码工程

运行结果分析

运行截图冷却速率(即温度下降的变化率)设置为0.001时,路径大小收敛过程如下图:
这里写图片描述
>
>
运行截图冷却速率(即温度下降的变化率)设置为0.01时,路径大小收敛过程如下图:
这里写图片描述
>
由上图对比可以发现,冷却速率越大,路径大小收敛越快。但是,也不是说冷却速率越大越好,冷却速率越大越容易陷入局部最优解。使用中,冷却速率和内循环次数应综合考虑。

总结

模拟退火算法其特点是在开始搜索阶段解的质量提高比较缓慢,但是到了迭代后期,它的解的质量提高明显,所以如果在求解过程中,对迭代步数限制比较严格的话,模拟退火算法在有限的迭代步数内很难得到高质量的解。总体而言模拟退火算法比较适合用于有充足计算资源的问题求解。

附上本文的全部源码:项目源码

参考资料

1.退火概念参考:http://www.cnblogs.com/ranjiewen/p/6084052.html
2.TSP问题参考:http://blog.csdn.net/wangqiuyun/article/details/8918523
3.TSP解法改编自:http://www.theprojectspot.com/tutorial-post/simulated-annealing-algorithm-for-beginners/6
4.伪代码参考自:http://blog.csdn.net/dearrita/article/details/52236807

猜你喜欢

转载自blog.csdn.net/tyhj_sf/article/details/53447731