遗传算法 | Java版GA_TSP(我的第一个Java程序)


  嗯哼,第一次写博客,准确说是第一次通过文字的方式记录自己的工作,闲话少叙,技术汪的博客就该直奔技术主题(关于排版问题,会在不断写博客的过程中慢慢学习,先将就着用吧,重在技术嘛~~~)。

  遗传算法(Genetic Algorithm, GA),作为很多人接触智能优化算法的第一个算法,互联网上关于遗传算法的资料不可谓不多,但由于其不是本文的重点,故在此不过细展开,只简单说下大概思想:根据现代生物学理论 “物竞天择,适者生存” 原理,不断淘汰适应能力差的个体,模拟生物进化过程。大致步骤为:

  1. 生成一个初始种群(Initial Population);
  2. 通过计算种群中各个个体(Individual)的适应度(Fitness)大小来表示种群中各个个体的对环境的适应能力;
  3. 根据 “适者生存” 原则选择(Select)部分个体繁衍(即Cross、Mutation操作)生成子代种群;
  4. 判断是否满足进化结束条件(即算法终止条件). 若满足,结束进化,输出结果;否则,重复执行步骤2~3.

  [注]本文用于求解TSP的遗传算法并非经典遗传算法,而是针对TSP特征写的一个简化版的遗传算法,求解质量比较low,毕竟是第一个java程序,还是要预留些改进空间么不是~~~,下一篇文章将针对本文所用的遗传算法进行改进,给个传送门(Java版GA_TSP (2))。

  旅行商问题(Travel Salesman Problem, TSP)作为运筹学分支组合优化中的经典问题,一直备受关注。大白话的说法是:一个旅行商从某一城市出发,希望以最短的路程完成对N个城市的巡回访问,并最终回到出发城市。专业一点的说法是:在有向图中,从某一点开始,以最小代价不重不漏的遍历所有点,并返回初始点。

  关于Java必须多说两句。首先,Java语言是新学的,之前一直用的MATLAB,然而实验室的工作站带MATLAB还可以,自己的破笔记本实在带不动。感觉每次用MATLAB运行代码,我的小本都会经历一场生死,从点击“Run” 360小球噌的一下窜上95%那一瞬间,仿佛全世界就是剩下了小本呼哧呼哧的挣扎。

  本着人道主义精神,终于在某个深夜卸载了MATLAB,顿时感觉小本的重量都轻了许多(可能是幻觉吧)。MATLAB是卸载了,但毕业还要写代码啊,总不能不毕业吧,所以之后选用Python试了试,相比动辄十几个G的MATLAB,几十M的Python用起来确实不错,在加上Python语言本身确实简单,对代码汪想法的快速程序表达非常友好,以及那句“人生苦短,我用Python”,使得我一度认为Python应该就是我的最终选择了,但是……但是……但是用Python跑了GA&TSP后我就动摇了,运行速度着实堪忧,可能是自己技术太渣,即使使用Pypy也不能达到我的预期,而作为对算法实现效率有着苛刻要求的组合优化问题,为了把其他论文中的求解结果给PK下去,所以……所以……所以我又抛弃了Python。

  之所以没有选用高效率的且大一就学过的C语言,则是因为实在搞不定C语言的内存管理,对于指针的使用更是随心而动,致使最后Debug的时候就像二哈咬刺猬,都不知道从哪下口……

  嗯哼,然后就是Java了,选择Java就是在使用了排除法后的选择,兼顾了我笔记本的性能缺陷与运行效率。说了这么多,终于到达战场,干货出场:

