嗯哼,第一次写博客,准确说是第一次通过文字的方式记录自己的工作,闲话少叙,技术汪的博客就该直奔技术主题(关于排版问题,会在不断写博客的过程中慢慢学习,先将就着用吧,重在技术嘛~~~)。
遗传算法(Genetic Algorithm, GA),作为很多人接触智能优化算法的第一个算法,互联网的资料不可谓不多,但不是本文的重点,故在此不再过细展开,只简单说下大概思想:根据现代生物学理论 “物竞天择,适者生存” 原理,不断淘汰适应能力差的个体,模拟生物进化过程。大致步骤为:
[注]本文用于求解TSP的遗传算法并非经典遗传算法,而是针对TSP特征写的一个简化版的遗传算法,求解质量比较low,毕竟是第一个java程序,还是要预留写改进空间么不是~~~
旅行商问题(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类:用来计算各客户点间的欧氏距离。原打算也放在GSTSP类中,但考虑到有些问题中距离矩阵是给定的,而不是通过公式计算得到的,就单独拿出来了。
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 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 = 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写遗传算法~~~
[完]
原文:https://www.cnblogs.com/Alex-Xu-OR/p/9458239.html