数学建模常用算法:遗传算法求解tsp问题+att48算例测试【java实现--详细注释】

简介: 数学建模常用算法:遗传算法求解tsp问题+att48算例测试【java实现--详细注释】

代码

个体类

package com.dam.heuristic.ga.test;
import java.io.Serializable;
import java.util.*;
/**
 * 个体
 */
public class Individual implements Serializable {
    //基因序列
    private int[] genes;
    //路程
    private double distance;
    //适应度(在tsp问题中,路程越短越好,即路程越短,适应度越高,取fitness=1.0/distance)
    private double fitness;
    //生存率(适应度越高,生存率越高)
    private double survivalRate;
    public Individual(int cityNum) {
        this.genes = new int[cityNum];
        this.initGenesByRandom();
    }
    /**
     * 随机初始化基因
     */
    public void initGenesByRandom() {
        List<Integer> cityCodeList = new ArrayList<>();
        for (int i = 0; i < this.genes.length; i++) {
            cityCodeList.add(i);
        }
        //随机打乱集合的元素
        Collections.shuffle(cityCodeList);
        for (int i = 0; i < cityCodeList.size(); i++) {
            this.genes[i] = cityCodeList.get(i);
        }
    }
    /**
     * 计算个体的tsp回路路程距离和适应值
     */
    public void calculateDistanceAndFitness(double[][] distanceMatrix) {
        ///计算路程距离
        //计算从第0个城市到最后一个城市的路程
        this.distance = 0;
        for (int i = 0; i < this.genes.length - 1; i++) {
            this.distance += distanceMatrix[this.genes[i]][this.genes[i + 1]];
        }
        //计算最后一个城市到第0个城市的路程
        this.distance += distanceMatrix[this.genes[0]][this.genes[this.genes.length - 1]];
        ///计算适应值
        this.fitness = 1.0 / this.distance;
    }
    /**
     * 对个体基因中的两个元素进行交换
     */
    public void swapTwoElement() {
        //对序列中的元素进行打乱,即可产生新的序列
        Random random = new Random();
        int i = random.nextInt(this.genes.length);
        int j = random.nextInt(this.genes.length);
        while (i == j) {
            j = random.nextInt(this.genes.length);
        }
        int temp = this.genes[i];
        this.genes[i] = this.genes[j];
        this.genes[j] = temp;
    }
    public int[] getGenes() {
        return genes;
    }
    public void setGenes(int[] genes) {
        this.genes = genes;
    }
    public double getDistance() {
        return distance;
    }
    public void setDistance(double distance) {
        this.distance = distance;
    }
    public double getFitness() {
        return fitness;
    }
    public void setFitness(double fitness) {
        this.fitness = fitness;
    }
    public double getSurvivalRate() {
        return survivalRate;
    }
    public void setSurvivalRate(double survivalRate) {
        this.survivalRate = survivalRate;
    }
    @Override
    public String toString() {
        return "Individual{" +
                "genes=" + Arrays.toString(genes) +
                ", distance=" + distance +
                '}';
    }
}


遗传算法

package com.dam.heuristic.ga.test;
import com.dam.heuristic.ga.shuJuMoShuShi.TSPData;
import java.io.*;
import java.util.*;
/**
 * 遗传算法
 */
public class GaApi {
    private int cityNum; //城市数
    //种群的基因个数
    private int populationNum = 100;
    //迭代次数
    private int maxGen = 20000; //进化代数
    private double crossRate= 0.98f;//交叉概率
    private double mutateRate = 0.4f;//变异概率
    private double[][] distanceMatrix; //地图数据
    public GaApi(double[][] distanceMatrix) {
        this.cityNum = distanceMatrix[0].length;
        this.distanceMatrix = distanceMatrix;
    }
    public void solve() {
        变量声明
        //种群
        List<Individual> population = new ArrayList<>();
        //随机数工具
        Random random = new Random();
        //开始计算时间
        long startTime = System.currentTimeMillis();
        //存储最优个体
        Individual bestIndividual = null;
        求解
        //初始化种群
        this.initPopulation(population);
        //迭代优化
        for (int t = 0; t < maxGen; t++) {
            //选择
            Individual localBestIndividual = this.select(population, random);
            if (bestIndividual == null || localBestIndividual.getDistance() < bestIndividual.getDistance()) {
                bestIndividual = localBestIndividual;
                System.out.println("最短距离:" + bestIndividual.getDistance());
            }
            //交叉
            this.crossover(population, random);
            //变异
            this.mutate(population, random);
        }
        //获取最优解
        System.out.println("最短距离:" + bestIndividual.getDistance());
        System.out.println("最优路径:"+ Arrays.toString(bestIndividual.getGenes()));
        System.out.println("运行时间:" + (System.currentTimeMillis() - startTime) + "ms");
    }
    /**
     * 初始化种群
     */
    public void initPopulation(List<Individual> population) {
        for (int i = 0; i < this.populationNum; i++) {
            population.add(new Individual(this.cityNum));
        }
    }
    /**
     * 计算种群中每个个体的适应度和生存率
     * 选择出最优个体(适应值最高)
     *
     * @param population
     */
    public Individual calculateFitnessAndSurvivalRateOfIndividualAndChooseBestOne(List<Individual> population) {
        ///变量声明
        //存储种群中所有个体的总适应值
        double totalFitness = 0;
        //存储最优适应值
        double bestFitness = -Double.MAX_VALUE;
        //存储最优个体
        Individual bestIndividual = null;
        ///计算个体的适应值,并选择出最优个体
        for (Individual individual : population) {
            individual.calculateDistanceAndFitness(this.distanceMatrix);
            totalFitness += individual.getFitness();
            if (individual.getFitness() > bestFitness) {
                bestFitness = individual.getFitness();
                bestIndividual = individual;
            }
        }
        //将最优个体从种群中移除
        population.remove(bestIndividual);
        //删除最优个体对应的适应值
        totalFitness -= bestIndividual.getFitness();
        ///计算种群中剩余个体的生存率
        for (Individual individual : population) {
            //个体生存率=个体适应值/种群总适应值
            individual.setSurvivalRate(individual.getFitness() / totalFitness);
        }
        return bestIndividual;
    }
    /**
     * 选择优秀个体,选择出适应值最高的个体,将个体复制几个,剩余的名额使用轮盘赌从种群剩余个体中选出
     *
     * @param population
     */
    public Individual select(List<Individual> population, Random random) {
        变量声明
        //新的种群
        List<Individual> newPopulation = new ArrayList<>();
        //将最优个体复制几次
        int cloneNumOfBestIndividual = 3;
        //至少有一次,否则不会被添加到新种群
//        cloneNumOfBestIndividual = cloneNumOfBestIndividual == 0 ? 1 : cloneNumOfBestIndividual;
        选择个体,选择方式:1、选择种群的最优个体,然后复制几次;2、轮盘赌选择剩余的个体
        //计算种群中每个个体的适应度和生存率并选择出最优个体
        Individual bestIndividual = this.calculateFitnessAndSurvivalRateOfIndividualAndChooseBestOne(population);
        ///1、选择种群的最优个体,然后复制几次,将最优个体复制多个,存到新的集合中
        for (int i = 0; i < cloneNumOfBestIndividual; i++) {
            //deepClone对对象进行深拷贝,否则,改变一个属性,其他几个的属性会跟着变化
            newPopulation.add((Individual) this.deepClone(bestIndividual));
        }
        ///2、轮盘赌确定剩余个体
        for (int i = 0; i < (this.populationNum - cloneNumOfBestIndividual); i++) {
            double p = random.nextDouble();
            double sumP = 0;
            for (Individual individual : population) {
                sumP += individual.getSurvivalRate();
                if (sumP >= p) {
                    //选择个体
                    newPopulation.add((Individual) this.deepClone(individual));
                    break;
                }
            }
        }
        //用newPopulationt替换population
        population.clear();
        population.addAll(newPopulation);
        return bestIndividual;
    }
    /**
     * 交叉
     * 当随机数>pcl,小于pch时,随机找两个个体出来进行基因交叉互换
     */
    public void crossover(List<Individual> population, Random random) {
        double p = random.nextDouble();
        if ( p < this.crossRate) {
            //随机找两个索引
            int i = random.nextInt(population.size());
            int j = random.nextInt(population.size());
            while (i == j) {
                j = random.nextInt(population.size());
            }
            Individual individualI = population.get(i);
            Individual individualJ = population.get(j);
//            System.out.println("执行交叉前-----------------------------------");
//            System.out.println(Arrays.toString(individualI.getGenes()));
//            System.out.println(Arrays.toString(individualJ.getGenes()));
            //使用LinkedHashSet存储元素,方便替换元素时,对元素进行删减
            LinkedHashSet<Integer> hashSetI = new LinkedHashSet<>();
            LinkedHashSet<Integer> hashSetJ = new LinkedHashSet<>();
            for (int k = 0; k < this.cityNum; k++) {
                hashSetI.add(individualI.getGenes()[k]);
                hashSetJ.add(individualJ.getGenes()[k]);
            }
            //开始交换的位置,一直交换到尾部,即单点交叉(begin至少要从1开始,否则没有意义)
            int begin = random.nextInt(this.cityNum - 1) + 1;
            for (int k = begin; k < this.cityNum; k++) {
                //交换两个基因的对应位置
                int temp = individualI.getGenes()[k];
                individualI.getGenes()[k] = individualJ.getGenes()[k];
                individualJ.getGenes()[k] = temp;
                //删除对应的元素,该元素已经在k位置了
                hashSetI.remove(individualI.getGenes()[k]);
                hashSetJ.remove(individualJ.getGenes()[k]);
            }
            //重新填充非交叉区域的元素
            begin = 0;
            for (Integer element : hashSetI) {
                individualI.getGenes()[begin++] = element;
            }
            begin = 0;
            for (Integer element : hashSetJ) {
                individualJ.getGenes()[begin++] = element;
            }
//            System.out.println("执行交叉后-----------------------------------");
//            System.out.println(Arrays.toString(individualI.getGenes()));
//            System.out.println(Arrays.toString(individualJ.getGenes()));
//            System.out.println();
        }
    }
    /**
     * 变异:随机交换个体基因的两个元素
     */
    public void mutate(List<Individual> population, Random random) {
        for (Individual individual : population) {
            //当随机数小于变异概率时,对个体的基因进行变异
            if (random.nextDouble() < this.mutateRate) {
                //对个体基因中的两个元素进行交换
                individual.swapTwoElement();
            }
        }
    }
    /**
     * 深拷贝对象
     *
     * @param srcObject
     * @return
     */
    public Object deepClone(Object srcObject) {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        Object result = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //将对象写到流里
            oos.writeObject(srcObject);
            //从流里读回来
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            result = ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

测试

package com.dam.heuristic.ga.test;
import com.dam.heuristic.ts.test.TsApi;
import java.io.File;
import java.io.FileInputStream;
public class GaMainRun {
    public static void main(String[] args) throws Exception {
        声明变量
        //距离矩阵,可以直接获取任意两个编号城市的距离
        double[][] distanceMatrix;
        读取数据
        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];
        for (int i = 0; i < cityDataArr.length; i++) {
            String[] city1Arr = cityDataArr[i].split(" ");
            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];
                }
            }
        }
        GaApi gaApi = new GaApi( distanceMatrix);
        gaApi.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) {
            }
        }
    }
}


最短距离:12163.007870412615
最优路径:[18, 36, 5, 27, 32, 11, 10, 46, 20, 12, 24, 13, 22, 2, 21, 15, 0, 7, 8, 37, 30, 43, 17, 6, 35, 45, 14, 39, 33, 40, 28, 1, 3, 25, 34, 44, 9, 23, 41, 4, 47, 38, 31, 19, 29, 26, 42, 16]
运行时间:12451ms
目录
相关文章
|
11小时前
|
算法 Java 开发者
使用Java编写高效的内存管理算法
使用Java编写高效的内存管理算法
|
11小时前
|
Java 测试技术 开发者
Java中设计可测试的代码的最佳实践
Java中设计可测试的代码的最佳实践
|
17小时前
|
Java 测试技术
在Java中使用断言函数进行代码测试
在Java中使用断言函数进行代码测试
|
1天前
|
缓存 算法 安全
Java中的数据结构与算法优化策略
Java中的数据结构与算法优化策略
|
1天前
|
Java jenkins 持续交付
Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试
【7月更文挑战第1天】Jenkins是开源CI/CD工具,用于自动化Java项目构建、测试和部署。通过配置源码管理、构建触发器、执行Maven目标,实现代码提交即触发构建和测试。成功后,Jenkins执行部署任务,发布到服务器或云环境。使用Jenkins能提升效率,保证软件质量,加速上线,并需维护其稳定运行。
11 0
|
1天前
|
XML 测试技术 数据格式
《手把手教你》系列基础篇(八十三)-java+ selenium自动化测试-框架设计基础-TestNG测试报告-下篇(详解教程)
【7月更文挑战第1天】使用TestNG自定义报告的简要说明: - TestNG提供默认的HTML和XML报告,但可通过实现IReporter接口创建自定义报告。 - 自定义报告器类需扩展`CustomReporter.java`,实现`generateReport()`方法,接收XML套房、测试结果及输出目录作为参数。
10 0
|
1天前
|
算法 安全 Java
Java中MD5加密算法的原理与实现详解
Java中MD5加密算法的原理与实现详解
|
1天前
|
Java 测试技术 Maven
如何使用Java进行单元测试
如何使用Java进行单元测试
|
1天前
|
缓存 算法 Java
如何优化Java中的递归算法?
如何优化Java中的递归算法?
|
1天前
|
Java 测试技术
Java中的测试驱动开发(TDD)实践
Java中的测试驱动开发(TDD)实践