数学建模常用算法:迭代局部搜索算法求解tsp问题+att48算例测试【java实现--详细注释】

简介: 数学建模常用算法:迭代局部搜索算法求解tsp问题+att48算例测试【java实现--详细注释】

代码

package com.dam.heuristic.ils.test;
import java.util.*;
import static com.dam.heuristic.ils.ShuJuMoShuShi.City.CITY_SIZE;
/**
 * 迭代局部搜索
 */
public class IlsApi {
    //最大的迭代次数
    private int maxGen;
    //可接受最大没有提高解的次数
    private int acceptMaxNotImproveSolutionNum;
    //城市数量
    private int cityNum;
    //扰动最优序列时,可接受的新序列对应目标函数值和最优目标函数值之间的差值
    private double acceptDifferentLimit;
    //扰动最优序列时的代数
    private int genNum;
    //距离矩阵
    private double[][] distanceMatrix;
    public IlsApi(int maxGen, int acceptMaxNotImproveSolutionNum, double acceptDifferentLimit, int genNum, double[][] distanceMatrix) {
        this.maxGen = maxGen;
        this.acceptMaxNotImproveSolutionNum = acceptMaxNotImproveSolutionNum;
        this.distanceMatrix = distanceMatrix;
        this.acceptDifferentLimit = acceptDifferentLimit;
        this.genNum = genNum;
        this.cityNum = distanceMatrix[0].length;
    }
    public int[] solve() {
        定义变量
        long startTime = System.currentTimeMillis();
        ///最优解
        //最优代数
        int bestT = 0;
        //序列
        int[] localSequence = new int[this.distanceMatrix[0].length];
        //最优序列
        int[] bestSequence;
        //存储求得最优解的时间
        long bestTime = 0;
        //最优序列对应的目标函数值
        double bestObjectValue = 0;
        生成初始序列
        this.generateInitialSequence(localSequence);
        //初始化bestSequence,刚开始的最优序列为初始序列
        bestSequence = localSequence.clone();
        bestObjectValue = this.getObjectValue(bestSequence);
        初始搜索
        bestSequence = this.localSearch(bestSequence, bestObjectValue);
        bestObjectValue = this.getObjectValue(bestSequence);
        迭代优化
        for (int t = 0; t < this.maxGen; t++) {
            ///多次扰动最优序列,当得到的序列没那么差,或者到达this.genNum限制之后,停止扰动
            int[] tempSequence = new int[this.cityNum];
            double tempObjectValue = Double.MAX_VALUE;
            for (int i = 0; i < this.genNum; i++) {
                //扰动最优解产生新解
                tempSequence = this.perturbation(bestSequence);
                tempObjectValue = this.getObjectValue(tempSequence);
                if (tempObjectValue - bestObjectValue < this.acceptDifferentLimit) {
                    break;
                }
            }
            //对tempSequence进行局部搜索
            tempSequence = this.localSearch(tempSequence, tempObjectValue);
            tempObjectValue = this.getObjectValue(tempSequence);
            //更新最优解
            if (tempObjectValue < bestObjectValue) {
                bestObjectValue = tempObjectValue;
                bestSequence = tempSequence.clone();
                bestT = t;
                bestTime = System.currentTimeMillis() - startTime;
//                System.out.println("当前最优目标函数值:" + tempObjectValue);
//                System.out.println("计算时间为:" + bestTime + "ms");
            }
        }
        System.out.println("最佳迭代次数:" + bestT);
        System.out.println("最优目标函数值:" + bestObjectValue);
        System.out.println("最优解对应序列:" + Arrays.toString(bestSequence));
        System.out.println("计算时间为:" + (System.currentTimeMillis() - startTime) + "ms");
        System.out.println(this.getObjectValue(bestSequence));
        return bestSequence;
    }
    /**
     * 局部搜索更优解
     *
     * @param bestSequence
     * @param bestObjectValue
     * @return 当前所搜索到的最优解对应的目标函数值
     */
    public int[] localSearch(int[] bestSequence, double bestObjectValue) {
        int count = 0;
        while (count++ <= this.acceptMaxNotImproveSolutionNum) {
            for (int i = 0; i < this.cityNum - 1; i++) {
                for (int j = i + 1; j < this.cityNum; j++) {
                    //通过反转indexI和indexJ之间的元素,产生新的序列
                    int[] tempSequence = this.generateNewSequenceBySwapTwoElementWithTwoAppointIndex(bestSequence, i, j);
                    double tempObjectValue = this.getObjectValue(tempSequence);
                    //更新最优解
                    if (tempObjectValue < bestObjectValue) {
                        count = 0;
                        bestObjectValue = tempObjectValue;
                        bestSequence = tempSequence.clone();
                        break;
                    }
                }
            }
        }
        return bestSequence;
    }
    /**
     * 通过将bestSequence的元素分为四组,然后改变四个组的组序,获得新序列
     *
     * @param bestSequence
     */
    public int[] perturbation(int[] bestSequence) {
        Random random = new Random();
        int[] newSequence = new int[bestSequence.length];
        获取五个index,这五个index将bestSequence划分为四组(并非均分)
        //this.cityNum - 2的原因是:indexArr[0]和indexArr[4]都已经确定
        int elementNumInOneGroup = (this.cityNum - 2) / 3;
        int[] indexArr = new int[5];
        indexArr[0] = 0;
        indexArr[1] = random.nextInt(elementNumInOneGroup) + 1;
        indexArr[2] = random.nextInt(elementNumInOneGroup) + elementNumInOneGroup + 1;
        indexArr[3] = random.nextInt(elementNumInOneGroup) + elementNumInOneGroup * 2 + 1;
        indexArr[4] = this.cityNum;
        将组别[0,1,2,3]对应的元素赋值给newSequence
        ///将组序打乱
        List<Integer> groupCodeList = new ArrayList<>();
        //将[0,1,2,3]赋值给集合
        Collections.addAll(groupCodeList, 0, 1, 2, 3);
        //随机打乱
        Collections.shuffle(groupCodeList);
        ///赋值
        int index = 0;
        for (int i = 0; i < groupCodeList.size(); i++) {
            for (int j = indexArr[groupCodeList.get(i)]; j < indexArr[groupCodeList.get(i) + 1]; j++) {
                newSequence[index] = bestSequence[j];
                index++;
            }
        }
        return newSequence;
    }
    /**
     * 通过反转indexI和indexJ之间的元素,产生新的序列
     *
     * @param sequence
     * @param indexI
     * @param indexJ
     */
    public int[] generateNewSequenceBySwapTwoElementWithTwoAppointIndex(int[] sequence, int indexI, int indexJ) {
        //克隆出新序列
        int[] newSequence = sequence.clone();
        int temp;
        //不但交换indexI和indexJ对应的元素
        while (indexI < indexJ) {
            temp = newSequence[indexI];
            newSequence[indexI] = newSequence[indexJ];
            newSequence[indexJ] = temp;
            indexI++;
            indexJ--;
        }
        return newSequence;
    }
    /**
     * 生成初始序列
     */
    public void generateInitialSequence(int[] sequence) {
      /*  HashSet<Integer> sequenceSet = new HashSet<>();
        for (int i = 1; i < sequence.length; i++) {
            sequenceSet.add(i);
        }
        //贪婪算法获取初始序列,从城市0开始旅行,即城市0为起点城市
        sequence[0] = 0;
        //每次获取离当前城市最近的城市,并加入到sequence
        for (int i = 1; i < sequence.length; i++) {
            //寻找离i-1城市最近的城市,即确定第i个城市是哪个
            double smallDistance = Double.MAX_VALUE;
            int curCity = 0;
            for (Integer j : sequenceSet) {
                if (this.distanceMatrix[sequence[i - 1]][j] < smallDistance && j != sequence[i - 1] ) {
                    smallDistance = this.distanceMatrix[sequence[i - 1]][j];
                    curCity = j;
                }
            }
            sequence[i] = curCity;
            sequenceSet.remove(curCity);
        }*/
        for (int i = 0; i < sequence.length; i++) {
            sequence[i] = i;
        }
    }
    /**
     * 根据当前序列获取目标函数值
     *
     * @param sequence
     * @return
     */
    public double getObjectValue(int[] sequence) {
        double objectValue = 0;
        //计算从第0个城市到最后一个城市的路程
        for (int i = 0; i < sequence.length - 1; i++) {
            objectValue += this.distanceMatrix[sequence[i]][sequence[i + 1]];
        }
        //计算最后一个城市到第0个城市的路程
        objectValue += this.distanceMatrix[sequence[0]][sequence[sequence.length - 1]];
        return objectValue;
    }
}

