本实验报告为一csdn博客的详细注释版,保证网上Java实现SA TSP最详细的一篇。
改进之处包括且不限于以下几点:
一、数据集的详细获取办法;
二、代码每处都有注释,不懂的地方能够留言;
三、实验结果不一样参数的比较分析。html
了解SA的思想;
了解模拟退火算法求解TSP问题是的算法结构;
体会SA的参数控制。java
TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每一个城市只能拜访一次,并且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为全部路径之中的最小值。node
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}是全部城市之间链接的成本度量(通常为城市之间的距离);dom
若是Crs = Csr, 那么该TSP问题为对称的,不然为非对称的。ide
一个TSP问题能够表达为:函数
求解遍历图G = (V, E, C),全部的节点一次而且回到起始节点,使得链接这些节点的路径成本最低。oop
Given a set of n nodes and distances for each pair of nodes, find a roundtrip of minimal total length visiting each node exactly once. The distance from node i to node j is the same as from node j to node i.性能
略优化
模拟退火法是由 Metropolis 于1953 年提出的,是一种基于热力学原理的随机搜索算法
,是局部搜索算法
的拓展。它与局部搜索算法的不一样之处在于:它以必定的几率选择邻域中目标函数值差的状态
。
退火是一种物理过程,一种金属物体在加热至必定的温度后,它的全部分子在其状态空间中自由运动。随着温度的降低,这些分子逐渐停留在不一样的状态。在温度最低时,分子从新以必定的结构排列。统计力学的研究代表,在同一个温度,分子停留在能量最小的状态的几率比停留在能量大的状态的几率要大。当温度至关高时,每一个状态的几率基本相同,都接近平均值。当温度趋向0时,分子停留在最低能量状态的几率趋向于1。
模拟退火算法是一种基于上述退火原理创建的随机搜索算法。组合优化问题与金属物体的退火过程可进行以下类比:组合优化问题的解相似于金属物体的状态,组合优化问题的最优解相似于金属物体的能量最低的状态,组合优化问题的费用函数相似于金属物体的能量。
为了克服局部搜索算法极易陷入局部最优解的缺点
,模拟退火算法使用基于几率的双方向随机搜索技术
:当基于邻域的一次操做使当前解的质量提升时,模拟退火算法接受这个被改进的解做为新的当前解;在相反的状况下,算法以必定的几率exp(-ΔC/T)接受相对于当前解来讲质量较差的解做为新的当前解,其中Δc为邻域操做先后解的评价值的差,T为退火过程的控制参数(即温度)。模拟退火算法已在理论上被证实是一种以几率1收敛于全局最优解的全局优化算法。
模拟退火算法的实施步:
具体看代码讲解
在下面的JAVA实现中咱们选择使用tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的数据att48。
删除掉数据集头部,另存为data.txt。
这是一个对称TSP问题,城市规模为48,其最优值为10628.
an example of Simulated Annealing Agorithm to solve TSP * 算法主要思路: * 一、邻域变换函数:随机交换当前的两个元素。 * 二、评估函数:以当前方案的路程总和做为评估值。 * 数据集来源:tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的数据att48去除头部。 * 理想计算结果:10628 ,未取得。 * 最佳计算结果10653时的参数选择: * 外层迭代:1000 * 内层迭代次数:4000 * 降温系数:0.991 * 初始温度:1000 * 名词解释 * 伪欧式距离:....
import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.Random; /** * an example of Simulated Annealing Agorithm to solve TSP * 算法主要思路: * 一、邻域变换函数:随机交换当前的两个元素。 * 二、评估函数:以当前方案的路程总和做为评估值。 * 数据集来源:tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的数据att48去除头部。 * 理想计算结果:10628 ,未取得。 * 最佳计算结果10653时的参数选择: * 外层迭代:1000 * 内层迭代次数:4000 * 降温系数:0.991 * 初始温度:1000 * 名词解释 * 伪欧式距离:.... */ public class SimulatedAnnealingTSP { private int cityNum; // 城市数量,编码长度 private int N;// 每一个温度迭代步长 private int T;// 降温次数 private float a;// 降温系数 private float t0;// 初始温度, 尽量大,最后结果与初始温度无关 private int[][] distance; // 距离矩阵 private int bestT;// 最佳出现代数 private int[] Ghh;// 初始路径编码,当前编码(相对于temGhh 是上次编码) private int GhhEvaluation;//评估值 private int[] bestGh;// 最好的路径编码 private int bestEvaluation;//最好的路径编码的评估值 private int[] tempGhh;// 存放临时编码 private int tempEvaluation;//评估值 private Random random; /** * constructor * * @param cn * 城市数量 * @param t * 降温次数 * @param n * 每一个温度迭代步长 * @param tt * 初始温度 * @param aa * 降温系数 * **/ public SimulatedAnnealingTSP(int cn, int n, int t, float tt, float aa) { cityNum = cn; N = n; T = t; t0 = tt; a = aa; } @SuppressWarnings("resource")// 给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默 /** * 读取数据集,初始化cityNum, distance[][], * @param filename 数据文件名,该文件存储全部城市节点坐标数据 * @throws IOException */ private void init(String filename) throws IOException { // 读取数据 int[] x; int[] y; String strbuff; BufferedReader data = new BufferedReader(new InputStreamReader( new FileInputStream(filename))); distance = new int[cityNum][cityNum]; x = new int[cityNum]; y = new int[cityNum]; for (int i = 0; i < cityNum; i++) { // 读取一行数据,数据格式1 6734 1453 (编号 x坐标 y坐标) strbuff = data.readLine(); // 字符分割 String[] strcol = strbuff.split(" "); x[i] = Integer.valueOf(strcol[1]);// x坐标 y[i] = Integer.valueOf(strcol[2]);// y坐标 } // 计算距离矩阵 // 针对具体问题,距离计算方法也不同,此处用的是att48做为案例,它有48个城市,距离计算方法为伪欧氏距离,最优值为10628 for (int i = 0; i < cityNum - 1; i++) { distance[i][i] = 0; // 对角线为0 for (int j = i + 1; j < cityNum; j++) { double rij = Math .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j])) / 10.0); //小数部分进位, 1.1-> 2 1.7->2 int tij = (int) Math.round(rij);// 四舍五入 if (tij < rij) { distance[i][j] = tij + 1; distance[j][i] = distance[i][j]; } else { distance[i][j] = tij; distance[j][i] = distance[i][j]; } } } distance[cityNum - 1][cityNum - 1] = 0; Ghh = new int[cityNum]; bestGh = new int[cityNum]; bestEvaluation = Integer.MAX_VALUE;//设置初始值位最大 tempGhh = new int[cityNum]; tempEvaluation = Integer.MAX_VALUE;//设置初始值位最大 bestT = 0;//迭代歩数 random = new Random(System.currentTimeMillis()); System.out.println("城市数:"+ cityNum+","+ "每一个温度迭代步长(内层迭代上限):"+N+","+ "降温次数(外层迭代上限):"+T+","+ "降温系数:"+a+","+ "初始温度:"+t0); } /** * 初始化编码Ghh * 随机产生一组城市号 0到cityNum的序列 * 能够用任意方式指定,此处算法中的二重循环我没有看懂 */ void initGroup() { int i, j; Ghh[0] = random.nextInt(65535) % cityNum; for (i = 1; i < cityNum;)// 编码长度 { Ghh[i] = random.nextInt(65535) % cityNum; for (j = 0; j < i; j++) { if (Ghh[i] == Ghh[j]) { break; } } if (j == i) { i++; } } } // 复制编码体,复制编码Gha到Ghb public void copyGh(int[] Gha, int[] Ghb) { for (int i = 0; i < cityNum; i++) { Ghb[i] = Gha[i]; } } /** * 估计函数 * @param chr 路径编码,起始城市,城市1,城市2...城市n * @return 路径总长度 */ public int evaluate(int[] chr) { // 0123 int len = 0; // 计算路径总长度 for (int i = 1; i < cityNum; i++) { len += distance[chr[i - 1]][chr[i]]; } // 加上从最后一个城市回到出发城市的路程 len += distance[chr[cityNum - 1]][chr[0]]; return len; } /** * 邻域交换,获得当前编码Ghh的邻域编码tempGhh * 随机交换Ghh的两个编码位 */ public void Linju(int[] Gh, int[] tempGh) { int i, temp; int ran1, ran2; //copy for (i = 0; i < cityNum; i++) { tempGh[i] = Gh[i]; } //随机生成两个不一样的编码位下标 ran1 = random.nextInt(65535) % cityNum; ran2 = random.nextInt(65535) % cityNum; while (ran1 == ran2) { ran2 = random.nextInt(65535) % cityNum; } //交换两个编码位 temp = tempGh[ran1]; tempGh[ran1] = tempGh[ran2]; tempGh[ran2] = temp; } public void solve() { // 初始化编码Ghh initGroup(); copyGh(Ghh, bestGh);// 复制当前编码Ghh到最好编码bestGh bestEvaluation = evaluate(Ghh); GhhEvaluation = bestEvaluation; int k = 0;// 降温次数 int n = 0;// 迭代步数 float t = t0; float r = 0.0f; while (k < T) {//T位降温次数上限 n = 0; while (n < N) { Linju(Ghh, tempGhh);// 获得当前编码Ghh的邻域编码tempGhh tempEvaluation = evaluate(tempGhh);//评估当前路劲,结果为总的路程 if (tempEvaluation < bestEvaluation)//if the temp one is better { copyGh(tempGhh, bestGh);//copy tempGhh to bestGh bestT = k; bestEvaluation = tempEvaluation; } //根据Metropolis准则判断是否接受当前解 r = random.nextFloat();// 返回值属于[0,1] if (tempEvaluation < GhhEvaluation || Math.exp((GhhEvaluation - tempEvaluation) / t) > r) {// t = current temperature copyGh(tempGhh, Ghh); GhhEvaluation = tempEvaluation; } n++; } t = a * t; k++; } System.out.println("这次运行的最佳长度出现代数:"); System.out.println(bestT); System.out.println("这次运行的最佳长度(理想值为10628,历史最佳结果10653)"); System.out.println(bestEvaluation); System.out.println("这次运行的最佳路径:"); for (int i = 0; i < cityNum; i++) { System.out.print(bestGh[i] + ","); if (i % 10 == 0 && i != 0) { System.out.println(); } } } /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { System.out.println("Start...."); SimulatedAnnealingTSP sa = new SimulatedAnnealingTSP(48, 4000, 1000, 10000.0f, 0.992f); sa.init("f://data.txt"); sa.solve(); } }
比较三次运行结果,发现结果不稳定,且与最优值差距很大。问题的最优值为10628。
提升内层迭代次数,结果更加接近最优解。
提升降温系数到0.99,结果很是接近最优解。这也是数十次运行中最佳的一次运算结果。
调整降温系数到0.999,结果反而出现异常,降温系数不是越接近1越好
。
最佳的一次计算结果以下:
模拟算法其特色是在开始搜索阶段解的质量提升比较缓慢,可是到了迭代后期,它的解的质量提升明显,因此若是在求解过程当中,对迭代步数限制比较严格的话,模拟退火算法在有限的迭代步数内很可贵到高质量的解。整体而言模拟退火算法比较适合用于有充足计算资源的问题求解。
模拟退火算法的应用很普遍,能够求解NP彻底问题,但其参数难以控制,其主要问题有如下三点:
(1) 温度T的初始值设置问题。
温度T的初始值设置是影响模拟退火算法全局搜索性能的重要因素之一。初始温度越高,则搜索到全局最优解的可能性大,但所以要花费大量的计算时间;反之,则可节约计算时间,但全局搜索性能可能受到影响。实际应用过程当中,初始温度通常须要依据实验结果进行若干次调整。
(2) 退火速度问题。
模拟退火算法的全局搜索性能也与退火速度密切相关。通常来讲,同一温度下的“充分”搜索(退火)是至关必要的,但这须要计算时间。实际应用中,要针对具体问题的性质和特征设置合理的退火平衡条件。这个是与内外层迭代次数有关吗?
(3) 温度管理问题。
温度管理问题也是模拟退火算法难以处理的问题之一。实际应用中,因为必须考虑计算复杂度的切实可行性等问题,常采用以下所示的降温方式:T(t+1)=k×T(t)
式中k为正的略小于1.00的常数,t为降温的次数。
http://www.acmerblog.com/simulated-annealing-algorithm-tsp-5841.html
出处:http://www.codeforge.cn/read/126652/SACompute.java__html
/** * 模拟退火算法 * @author acer * @param <T> */ public abstract class SimulatedAnnealingTemplate<T> implements Runnable { private T initAnswer; private T resultAnswer; public SimulatedAnnealingTemplate(T initAnswer) { this.initAnswer = initAnswer; } /** * 获取初始温度 * * @return */ public abstract double getInitTemplate(); /** * 获取临近解 * * @param current * @param nowTemperature * @param nowExternalIterateNumber * @param nowInnerIterateNumber * @return */ public abstract T getNearbyAnser(T current, double nowTemperature, int nowExternalIterateNumber, int nowInnerIterateNumber); /** * 获取两个解的评价值之差 * * @param answer1 * @param answer2 * @return */ public abstract double getDeltaValue(T answer1, T answer2); /** * 是否退出内部循环 * * @param nowInnerIterateNumber * @return */ public abstract boolean exitInnerLoop(int nowInnerIterateNumber); /** * 是否退出外部循环 * * @param nowExternalIterateNumber * @return */ public abstract boolean exitExternalLoop(int nowExternalIterateNumber, double nowTemperature); /** * 外部循环降温 * * @param nowTemperature * @param nowExternalIterateNumber * @return */ public abstract double countDownTemperature(double nowTemperature, int nowExternalIterateNumber); /** * 开始运行 */ public void run() { double nowTemperature = getInitTemplate(); int nowExternalIterNumber = 0; int nowInnerIterNumber = 0; resultAnswer = initAnswer; while (true) { double deltatotaldis = 0.0; while (true) { T nearbyAnswer = getNearbyAnser(resultAnswer, nowTemperature, nowExternalIterNumber, nowInnerIterNumber); // 从某路径的邻域中随机选择一个新的路径,邻域映射为2-opt deltatotaldis = getDeltaValue(nearbyAnswer, resultAnswer); // 计算新路径与当前路径的路程长度差值 if (deltatotaldis <= 0.0) resultAnswer = nearbyAnswer; // 若是新路径的路程短,则用它替换当前路径 else { double chgprobability = Math .exp(-(deltatotaldis / nowTemperature)); double random = Math.random(); if (chgprobability > random) resultAnswer = nearbyAnswer; // 若是新路径长于当前路径,但exp(-Δf/t) > random(0,1),则仍然替换当前路径 } if (exitInnerLoop(nowInnerIterNumber)) break; // 判断内循环是否结束,结束则跳出当前温度的内循环 else nowInnerIterNumber++; // 判断内循环是否结束,不结束则继续内循环 } if (exitExternalLoop(nowExternalIterNumber, nowTemperature)) break; // 判断外循环是否结束,结束则结束模拟退火计算 else { nowTemperature = countDownTemperature(nowTemperature, nowExternalIterNumber); nowExternalIterNumber++; nowInnerIterNumber = 0; // 判断外循环是否结束,不结束则计算出降低后的温度,并继续循环 } } } public static void main(String[] args) { System.out.println(Math.exp(-5)); } }
有待研究
http://blog.csdn.net/column/details/mio-algorithms.html
基于蚁群算法求解求解TSP问题(JAVA)
基于GA求解求解TSP问题(JAVA)
基于粒子算法求解求解TSP问题(JAVA)