一文看懂模拟退火算法求解TSP问题(通俗易懂)

基于改进模拟退火算法求解TSP问题

  • 1.算法思想
  • 2.算法设计
  • 3. 详细代码
  • 4. 测试结果
    • 测试数据
    • 测试结果
    • 结果分析
  • 5.结论

1.算法思想

粒子在某个温度 T 时,固体所处的状态具有一定的随机性,而这些状态之 间的转换能否实现由 Metropolis 准则决定。设从当前状态 i 生成新状态 j,若新 状态的内能(Ej)小于状态 i 的内能 (Ei),则接受新状态 j 作为新的当前状态; 否则,以概率 exp[-(Ej-Ei)/KT] 接受状态 j,其中 K 为 Boltzmann 常数,这就是通 常所说的 Metropolis 准则。随着 T 的降低,接收的概率越低。系统趋于稳定状态。

2.算法设计

我事先找到了 31 个城市的坐标信息,根据坐标信息计算出两两城市之间的 距离信息。整个算法的流程图如下:
一文看懂模拟退火算法求解TSP问题(通俗易懂)_第1张图片

3. 详细代码


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);
    }
}

4. 测试结果

测试数据

测试数据是我在网上找的31个城市的坐标信息,31那估计可能是中国的31个省市信息吧。

测试结果

一文看懂模拟退火算法求解TSP问题(通俗易懂)_第2张图片
从上图可以看到大约降温 250 次后,最短路径开始收敛,该方法的邻域 函数是通过从上一个解从交换两个城市、倒换一段城市、交换两段城市三种 方案随机选择一种方案。一文看懂模拟退火算法求解TSP问题(通俗易懂)_第3张图片
如果仅用交换两两城市的方式获得最新解,可以看到算法不容易收敛到 一个很好的结果。
一文看懂模拟退火算法求解TSP问题(通俗易懂)_第4张图片
由结果明显可以看出,优化邻域函数可以让结果更容易收敛到一个更短 的距离。 接下来通过改变降温系数来测试系统的性能:一文看懂模拟退火算法求解TSP问题(通俗易懂)_第5张图片
随着降温系数的降低,算法收敛加快,性能更优。蓝色为降温系数为 0.98 的结果图。黄色为降温系数为 0.8 的值,降温系数梯度依次为 0.98、0.95、 0.9、0.85、0.8。

结果分析

由实验结果可以看出,随着温度的降低,波动的幅度减小,也就是说接 收更大解的可能性降低,系统趋于稳定。当优化邻域函数时,模拟退火算法 的性能有着明显的提升,得到的最优解更加接近真实最优解。 当降低降温系数的时候,算法的性能提升明显,算法计算的结果并无太 大下降。所以取一个合适的降温系数尤为重要。

5.结论

邻域函数的选取直接影响收敛结果。通过改进邻域函数的方法让模拟退 火的性能取得了极强的优化。 降温系数对模拟退火的性能也有明显的影响。一个合适的降温系数可以 使算法的运行时间得到优化,本次实验采用了指数降温方式,实验过后将尝 试不同的降温方式观察算法的结果。

你可能感兴趣的:(计算智能方法,算法,java,python)