测试

package com.dam.heuristic.ils.test;
import com.alibaba.fastjson.JSON;
import com.dam.heuristic.ts.test.TsApi;
import com.dam.heuristic.util.paint.PaintTspResult;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class IlsMainRun {
    public static void main(String[] args) throws Exception {
        声明变量
        //距离矩阵,可以直接获取任意两个编号城市的距离
        double[][] distanceMatrix;
        //存储每个城市对应的x,y坐标
        double[][] cityPositionArr;
        读取数据
        String data = read(new File("src/main/java/com/data/tsp/att48.txt"), "GBK");
        String[] cityDataArr = data.split("\n");
        //初始化数组
        distanceMatrix = new double[cityDataArr.length][cityDataArr.length];
        cityPositionArr = new double[cityDataArr.length][2];
        for (int i = 0; i < cityDataArr.length; i++) {
            String[] city1Arr = cityDataArr[i].split(" ");
            cityPositionArr[i][0] = Double.valueOf(city1Arr[1]);
            cityPositionArr[i][1] = Double.valueOf(city1Arr[2]);
            int cityOne = Integer.valueOf(city1Arr[0]);
            for (int j = 0; j < i; j++) {
                String[] city2Arr = cityDataArr[j].split(" ");
                int cityTwo = Integer.valueOf(city2Arr[0]);
                if (cityOne == cityTwo) {
                    distanceMatrix[cityOne - 1][cityTwo - 1] = 0;
                } else {
                    distanceMatrix[cityOne - 1][cityTwo - 1] = getDistance(Double.valueOf(city1Arr[1]), Double.valueOf(city1Arr[2]), Double.valueOf(city2Arr[1]), Double.valueOf(city2Arr[2]));
                    //对称赋值
                    distanceMatrix[cityTwo - 1][cityOne - 1] = distanceMatrix[cityOne - 1][cityTwo - 1];
                }
            }
        }
     /*   System.out.println("输出距离矩阵-------------------------------------------------------------------");
        for (double[] matrix : distanceMatrix) {
            System.out.println(Arrays.toString(matrix));
        }*/
        IlsApi ilsApi = new IlsApi(10, 50, 500, 5, distanceMatrix);
        int[] bestSequence = ilsApi.solve();
    }
    /**
     * 给定两个城市坐标,获取两个城市的直线距离
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    private static double getDistance(double x1, double y1, double x2, double y2) {
        return Math.sqrt((Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)) / 10);
    }
    private static String read(File f, String charset) throws Exception {
        FileInputStream fstream = new FileInputStream(f);
        try {
            int fileSize = (int) f.length();
            if (fileSize > 1024 * 512) {
                throw new Exception("File too large to read! size=" + fileSize);
            }
            byte[] buffer = new byte[fileSize];
            fstream.read(buffer);
            return new String(buffer, charset);
        } finally {
            try {
                fstream.close();
            } catch (Exception e) {
            }
        }
    }
}



最佳迭代次数:36
最优目标函数值:10698.532235769424
最优解对应序列:[35, 29, 42, 16, 26, 18, 36, 5, 27, 6, 17, 43, 30, 37, 8, 7, 0, 15, 21, 2, 33, 40, 28, 1, 25, 3, 34, 44, 9, 23, 41, 4, 47, 38, 31, 20, 12, 24, 13, 22, 10, 46, 19, 11, 39, 14, 32, 45]
计算时间为:1121ms


目录
相关文章
|
29天前
|
算法 Java API
Java 方法注释:规范、实用和高质量的写法
本文深入探讨了如何编写高质量的 Java 方法注释
50 11
|
2月前
|
Java
Java 中的注释
1. 单行注释:// 2. 多行注释:/* */ 3. 文档注释::/** **/
|
4月前
|
Java 测试技术 程序员
💡Java 零基础 | 深入理解注释的重要性与应用
【10月更文挑战第10天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
50 5
|
5月前
|
Java
Java搜索与替换
Java搜索与替换
37 4
Java搜索与替换
|
4月前
|
XML Java Maven
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
89 7
|
4月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
109 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
5月前
|
Java API 开发者
Java 注释规范
Java中的注释规范包括单行注释(`//`)、多行注释(`/* ... */`)和文档注释(`/** ... */`)。单行注释适用于简短说明,多行注释用于较长描述,文档注释则专为自动生成API文档设计。注释应清晰明了、及时更新,避免冗余,并详细说明参数和返回值。遵循这些规范有助于提高代码的可读性和可维护性。
289 5
|
4月前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
65 0
|
3天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
39 14
|
6天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
35 13