zoukankan      html  css  js  c++  java
  • 转 | 模拟退火算法(SA)和迭代局部搜索(ILS)求解TSP的Java代码分享

    以下文章来源于数据魔术师 ,作者周航

    前言

    大家好呀!我们你们好久不见的。。。咳咳,初次见面的小编!

    之前重新整理了ILS的代码,有人留言问能不能提供java版。

    正好最近在学启发式算法和java,为了造福人类小编打算提供模拟退火法和迭代局部搜索求解TSP的java版本,方便一些不喜欢C++的同鞋~~

    代码是基于我自己写的版本,但我是学习了公众号推文之后写的,同时有参照原文代码,因此虽然与C++代码有些许区别,但总体是类似的。

    本文不再赘述TSP或者SA,ILS了,有需要请点击下方链接学习(一看就懂的那种哦!):

    干货 | 用模拟退火(SA, Simulated Annealing)算法解决旅行商问题

    干货|迭代局部搜索算法(Iterated local search)探幽(附C++代码及注释)

    不多说了,开始看代码吧~!

    代码下载

    欲下载本文相关的代码及算例,请关注公众号【程序猿声】,后台回复【SAILSJAVA】不包括【】即可

    SA求解TSP的JAVA代码

    SA分为四个类:MainRun,Data,Path,SimulatedAnnealing。

    MainRun是程序的入口。

    package SA;
    
    /**
     * 主函数运行类
     */
    
    public class MainRun {
    
      public static void main (String args[])
      {
        long begintime = System.nanoTime();
    
        Data.PrintData();
        SimulatedAnnealing.SA();
        SimulatedAnnealing.PrintPath();
    
        long endtime = System.nanoTime();
        double usedTime= (endtime - begintime)/(1e9);
        System.out.println();
        System.out.println("程序耗时:"+usedTime+"s");
      }
    }
    
    

    Data是数据类,存放SA和TSP的数据。

    package SA;
    
    /**
     * 数据类,包括:
     * TSP城市坐标,采用柏林52城
     * SA系数。
     */
    
    
    public class Data {
      public static final double  T0=50000.0, // 初始温度
      T_min=1e-8,  // 结束温度
      q=0.98,   // 退火系数
      K=1;  //公式中的常数K 
      public static final int L=1000,  // 每个温度时的迭代次数,即链长
      N=52;  // 城市数量
    
      public static double[][] city_pos= //柏林52城城市坐标,最优解7542
      {
      { 565,575 },{ 25,185 },{ 345,750 },{ 945,685 },{ 845,655 },
      { 880,660 },{ 25,230 },{ 525,1000 },{ 580,1175 },{ 650,1130 },
      { 1605,620 },{ 1220,580 },{ 1465,200 },{ 1530,5 },
      { 845,680 },{ 725,370 },{ 145,665 },
      { 415,635 },{ 510,875 },{ 560,365 },{ 300,465 },{ 520,585 },{ 480,415 },
      { 835,625 },{ 975,580 },{ 1215,245 },{ 1320,315 },{ 1250,400 },{ 660,180 },
      { 410,250 },{ 420,555 },{ 575,665 },{ 1150,1160 },{ 700,580 },{ 685,595 },
      { 685,610 },{ 770,610 },{ 795,645 },{ 720,635 },{ 760,650 },{ 475,960 },
      { 95,260 },{ 875,920 },{ 700,500 },{ 555,815 },{ 830,485 },{ 1170,65 },
      { 830,610 },{ 605,625 },{ 595,360 },{ 1340,725 },{ 1740,245 }
      };
    
      public static void PrintData()
    {
        System.out.println("模拟退火算法,初始温度T0="+T0+
            ",降温系数q="+q+",每个温度迭代"+L+"次");
      }
    
    }
    
    

    Path是路径类,打包处理路径的静态方法。

    package SA;
    import static SA.Data.*;
    import static java.lang.Math.*;
    
    /**
     * 路径类,打包处理路径的静态方法:
     * 计算两点间距离 distance
     * 计算路径长度 path_len
     * 产生新解(新路径)create_new
     */
    
    public class Path {
      public static double distance(double[] p1,double[] p2)  //计算两点间距离 
    {
        double dis=0;
        dis=sqrt(pow(p1[0]-p2[0],2)+pow(p1[1]-p2[1],2));
        return dis;
      }
    
      public static double path_len(int[] city_list) //计算路径长度 
    {
        double path=0;
        for (int i=0;i<(N-1);i++)
          path+=distance(city_pos[city_list[i]],city_pos[city_list[i+1]]);
        path+=distance(city_pos[city_list[0]],city_pos[city_list[N-1]]) ;
        return path;
      }
    
      public static void create_new(int[] city_list)   //产生新解
    {
         int i=(int) (random()*N);
         int j=(int) (random()*N);
        while(j==i)
          j=(int) (random()*N);
    
        int temp;
         temp=city_list[i];
         city_list[i]=city_list[j];
         city_list[j]=temp;
       }
    }
    
    

    SimulatedAnnealing开始模拟退火。

    package SA;
    import static SA.Data.*;   
    import static SA.Path.*;
    import static java.lang.Math.*;
    import java.util.*;
    
    /**
     * 模拟退火过程
     */
    
    public class SimulatedAnnealing {
    
      private static int[] bestpath=new int[N];
      private static int count=0;
      private static double f2;
    
      public static void SA() //模拟退火
    {
        for(int i=0;i<N;i++)  // 初始化总最优路径
          bestpath[i]=i;
    
         int[] newpath=Arrays.copyOf(bestpath,bestpath.length);   // 新解
    
         double f1;
        f2=path_len(bestpath); 
        double T=T0;
    
         while(T>T_min)
         {
           for (int i=0;i<L;i++)
          {
             create_new(newpath);
             f1=path_len(newpath);
             double de=f1-f2;
             if (de<0)
             {
               System.arraycopy(newpath, 0, bestpath, 0, bestpath.length);
              f2=f1;
               }
            else 
             {
               double r = random();
               if(exp(de/(K*T))<r)
               {
                 System.arraycopy(newpath, 0, bestpath, 0,bestpath.length);
                 f2=f1;
                 }
                 else
                   System.arraycopy(bestpath, 0, newpath, 0, bestpath.length);
             }
           }
           T*=q;
             count++;
         }
      }
    
      public static void PrintPath() //打印最优解
    {
        System.out.println("共降温"+count+"次,得到的TSP最优距离为"+f2+"路径为");
         for(int j=0;j<N;j++)  
         {
           if (j==0) 
            System.out.print(bestpath[j]);
           else
             System.out.print("--->"+bestpath[j]);
         }
      }
    }
    
    

    ILS求解TSP的JAVA代码

    ILS分为MainRun,City,Delta,Perturbation,LocalSearch,Solution。

    主函数运行类,包括了ILS总方法。

    package ILSTSP;
    import static ILSTSP.City.*; 
    import static ILSTSP.Solution.*; 
    import static ILSTSP.Perturbation.*;
    import static ILSTSP.LocalSearch.*;
    
    /**
     * 主函数运行类,包括了ILS总方法以及计时功能。
     */
    
    public class MainRun {
      public static void main(String args[])
      {
        int max_iterations = 600;
        int max_no_improve = 50;
        long begintime = System.nanoTime();
    
        Solution best_solution=new Solution();
        iterated_local_search(best_solution, max_iterations, max_no_improve);
    
        System.out.println();
        System.out.println("搜索完成!最优路线总长度 = "+best_solution.cost);
        System.out.println("最优访问城市序列如下:" );
          for (int i = 0; i < CITY_SIZE;i++)
            System.out.print(best_solution.permutation[i]+"	");
    
          long endtime = System.nanoTime();
        double usedTime= (endtime - begintime)/(1e9);
        System.out.println();
        System.out.println("程序耗时:"+usedTime+"s");
      }
    
      static void iterated_local_search(Solution best_solution, int max_iterations, int max_no_improve)
      {
          Solution current_solution = new Solution();
    
          //获得初始随机解
          random_permutation(best_solution.permutation);
    
          best_solution.cost = cost_total(best_solution.permutation);
          local_search(best_solution, max_no_improve); //初始搜索
    
          for (int i = 0; i < max_iterations; i++)
          {
              perturbation(best_solution,current_solution); //扰动+判断是否接受新解
              local_search(current_solution, max_no_improve);//继续局部搜索
    
              //找到更优解
              if (current_solution.cost < best_solution.cost)
              {
                  for (int j = 0; j < CITY_SIZE; j++)
                  {
                      best_solution.permutation[j] = current_solution.permutation[j];
                  }
                  best_solution.cost = current_solution.cost;
              }
    
              System.out.println("迭代搜索 "+i+ " 次	" +
              "最优解 = "+ best_solution.cost+
              "当前解 = "+ current_solution.cost 
                  );
          }  
      }
    }
    
    

    City类存放城市数据,柏林52城坐标。

    package ILSTSP;
    
    import static java.lang.Math.*;
    
    /**
     * TSP数据类,
     * 存放柏林52城坐标,
     * 两点间距离计算distance_2city。
     */
    
    public class City {
    
      public static int CITY_SIZE=52;  // 城市数量
    
      public static int[][] cities=
        {
        { 565,575 },{ 25,185 },{ 345,750 },{ 945,685 },{ 845,655 },
        { 880,660 },{ 25,230 },{ 525,1000 },{ 580,1175 },{ 650,1130 },
        { 1605,620 },{ 1220,580 },{ 1465,200 },{ 1530,5 },
        { 845,680 },{ 725,370 },{ 145,665 },
        { 415,635 },{ 510,875 },{ 560,365 },{ 300,465 },{ 520,585 },{ 480,415 },
        { 835,625 },{ 975,580 },{ 1215,245 },{ 1320,315 },{ 1250,400 },{ 660,180 },
        { 410,250 },{ 420,555 },{ 575,665 },{ 1150,1160 },{ 700,580 },{ 685,595 },
        { 685,610 },{ 770,610 },{ 795,645 },{ 720,635 },{ 760,650 },{ 475,960 },
        { 95,260 },{ 875,920 },{ 700,500 },{ 555,815 },{ 830,485 },{ 1170,65 },
        { 830,610 },{ 605,625 },{ 595,360 },{ 1340,725 },{ 1740,245 }
        };
    
      public static double distance_2city(int[] c1,int[] c2)  //计算两点间距离 
    {
        double dis=0;
        dis=sqrt(pow(c1[0]-c2[0],2)+pow(c1[1]-c2[1],2));
        return dis;
      }
    }
    
    

    Solution类,存放解,即路径。

    package ILSTSP;
    
    import static java.lang.Math.*; 
    import static ILSTSP.City.*;
    
    public class Solution {
      public int[] permutation=new int[CITY_SIZE]; //城市排列
        public double cost;  
    
      //获取随机城市排列
        public static void random_permutation(int[] cities_permutation)
    {
            int n=CITY_SIZE;
          int[] temp=new int[CITY_SIZE];
          for(int i=0;i<CITY_SIZE;i++)
             temp[i]=i; 
          for(int i=0;i<CITY_SIZE-1;i++)
          {
            int r=(int) random()*n;
            cities_permutation[i]=temp[r];
            temp[r]=temp[n-1];
            n--;
          }
          cities_permutation[CITY_SIZE-1]=temp[0];
        }
    
        public static double cost_total(int[] cities_permutation)
    {
            double total_distance = 0;
            for (int i = 0; i < CITY_SIZE-1; i++)
                total_distance += distance_2city(cities[cities_permutation[i]], cities[cities_permutation[i+1]]);
            total_distance += distance_2city(cities[cities_permutation[0]], cities[cities_permutation[CITY_SIZE-1]]);
            //最后一个城市和第一个城市计算距离
            return total_distance;
        }
    }
    
    

    扰动类。

    package ILSTSP;
    import static ILSTSP.Solution.*; 
    import static ILSTSP.City.*;
    import static java.lang.Math.*; 
    
    /**
     * 扰动类,跳出局部。
     */
    
    public class Perturbation {
      //扰动
      public static void perturbation(Solution best_solution, Solution current_solution)
    {
          double_bridge_move(best_solution.permutation, current_solution.permutation);
          current_solution.cost = cost_total(current_solution.permutation);
      }
    
      //将城市序列分成4块,然后按块重新打乱顺序。
      //用于扰动函数
      private static void double_bridge_move(int[] cities_permutation, int[]  new_cities_permutation)
    {
        int[] pos=new int[5];
        pos[0]= 0; 
        pos[1]= (int) random()*(CITY_SIZE/3)+1;
          pos[2]= (int) (random()*(CITY_SIZE/3)+CITY_SIZE/3);
          pos[3]= (int) (random()*(CITY_SIZE/3)+(CITY_SIZE/3)*2);
        pos[4]= CITY_SIZE;
    
        int n=4;
        int[] random_order=new int[4],
            temp=new int[4];
        for(int i=0;i<4;i++)
           temp[i]=i; 
        for(int i=0;i<3;i++)
        {
          int r=(int) (random()*n);
          random_order[i]=temp[r];
          temp[r]=temp[n-1];
          n--;
        }
        random_order[3]=temp[0];
    
        int deadprotect=0;
        do
        {
          int i=0;
            for (int j=pos[random_order[0]];j<pos[random_order[0]+1];j++)
            {
              new_cities_permutation[i]=cities_permutation[j];
              i++;
            }
    
            for (int j=pos[random_order[1]];j<pos[random_order[1]+1];j++)
            {
              new_cities_permutation[i]=cities_permutation[j];
              i++;
            }
    
            for (int j=pos[random_order[2]];j<pos[random_order[2]+1];j++)
             {
              new_cities_permutation[i]=cities_permutation[j];
              i++;
            }
    
            for (int j=pos[random_order[3]];j<pos[random_order[3]+1];j++)
            {
              new_cities_permutation[i]=cities_permutation[j];
              i++;
            }
    
            deadprotect++;
            if (deadprotect==5) break;
    
          }while(AcceptanceCriterion(cities_permutation, new_cities_permutation));
      }
    
      //判断接受准则
      private static boolean AcceptanceCriterion(int[] cities_permutation, int[] new_cities_permutation)
    {
        int AcceptLimite=500;
        double c1=cost_total(cities_permutation);
        double c2=cost_total(new_cities_permutation)-AcceptLimite;
        if (c1<c2)
          return false;
        else
          return true;
      }
    }
    
    

    局部搜索类

    package ILSTSP;
    
    import static ILSTSP.City.*; 
    import static ILSTSP.Delta.*;
    
    /**
     * 局部搜索类,
     * 采用反转i到j之间城市序列的邻域动作。
     */
    
    public class LocalSearch {
      //本地局部搜索,边界条件 max_no_improve
      //best_solution最优解
      //current_solution当前解
      public static void local_search(Solution best_solution, int max_no_improve)
    {
          int count = 0;
          int i, k;
          double inital_cost = best_solution.cost; //初始花费
          double now_cost = 0;
    
          Solution current_solution = new Solution(); //为了防止爆栈……直接new了,你懂的
    
        for (i = 0; i < CITY_SIZE - 1; i++)
          {
             for (k = i + 1; k < CITY_SIZE; k++)
          {
            Delta[i][k] = calc_delta(i, k, best_solution.permutation);
              }
          }
    
          do
          {
              //枚举排列
              for (i = 0; i < CITY_SIZE - 1; i++)
              {
                  for (k = i + 1; k < CITY_SIZE; k++)
                  {
                      //邻域动作
                      two_opt_swap(best_solution.permutation, current_solution.permutation, i, k);
              now_cost = inital_cost + Delta[i][k];
                      current_solution.cost = now_cost;
                      if (current_solution.cost < best_solution.cost)
                      {
                          count = 0; //better cost found, so reset
                          for (int j = 0; j < CITY_SIZE; j++)
                          {
                              best_solution.permutation[j] = current_solution.permutation[j];
                          }
                          best_solution.cost = current_solution.cost;
                          inital_cost = best_solution.cost;
                          Update(i, k, best_solution.permutation);
                      }
                  }
              }
              count++;
          } while (count <= max_no_improve);
      }
    
      //邻域动作 反转index_i <-> index_j 间的元素
      private static void two_opt_swap(int[] cities_permutation, int[] new_cities_permutation, int index_i, int index_j)
    {
          for (int i = 0; i < CITY_SIZE; i++)
          {
              new_cities_permutation[i] = cities_permutation[i];
          }
    
          swap_element(new_cities_permutation, index_i, index_j);
      }
    
      //颠倒数组中下标begin到end的元素位置
      private static void swap_element(int[] p, int begin, int end)
    {
          int temp;
          while (begin < end)
          {
              temp = p[begin];
              p[begin] = p[end];
              p[end] = temp;
              begin++;
              end--;
          }
      }
    }
    
    

    Delta类,在原推文中未讲解,这里略微讲解一下。

    Delta实际上是对局部搜索的过程进行去重优化。

    Delta[i][j]中存放的数字表示反转i,j间路径后对总距离的改变量。(我们之前没有定义计算总距离的方法,因为这次改为了由Delta计算总距离)

    对calc_delta部分,每次翻转以后没必要再次重新计算Delta值,只需要在翻转的头尾做个小小处理。

    比如:

    有城市序列 1-2-3-4-5 总距离 = d_12 + d_23 + d_34 + d_45 + d_51 = A

    翻转后的序列 1-4-3-2-5 总距离 = d_14 + d_43 + d_32 + d_25 + d_51 = B

    由于 d_ij 与 d_ji是一样的,所以B也可以表示成 B = A – d_12 – d_45 + d_14 + d_25。

    对Update部分,每次翻转以后不需要依次更新所有Delta值,有一部分是可以忽略的。

    比如:

    对于城市序列1-2-3-4-5-6-7-8-9-10,如果对3-5应用了邻域操作2-opt (即翻转), 事实上对于Delta 1-2、6-10是不需要重复计算的。

    package ILSTSP;
    
    import static ILSTSP.City.*;
    
    /**
     * Delta类,
     * 对局部搜索的过程进行去重优化。
     * Delta[i][j]数组中存放的数字表示反转i,j间路径后对总距离的改变量。
     */
    
    public class Delta {
      public static double[][] Delta=new double[CITY_SIZE][CITY_SIZE];
    
      public static double calc_delta(int i, int k,  int[] tmp)
      {
        /*
                      以下计算说明:
                      对于每个方案,翻转以后没必要再次重新计算总距离
                      只需要在翻转的头尾做个小小处理
    
                      比如:
                      有城市序列   1-2-3-4-5 总距离 = d12 + d23 + d34 + d45 + d51 = A
                      翻转后的序列 1-4-3-2-5 总距离 = d14 + d43 + d32 + d25 + d51 = B
                      由于 dij 与 dji是一样的,所以B也可以表示成 B = A - d12 - d45 + d14 + d25
                      下面的优化就是基于这种原理
          */
          double delta=0;
          if((i==0)&&(k==CITY_SIZE-1))
             delta=0;
          else
        {
          int i2=(i-1+CITY_SIZE)%CITY_SIZE;
              int k2=(k+1)%CITY_SIZE; 
              delta = 0
                      - distance_2city(cities[tmp[i2]], cities[tmp[i]])
                      + distance_2city(cities[tmp[i2]], cities[tmp[k]])
                      - distance_2city(cities[tmp[k]], cities[tmp[k2]])
                      + distance_2city(cities[tmp[i]], cities[tmp[k2]]);
        }
          return delta;
      }
    
      public static void Update(int i, int k,  int[] tmp)
      {
        /*
        去重处理,对于Delta数组来说,对于城市序列1-2-3-4-5-6-7-8-9-10,如果对3-5应用了邻域操作2-opt , 事实上对于
        7-10之间的翻转是不需要重复计算的。所以用Delta提前预处理一下。
    
        当然由于这里的计算本身是O(1) 的,事实上并没有带来时间复杂度的减少(更新操作反而增加了复杂度) 
        如果delta计算 是O(n)的,这种去重操作效果是明显的。
          */
        if (i!=0 && k != CITY_SIZE - 1){
          i --; k ++;
          for (int j = i; j <= k; j ++){
            for (int l = j + 1; l < CITY_SIZE; l ++){
              Delta[j][l] = calc_delta(j, l, tmp);
            }
          }
    
          for (int j = 0; j < k; j ++){
            for (int l = i; l <= k; l ++){
              if (j >= l) continue;
              Delta[j][l] = calc_delta(j, l, tmp);
            }
          }
        }// 如果不是边界,更新(i-1, k + 1)之间的 
        else{
          for (i = 0; i < CITY_SIZE - 1; i++)
              {
                for (k = i + 1; k < CITY_SIZE; k++)
            {
              Delta[i][k] = calc_delta(i, k, tmp);
                }
              }  
        }// 边界要特殊更新 
      }
    }
    
    

    这次的代码是由刚学java的小白小编写的,翻译的过程中可能有一些处理不是很好,包括对类的分类等等。欢迎各位在留言区指正交流,小编虚心接受!

    好了,这次的java代码分享就到这里了。两段代码,容量不小。同鞋们可以分两次看。
    那么我们下次再见~

  • 相关阅读:
    pku2226 Muddy Fields
    pku3715 Blue and Red
    关于二分图的最大权匹配
    pku 2262&& pku 2739 && pku 3006
    pku2060 Taxi Cab Scheme
    pku 1486 Sorting Slides
    id、css命名规范
    Git 常用命令
    sublime text3插件使用
    Java实现数据结构栈stack和队列Queue
  • 原文地址:https://www.cnblogs.com/dengfaheng/p/12670160.html
Copyright © 2011-2022 走看看