前言
本实验报告为一csdn博客的详细注释版,保证网上Java实现SA TSP最详细的一篇。
改进之处包括且不限于如下几点:
1、数据集的详细获取办法;
2、代码每处都有注释,不懂的地方可以留言;
3、实验结果不同参数的比较分析。
一、实验目的
了解SA的思想;
了解模拟退火算法求解TSP问题是的算法结构;
体会SA的参数控制。
二、实验内容
(一)、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),所有的节点一次并且回到起始节点,使得连接这些节点的路径成本最低。
Symmetric traveling salesman problem (TSP)
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.
NonSymmetric traveling salesman problem (TSP)
略
(二)、模拟退火算法
模拟退火法是由 Metropolis 于1953 年提出的,是一种基于热力学原理的随机搜索算法
,是局部搜索算法
的拓展。它与局部搜索算法的不同之处在于:它以一定的概率选择邻域中目标函数值差的状态
。
退火是一种物理过程,一种金属物体在加热至一定的温度后,它的所有分子在其状态空间中自由运动。随着温度的下降,这些分子逐渐停留在不同的状态。在温度最低时,分子重新以一定的结构排列。统计力学的研究表明,在同一个温度,分子停留在能量最小的状态的概率比停留在能量大的状态的概率要大。当温度相当高时,每个状态的概率基本相同,都接近平均值。当温度趋向0时,分子停留在最低能量状态的概率趋向于1。
模拟退火算法是一种基于上述退火原理建立的随机搜索算法。组合优化问题与金属物体的退火过程可进行如下类比:组合优化问题的解类似于金属物体的状态,组合优化问题的最优解类似于金属物体的能量最低的状态,组合优化问题的费用函数类似于金属物体的能量。
为了克服局部搜索算法极易陷入局部最优解的缺点
,模拟退火算法使用基于概率的双方向随机搜索技术
:当基于邻域的一次操作使当前解的质量提高时,模拟退火算法接受这个被改进的解作为新的当前解;在相反的情况下,算法以一定的概率exp(-ΔC/T)接受相对于当前解来说质量较差的解作为新的当前解,其中Δc为邻域操作前后解的评价值的差,T为退火过程的控制参数(即温度)。模拟退火算法已在理论上被证明是一种以概率1收敛于全局最优解的全局优化算法。
模拟退火算法的实施步:
(三)、SA解TSP思路
具体看代码讲解
三、实验环境(数据来源)
在下面的JAVA实现中我们选择使用tsplib (http://comopt.ifi.uni-heidelb...)上的数据att48。
删除掉数据集头部,另存为data.txt。
四、程序源码与计算结果
这是一个对称TSP问题,城市规模为48,其最优值为10628.
代码介绍
an example of Simulated Annealing Agorithm to solve TSP
* 算法主要思路:
* 1、邻域变换函数:随机交换当前的两个元素。
* 2、评估函数:以当前方案的路程总和作为评估值。
* 数据集来源: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
* 算法主要思路:
* 1、邻域变换函数:随机交换当前的两个元素。
* 2、评估函数:以当前方案的路程总和作为评估值。
* 数据集来源: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为降温的次数。
参考
附录.ACM之家Java面向对象解法
http://www.acmerblog.com/simu...
附录.模拟退火算法通用类,改编自TSP
出处:http://www.codeforge.cn/read/...
/**
* 模拟退火算法
* @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));
}
}
附录.模拟退火算法的参数自动调整算法
有待研究
附录.人工智能算法求解TSP问题
http://blog.csdn.net/column/d...
基于蚁群算法求解求解TSP问题(JAVA)
基于GA求解求解TSP问题(JAVA)
基于粒子算法求解求解TSP问题(JAVA)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。