粒子在某个温度 T 时,固体所处的状态具有一定的随机性,而这些状态之 间的转换能否实现由 Metropolis 准则决定。设从当前状态 i 生成新状态 j,若新 状态的内能(Ej)小于状态 i 的内能 (Ei),则接受新状态 j 作为新的当前状态; 否则,以概率 exp[-(Ej-Ei)/KT] 接受状态 j,其中 K 为 Boltzmann 常数,这就是通 常所说的 Metropolis 准则。随着 T 的降低,接收的概率越低。系统趋于稳定状态。
我事先找到了 31 个城市的坐标信息,根据坐标信息计算出两两城市之间的 距离信息。整个算法的流程图如下:
import sun.rmi.runtime.Log;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author Jiao Tiancai
* @version 2.0
* @description: 通过改进邻域函数的方法,优化了模拟退火算法的性能,求得的解更加接近最优解
* @date 2021-4-9 15:49
*/
public class SA_TSP {
private Float T0 = 5000f; //SA初始温度
private float T_End = 1e-10f;//结束温度
private float delta = 0.98f; //SA降温系数
private Float fitness; //适应值
private ArrayList<Integer> ans = new ArrayList<Integer>();//当前解
private ArrayList<ArrayList<Integer>> citys;//城市坐标
private ArrayList<ArrayList<Float>> distance;//两两城市之间的距离
private Random random = new Random();
private int K = 100;//每个温度的迭代次数
private int cnt = 0; //降温次数
private ArrayList<Float> disPic = new ArrayList<Float>();
/**
* @description: 构造函数,初始化初始温度,降温系数,城市矩阵
* @param: [t0, delta, citys]
* @return:
* @author Jiao Tiancai
* @date: 2021-4-9 16:20
*/
public SA_TSP(float t0, float delta, ArrayList<ArrayList<Integer>> citys) {
T0 = t0;
this.delta = delta;
this.citys = citys;
}
/**
* @param: [citys] 读取Excel或者TXT,更新城市位置信息
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 17:41
*/
public static void updateLoc(List<ArrayList<Integer>> citys) throws IOException {
File file = new File("src/data/citys.txt");
if (!file.exists())
throw new RuntimeException("Not File!");
BufferedReader br = new BufferedReader(new FileReader(file));
String str = ""; //读取每一行的值
String[] Location;//每一个城市的坐标
ArrayList<Integer> Loc;
while ((str = br.readLine()) != null) {
Loc = new ArrayList<Integer>();
Location = str.split("\\s+");
Loc.add(Integer.parseInt(Location[0]));
Loc.add(Integer.parseInt(Location[1]));
citys.add(Loc);
}
}
/**
* @description: 解决模拟退火——TSP问题
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 17:45
*/
public void solve() {
//更新城市两点之间的距离信息
updateDis();
//先用贪心算法初始化第一个解
initAnsByGreedy();
//温度
float t = T0;
//适应值,用总距离算
fitness = 0f;
//保存新的解
ArrayList<Integer> ansNew;
Float oldDIS;
//新的解的距离
Float newDIS;
//这次的距离减去上次的距离
Float de;
while (t > T_End) {
for (int i = 0; i < K; i++) {
ansNew = createNewAns();
oldDIS = getRouteDis(ans);
newDIS = getRouteDis(ansNew);
de = newDIS - oldDIS;
fitness = oldDIS;
if (de < 0) {
ans = ansNew;
fitness = newDIS;
} else {
if (Math.pow(Math.E, -de / t) > random.nextFloat()) {
ans = ansNew;
fitness = newDIS;
}
}
}
disPic.add(fitness);
t = t * delta;
//t = (float) (T0/ Math.log(1+t));
cnt += 1;
}
}
/**
* @description: 得到路径长度
* @param: [ans]
* @return: java.lang.Float
* @author Jiao Tiancai
* @date: 2021-4-9 19:02
*/
private Float getRouteDis(ArrayList<Integer> ans) {
Float dis = 0f;
int i, length;
for (i = 0, length = citys.size() - 1; i < length; i++) {
dis += distance.get(ans.get(i)).get(ans.get(i + 1));
}
dis += distance.get(ans.get(i)).get(ans.get(0));
return dis;
}
/**
* @description: 根据现在的解在其邻域创建新的解
* @param: []
* @return: java.util.ArrayList
* @author Jiao Tiancai
* @date: 2021-4-9 18:29
*/
private ArrayList<Integer> createNewAns() {
//优化一下,可以看出效果明显要比不优化强得多
int strategy = random.nextInt(3);
//strategy = 0;
ArrayList<Integer> ansNew = new ArrayList<Integer>();
if (strategy == 0) {//策略一,用随机交换两个地方求邻域,
Random random = new Random();
int i = random.nextInt(31);
int j = random.nextInt(31);
for (int k = 0, length = ans.size(); k < length; k++) { //这里可以进行优化
if (k == i) {
ansNew.add(ans.get(j));
} else if (k == j) {
ansNew.add(ans.get(i));
} else {
ansNew.add(ans.get(k));
}
}
} else if (strategy == 1) {//策略二,一段倒置
int start = random.nextInt(30);
int end = random.nextInt(30);//随机产生倒转的范围
if (start >= end) {
int a = start;
start = end;
end = a + 1;
}
for (int k = 0; k < citys.size(); k++) {
if (k >= start && k <= end) {
ansNew.add(ans.get(end - (k - start)));//倒转操作
} else {
ansNew.add(ans.get(k));//不倒转
}
}
} else {//策略三,采取切片原则,三个整数可以分四片,然后交换中间两片
int n1 = random.nextInt(29);
int n2 = random.nextInt(29);
int n3 = random.nextInt(29);
if (n1 > n2) {//三个数懒得用排序算法
int temp = n1;
n1 = n2;
n2 = temp;
}
if (n2 > n3) {
int temp = n2;
n2 = n3;
n3 = temp;
}
if (n1 > n2) {
int temp = n1;
n1 = n2;
n2 = temp;
}
n2 += 1;
n3 += 2;
//让n1到n2的数和n2+1到n3的数交换
for (int k = 0; k < citys.size(); k++) {
if (k >= n1 && k <= n1 + (n3 - n2 - 1)) {
ansNew.add(ans.get(n2 + 1 + (k - n1)));
} else if (k > n1 + (n3 - n2 - 1) && k <= n3) {
ansNew.add(ans.get(n1 + (k - (n1 + (n3 - n2)))));
} else {
ansNew.add(ans.get(k));
}
//System.out.println(ansNew);
}
}
return ansNew;
}
/**
* @description: 初始化一个解,用贪心算法,第一版的算法仅仅随机构造了一个解
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 18:10
*/
private void initAnsByGreedy() {
for (int i = 0, length = citys.size(); i < length; i++) {
ans.add(i);
}
}
/**
* @description: 更新两两城市之间的距离信息
* @param: []
* @return: void
* @author Jiao Tiancai
* @date: 2021-4-9 17:48
*/
private void updateDis() {
ArrayList<Float> rowDis;
distance = new ArrayList<ArrayList<Float>>();
for (int i = 0, length = citys.size(); i < length; i++) {
rowDis = new ArrayList<Float>();
for (int j = 0; j < length; j++) {
rowDis.add((float) Math.sqrt((citys.get(i).get(0) - citys.get(j).get(0))
* (citys.get(i).get(0) - citys.get(j).get(0)) +
(citys.get(i).get(1) - citys.get(j).get(1))
* (citys.get(i).get(1) - citys.get(j).get(1))));//逐行获取两两城市之间的距离
}
distance.add(rowDis);//将获取完的距离信息添加到数组中去
}
}
public static void main(String[] args) throws IOException {
//创建一个空的城市
ArrayList<ArrayList<Integer>> citys = new ArrayList<ArrayList<Integer>>();
//读取城市位置信息
SA_TSP.updateLoc(citys);
//初始化城市距离信息
SA_TSP sa_tsp = new SA_TSP(5000, 0.98f, citys);
long startTime = System.currentTimeMillis(); //程序开始记录时间
sa_tsp.solve();
long endTime = System.currentTimeMillis(); //程序结束记录时间
long TotalTime = endTime - startTime; //总消耗时间
System.out.println("总消耗时长" + TotalTime / 1000.0 + "s");
System.out.println("求得的路径结果:\n" + sa_tsp.ans);
System.out.println("路径长度:" + sa_tsp.fitness);
System.out.println("降温次数:" + sa_tsp.cnt);
System.out.println(sa_tsp.disPic);
}
}
测试数据是我在网上找的31个城市的坐标信息,31那估计可能是中国的31个省市信息吧。
从上图可以看到大约降温 250 次后,最短路径开始收敛,该方法的邻域 函数是通过从上一个解从交换两个城市、倒换一段城市、交换两段城市三种 方案随机选择一种方案。
如果仅用交换两两城市的方式获得最新解,可以看到算法不容易收敛到 一个很好的结果。
由结果明显可以看出,优化邻域函数可以让结果更容易收敛到一个更短 的距离。 接下来通过改变降温系数来测试系统的性能:
随着降温系数的降低,算法收敛加快,性能更优。蓝色为降温系数为 0.98 的结果图。黄色为降温系数为 0.8 的值,降温系数梯度依次为 0.98、0.95、 0.9、0.85、0.8。
由实验结果可以看出,随着温度的降低,波动的幅度减小,也就是说接 收更大解的可能性降低,系统趋于稳定。当优化邻域函数时,模拟退火算法 的性能有着明显的提升,得到的最优解更加接近真实最优解。 当降低降温系数的时候,算法的性能提升明显,算法计算的结果并无太 大下降。所以取一个合适的降温系数尤为重要。
邻域函数的选取直接影响收敛结果。通过改进邻域函数的方法让模拟退 火的性能取得了极强的优化。 降温系数对模拟退火的性能也有明显的影响。一个合适的降温系数可以 使算法的运行时间得到优化,本次实验采用了指数降温方式,实验过后将尝 试不同的降温方式观察算法的结果。