遗传算法示意图

  下面上代码:

  Data类:用来放客户点的坐标,也可以用I/O流从外部文件中获取。

 1 package GA;  2 
 3 public class Data {  4     public static double[][] XY(){  5         double [][] xy = new double [][] {  6             {    1304    ,    2312 } ,  7             {    3639    ,    1315 } ,  8             {    4177    ,    2244 } ,  9             {    3712    ,    1399 } , 10             {    3488    ,    1535 } , 11             {    3326    ,    1556 } , 12             {    3238    ,    1229 } , 13             {    4196    ,    1004 } , 14             {    4312    ,    790 } , 15             {    4386    ,    570 } , 16             {    3007    ,    1970 } , 17             {    2562    ,    1756 } , 18             {    2788    ,    1491 } , 19             {    2381    ,    1676 } , 20             {    1332    ,    695 } , 21             {    3715    ,    1678 } , 22             {    3918    ,    2179 } , 23             {    4061    ,    2370 } , 24             {    3780    ,    2212 } , 25             {    3676    ,    2578 } , 26             {    4029    ,    2838 } , 27             {    4263    ,    2931 } , 28             {    3429    ,    1908 } , 29             {    3507    ,    2367 } , 30             {    3394    ,    2643 } , 31             {    3439    ,    3201 } , 32             {    2935    ,    3240 } , 33             {    3140    ,    3550 } , 34             {    2545    ,    2357 } , 35             {    2778    ,    2826 } , 36             {    2370    ,    2975 } 37  }; 38         return xy; 39  } 40 }

 

  DistanceMatrix类:用来计算各客户点间的欧氏距离。原打算也放在GATSP类中,但考虑到有些问题中距离矩阵是给定的,而不是通过公式计算得到的,就单独拿出来了。

 1 package GA;  2 
 3 public class DistanceMatrix {  4     public static double[][] DistMatrix(double[][] xy){  5         //计算距离矩阵
 6         int N = xy.length;  7         double[][] DM = new double[N][N];  8         for (int i = 0; i < N; i++) {  9             for (int j = 0; j < N; j++) { 10                 DM[i][j] = Math.hypot(xy[i][0] - xy[j][0],xy[i][1] - xy[j][1]); 11  } 12  } 13         return DM; 14  } 15 }

  

  Pop类:用来放置对Pop的操作方法,包括计算适应度的fit()函数(貌似这个命名不规范,但是我懒呀,我就不改,你能怎样???)和寻找并记录最有个体的Max()函数(写这个函数以及初始化种群的时候非常怀恋MATLAB,MATLAB中自带的的max()能够同时获取最大值的索引,初始化种群就更简单了,一个randperm()解决所有问题。)

 

 1 package GA;  2 
 3 public class Pop {  4     
 5     public static double fit(int[] Ind) {  6         //计算适应度函数
 7         double[][] xy = Data.XY();  8         double[][] DM = DistanceMatrix.DistMatrix(xy);  9         double dist = 0.0; 10         int s = Ind.length; 11         
12         for (int i0 = 0; i0 < s - 1; i0++) { 13             dist += DM[Ind[i0] - 1][Ind[i0 + 1] - 1]; 14  } 15         dist += DM[Ind[Ind.length - 1] - 1][Ind[0] - 1]; 16         return 1/dist; 17  } 18     
19     public static double[] Max(double[] Fit) { 20         //寻找最优个体及相应的位置
21         double[] max = new double[2]; 22         double maxFit = 0.0; 23         double maxIndex = -1; 24         for (int i = 0; i < Fit.length; i++) { 25             if (Fit[i] > maxFit) { 26                 maxFit = Fit[i]; 27                 maxIndex = (double)i; 28  } 29  } 30         max[0] = maxFit; 31         max[1] = maxIndex; 32         return max; 33  } 34 }

 

  Sharking类:存放扰动算子,想着以后其他智能算法中也能用得着,就一次性造好轮子,以后就能直接用了,写这几个扰动算子的时候也想夸夸MATLAB强大的轮子库, 如fliplr() 就是很好的轮子,还有对数组的各种切割、拼接神奇操作,让我只能一边痛苦的造着轮子,一边默念“效率第一,Java无敌”……

 

 1 package GA;  2 
 3 import java.util.Random;  4 
 5 public class Sharking {  6     
 7     public static int[] Swap(int[] S) {  8         //交换操作
 9         
10         Random rand = new Random(); 11         int I = rand.nextInt(S.length); 12         int J = rand.nextInt(S.length); 13         
14         int tmp = S[I]; 15         S[I] = S[J]; 16         S[J] = tmp; 17         
18         return S; 19  } 20     
21     public static int[] Flip(int[] S) { 22         //翻转操作
23         
24         int[] S0 = new int [S.length]; 25         
26         Random rand = new Random(); 27         int tmpI = rand.nextInt(S.length); 28         int tmpJ = tmpI; 29         while(tmpI==tmpJ) { 30             tmpJ = rand.nextInt(S.length); 31  } 32         int I = Math.min(tmpI, tmpJ); 33         int J = Math.max(tmpI, tmpJ); 34     
35         for (int i = 0; i < S0.length;i++) { 36             if (i >= I && i <= J) { 37                 S0[i] = S[I+J-i]; 38             }else { 39                 S0[i] = S[i]; 40  } 41  } 42         return S0; 43  } 44     
45     public static int[] Insert(int[] S) { 46         //插入操作
47         
48         int[] S0 = new int [S.length]; 49         
50         Random rand = new Random(); 51         int tmpI = rand.nextInt(S.length); 52         int tmpJ = tmpI; 53         while(tmpI==tmpJ) { 54             tmpJ = rand.nextInt(S.length); 55  } 56         int I = Math.min(tmpI, tmpJ); 57         int J = Math.max(tmpI, tmpJ); 58         
59         for (int i = 0; i < S0.length;i++) { 60             if (i >= I && i < J) { 61                 S0[i] = S[i+1]; 62             }else if(i==J){ 63                 S0[i] = S[I]; 64             }else{ 65                 S0[i] = S[i]; 66  } 67  } 68         return S0; 69  }
72 }

 

  GATSP类:本来准备将算法单独弄一个类,后来……后来……我懒呀、我懒呀…, 所以现在的这个类又臭又长,既包括算法,也包括其他轮子没有干的所有事情,比如说结果输出。

 1 package GA;  2 
 3 import java.util.Random;  4 
 5 public class GATSP {  6     
 7     public static double[] Cusume(double[] A) {  8         //适应于轮盘赌的累加器
 9         double[] cus = new double[A.length+1];  10         
 11         cus[0]  = 0.0;  12         for (int i = 0; i < A.length; i++) {  13             cus[i+1] = cus[i] + A[i];  14  }  15         return cus;  16  }  17     
 18     public static double Sum(double[] Arr) {  19         //求和
 20         double sum = 0.0;  21         for (int i = 0;i <Arr.length;i++) {  22             sum += Arr[i];  23  }  24         return sum;  25  }  26     
 27     
 28     
 29     public static void main(String[] args) {  30         
 31         long startTime=System.currentTimeMillis();  32         
 33         //参数列表  34         //31城市TSP最优解15377.711
 35         int MaxGen = 500;  36         int PopSize =200;  37         double[][] xy = Data.XY();  38         int N = xy.length;  39         
 40         double[][] DM = DistanceMatrix.DistMatrix(xy);  41         int[][] Pop = new int[PopSize][N];  42         double[] Trace = new double[MaxGen];  43         Pop nowPop = new Pop();  44         double bs = 1e10;  45         int[] BS = new int[N];  46         
 47         
 48         //生成初始种群
 49         for (int p = 0; p < PopSize; p++) {  50             for (int j = 0; j < N;j++) {  51                 Pop[p][j] = j + 1;  52  }  53             //随机生成初始个体
 54             for (int k = 0; k < N;k++) {  55                 Random rand = new Random();  56                 int r = rand.nextInt(N);  57                 int tmp;  58                 tmp = Pop[p][r];  59                 Pop[p][r] = Pop[p][k];  60                 Pop[p][k] = tmp;  61  }  62  }  63         
 64         //进入迭代
 65         for (int gen = 0; gen < MaxGen;gen++) {  66             // 计算种群适应度
 67             double[] Fit = new double[PopSize];  68             int[] indiv = new int[N];  69             
 70             for (int p = 0; p < PopSize;p++) {  71                 //取一个个体
 72                 for (int j = 0; j < N;j++) {  73                     indiv[j] = Pop[p][j];  74  }  75                 Fit[p] = nowPop.fit(indiv);  76  }  77             
 78             //更新最优个体以及最优个体的适应度
 79             double[] SortAfterFit = new double[PopSize];//拷贝一份适应度数组
 80             for (int i = 0; i < PopSize;i++) {  81                 SortAfterFit[i] = Fit[i];  82  }  83             double[] BestS = nowPop.Max(Fit);  84             double tmpbs = 1/BestS[0];            //当前代最优解(最优个体的适应度)
 85             if (tmpbs < bs) {  86                 bs = tmpbs;  87                 int BestIndex = (int)BestS[1];  88                 for (int i = 0; i < N; i++) {  89                     BS[i] =Pop[BestIndex][i];            //最优个体
 90  }  91  }  92             Trace[gen] = bs;  93             
 94             //轮盘赌选择
 95             double[] cusFit0 = Cusume(Fit);//数组长度变为N+1,补充了首个元素0.0
 96             double sumFit = Sum(Fit);  97                 //归一化
 98             double[] cusFit = new double[cusFit0.length];  99             for (int i = 0; i < cusFit.length; i++) { 100                 cusFit[i] =cusFit0[i] / sumFit; 101  } 102             
103             int[][] newPop = new int[PopSize][N]; 104             for (int q = 0;q < N; q++) { 105                  newPop[0][q] = BS[q]; 106  } 107             for (int p = 1; p < PopSize; p++) { 108                 double rand = Math.random(); 109                 for (int r = 0; r < PopSize; r++) { 110                     if (rand > cusFit[r] && rand <= cusFit[r+1]) { 111                         for (int q = 0;q < N; q++) { 112                             newPop[p][q] = Pop[r][q]; 113  } 114  } 115  } 116  } 117             
118             //扰动操作
119             for (int p = 0; p < PopSize; p++) { 120                 double R = Math.random(); 121                 
122                 int[] S = new int[N]; 123                 for (int i = 0; i < N; i++) { 124                     S[i] = newPop[p][i]; 125  } 126                 
127                 int[] S0 = new int[N]; 128                 if (R < 0.33) { 129                     S0 = Sharking.Swap(S); 130                 }else if (R > 0.67) { 131                     S0 = Sharking.Insert(S); 132                 }else { 133                     S0 = Sharking.Flip(S); 134  } 135                 
136                 for (int i = 0; i < N; i++) { 137                     newPop[p][i] = S0[i]; 138  } 139  } 140             
141             //更新种群
142             for (int p = 0; p < PopSize; p++) { 143                 for (int i = 0; i < N; i++) { 144                     Pop[p][i] = newPop[p][i]; 145  } 146  } 147         }//结果迭代
148         
149         long endTime=System.currentTimeMillis(); 150         //结果输出
151         System.out.println("经过"+MaxGen+"次迭代,最短路径长度为:"+bs); 152         System.out.println("程序用时 "+(double)(endTime - startTime)/1000+"秒."); 153         double bs0 = 15377.711; 154         System.out.println("与最优解的误差为"+(bs-bs0)/bs0*100+"%."); 155         for (int i = 0; i < MaxGen; i++) { 156             System.out.println(i+1+"  "+Trace[i]); 157  } 158         
159         System.out.println("相应的最短路径为"); 160         for (int i = 0; i < N; i++) { 161             System.out.print(BS[i]+"->"); 162  } 163         System.out.print(BS[0]); 164  } 165 }

 

  本来剧情发展到这里,应该是求解结果展示,然后就可以领盒饭了,无奈结果太渣,不忍心拿出来,所以剧情稍微转下,多说几句关于MATLAB与Java:

  本来应该还有一个PlotFigure类,然而Java学的不精,暂时还不会用,说到这里,又怀恋起MATLAB来,其强大的可视化结果能力,确实很有吸引力,想想之前只要用MATLAB写上几行代码就可以在每次跑完程序有一堆酷酷的图出来,比如说算法迭代收敛图、最优路径图什么的,现在只有“28->23->29->19->……”这种结果,心里实在不是很平衡,所以,我决定……下次学习下Java的绘图。

Ps:还是截图纪念下第一次Java写遗传算法~~~

 

 

  [完]

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM