蚁群算法解决TSP问题的JAVA实现(二)

3.代码实现

3.1.代码下载地址

代码

3.2.代码整体结构图

《蚁群算法解决TSP问题的JAVA实现(二)》

  1. 采用MVC结构,其中vc_1是对model层的一种显示实现;后续计划还将加入另一种实现vc_2。
  2. 代码中的所有常量都被写入static代码块中,若无大的改动之后会全部迁移到xml文件中。

3.2.1.整体结构介绍:

包名描述
main程序运行入口
modelmodel层
vc_1显示一次蚁群算法的结果的VC层
tool用到的工具文件

3.3.model层

3.3.1.结构图

《蚁群算法解决TSP问题的JAVA实现(二)》

3.3.2 model.ACO.java

model层唯一的入口;一个ACO对象只进行一次蚁群算法的运算,得出一个运算最优解释。

package model;

import tool.Point;
import model.world.World;

/** * 求解TSP问题的蚁周系统 * @author WangYikai */
public class ACO {
    /** * int型,循环轮数 */
    private static final int N_CMAX;

    /** * double型,各路径信息素强度初始值 */
    private static final double TAU_INIT;

    /** * double型,刷新信息素时之前信息素保留的百分比 */
    private static final double RHO;

    /** * double[][]型,(i, j)代表城市i到城市j的信息素强度 */
    private double[][] tau_s;

    /** * Round[]型,本次蚁群算法所有实现的轮数 */
    private Round[] rounds;

    /** * int型,蚁群系统时刻 */
    private int t;

    /** * Way型,最佳路径 */
    private Way best_way;

    static {
        N_CMAX = 100;
        TAU_INIT = 0.0;
        RHO = 0.7;
    }

    public ACO() {
        this.t = 0;

        this.tau_s = new double[World.getCITY_ARRAY().length][World.getCITY_ARRAY().length];
        for(int i = 0; i < this.tau_s.length; i++) {
            for(int j = 0; j < this.tau_s[i].length; j++) {
                if(i == j) {
                    this.tau_s[i][j] = 0;
                } else {
                    this.tau_s[i][j] = ACO.TAU_INIT;
                }
            }
        }

        this.rounds = new Round[ACO.N_CMAX];
        for(int i = 0; i < this.rounds.length; i++) {
            this.rounds[i] = null;
        }

        this.best_way = null;
    }

    /** * 获得城市坐标信息 * @return Point[]型,城市坐标信息 */
    public Point[] getCityPoints() {
        Point[] back = new Point[World.getCITY_ARRAY().length];
        for(int i = 0; i < back.length; i++) {
            back[i] = World.getCITY_ARRAY()[i].getPoint();
        }
        return back;
    }

    /** * 开始蚁群算法的第x轮 * @param x int型 */
    public void new_round_x(int x) {
        this.rounds[x] = new Round(this);

        // 刷新最佳路径
        if(this.best_way == null || this.best_way.getL() > this.rounds[x].getBest_way().getL()) {
            this.best_way = this.rounds[x].getBest_way();
        }

        //System.out.println(this.best_way.getL());

        // 将该轮的信息素信息更新到算法中
        for(int i = 0; i < this.tau_s.length; i++) {
            for(int j = 0; j < this.tau_s[i].length; j++) {
                this.tau_s[i][j] = ACO.RHO * this.tau_s[i][j] + this.rounds[x].getDelta_tau_s()[i][j];
            }
        }

        // 将本轮结束后的信息素情况写回本轮
        double[][] temp = new double[World.getCITY_ARRAY().length][World.getCITY_ARRAY().length];
        for(int i = 0; i < temp.length; i++) {
            for(int j = 0; j < temp.length; j++) {
                temp[i][j] = this.tau_s[i][j];
            }
        }
        this.rounds[x].setTau_s(temp);
    }

    /** * 获得当前最佳路径城市id按顺序构成的数组,没有则为null * @return int[]型,当前最佳路径城市id按顺序构成的数组 */
    public int[] getBest_way_id() {
        int[] back = null;
        if(this.best_way != null) {
            back = new int[this.best_way.getWay().length];
            for(int i = 0; i < back.length; i++) {
                back[i] = this.best_way.getWay()[i].getId();
            }
        }
        return back;
    }

    /** * 获得历史上曾出现过的最大信息素强度 * @return double型,历史上曾出现过的最大信息素强度 */
    public double getMax_pheromone() {
        double back = 0;

        for(int i = 0; i < this.rounds.length; i++) {
            if(this.rounds[i] != null) {

                for(int x = 0; x < this.rounds[i].getTau_s().length; x++) {
                    for(int y = 0; y < this.rounds[i].getTau_s()[x].length; y++) {
                        if(this.rounds[i].getTau_s()[x][y] > back) {
                            back = this.rounds[i].getTau_s()[x][y];
                        }
                    }
                }

            }
        }

        return back;
    }

    public static int getnCmax() {
        return N_CMAX;
    }

    public int getT() {
        return t;
    }

    public void setT(int t) {
        this.t = t;
    }

    public Round[] getRounds() {
        return rounds;
    }

    public double[][] getTau_s() {
        return tau_s;
    }

    public static double getTauInit() {
        return TAU_INIT;
    }

    public Way getBest_way() {
        return best_way;
    }
}

3.3.3 model.ChooseCity.java

例如,当前蚂蚁在城市0,选择下一个到访城市时将判断每座城市的概率信息存入该类。

可达城市编号选择概率选择区间
10.3[0, 0.3)
20.2[0.3, 0.5)
30.5[0.5, 1)

随后产生一个[0, 1)的随机数,该数落在某区间则选择对应城市。

package model;

import model.world.City;

/** * 选择辅助判断类 * @author WangYikai */
public class ChooseCity {
    /** * City型,待选择城市 */
    private final City city;

    /** * double型,开始概率 */
    private final double begin_p;

    /** * double型,结束概率 */
    private final double end_p;

    /** * 构造函数 * @param begin_p double型,开始概率 * @param end_p double型,结束概率 * @param city_choose City型,待选择城市 */
    public ChooseCity(double begin_p, double end_p, City city_choose) {
        this.city = city_choose;
        this.begin_p = begin_p;
        this.end_p = end_p;
    }

    public City getCity() {
        return city;
    }

    /** * 判断某[0, 1]随机数是否在该随即空间内部 * @param random double型,[0, 1]随机数 * @return boolean型,true:在;false:不在 */
    public boolean random_in(double random) {
        boolean back = false;
        if(random >= this.begin_p && random < this.end_p) {
            back = true;
        }
        return back;
    }
}

3.3.4 model.Round.java

一次蚁群算法中的一轮,对应了2.2.2.循环【循环层1】所发生的事件。

package model;

import java.util.ArrayList;
import java.util.List;

import model.antcolony.Ant;
import model.antcolony.AntColony;
import model.world.City;
import model.world.World;

/** * 蚁群算法中的一轮 * @author WangYikai */
public class Round {
    /** * double型,信息素信息对蚂蚁选择城市的影响程度 */
    private static final double ALPHA;

    /** * double型,环境信息对蚂蚁选择城市的影响程度 */
    private static final double BETA;

    /** * double型,每只蚂蚁每轮播撒的信息素总量 */
    private static final double Q;

    /** * AntColony型,蚁群 */
    private AntColony antColony;

    /** * ACO型,本轮所属的蚁群算法 */
    private final ACO aco;

    /** * double[][]型,(i, j)代表城市i到城市j的信息素强度在本轮中的增量 */
    private double[][] delta_tau_s;

    /** * double[][]型,(i, j)代表本轮结束后城市i到城市j的信息素强度 */
    private double[][] tau_s;

    /** * Way型,本轮最佳路径 */
    private Way best_way;

    static {
        ALPHA = 16;
        BETA = 5;
        Q = 1.0;
    }

    /** * 构造函数 * @param aco ACO型,本轮所属的蚁群算法 */
    public Round(ACO aco) {
        this.aco = aco;

        // 生成蚁群
        this.antColony = new AntColony();

        this.delta_tau_s = new double[World.getCITY_ARRAY().length][World.getCITY_ARRAY().length];
        for(int i = 0; i < this.delta_tau_s.length; i++) {
            for(int j = 0; j < this.delta_tau_s[i].length; j++) {
                this.delta_tau_s[i][j] = 0;
            }
        }

        for(int i = 0; i < (World.getCITY_ARRAY().length - 1); i++) {
            // 过了一个单位时间
            aco.setT(aco.getT() + 1);

            // 在这一个单位时间内每只蚂蚁的动作
            for(int k = 0; k < this.antColony.getAnt_array().length; k++) {
                // 到达下一个要访问的城市
                City next_city = this.choose_next_city(i, this.antColony.getAnt_array()[k]);
                // 将新到达的城市放入禁忌表
                this.antColony.getAnt_array()[k].getTabu()[i + 1] = next_city;
                // 更新爬过的路径长度
                this.antColony.getAnt_array()[k].setL(this.antColony.getAnt_array()[k].getL() + World.d_city(this.antColony.getAnt_array()[k].getTabu()[i], next_city));
            }
        }

        // 将每一只蚂蚁的信息反馈给蚁群系统
        this.best_way = new Way(this.antColony.getAnt_array()[0].getTabu(), this.antColony.getAnt_array()[0].getL());
        for(int k = 0; k < this.antColony.getAnt_array().length; k++) {
            if(this.antColony.getAnt_array()[k].getL() < this.best_way.getL()) {
                this.best_way = new Way(this.antColony.getAnt_array()[k].getTabu(), this.antColony.getAnt_array()[k].getL());
            }

            // 更新该蚂蚁产生的信息素
            double tau_k = Round.Q / this.antColony.getAnt_array()[k].getL();
            int city_1_index = 0;
            int city_2_index = 1;
            while(city_2_index < this.antColony.getAnt_array()[k].getTabu().length) {
                int city_1_id = this.antColony.getAnt_array()[k].getTabu()[city_1_index].getId();
                int city_2_id = this.antColony.getAnt_array()[k].getTabu()[city_2_index].getId();
                this.delta_tau_s[city_1_id][city_2_id] = this.delta_tau_s[city_1_id][city_2_id] + tau_k;
                this.delta_tau_s[city_2_id][city_1_id] = this.delta_tau_s[city_1_id][city_2_id];

                city_1_index++;
                city_2_index++;
            }
        }

        // 休整一个单位时间
        aco.setT(aco.getT() + 1);
    }

    /** * 某只蚂蚁选择下一个要前往的城市 * @param step int型,本轮中某只蚂蚁已爬的步数 * @param ant Ant型,蚂蚁 * @return City型,下一个要前往的城市 */
    private City choose_next_city(int step, Ant ant) {
        City back = null;

        // 得到该蚂蚁所有可选择的城市
        List<City> allowed_city = new ArrayList<City>();
        for(int i = 0; i < World.getCITY_ARRAY().length; i++) {
            if(!ant.inTabu(step, World.getCITY_ARRAY()[i])) {
                allowed_city.add(World.getCITY_ARRAY()[i]);
            }
        }

        // 计算每一个可达城市的概率信息的分子及分母
        double[] numerator = new double[allowed_city.size()];
        double denominator = 0;
        for(int i = 0; i < numerator.length; i++) {
            // 两城市间的距离
            double d = World.d_city(ant.getTabu()[step], allowed_city.get(i));
            // 两城市间的启发式信息
            double eta = 1 / d;
            // 分子
            numerator[i] = Math.pow(this.aco.getTau_s()[ant.getTabu()[step].getId()][allowed_city.get(i).getId()], Round.ALPHA) + Math.pow(eta, Round.BETA);
            // 累加分母
            denominator = denominator + numerator[i];
        }

        // 得到每一个可达城市的概率
        double[] p = new double[allowed_city.size()];
        for(int i = 0; i < p.length; i++) {
            p[i] = numerator[i] / denominator;
        }

        // 存储每一个可达城市的概率信息
        double begin_p = 0;
        double end_p = 0;
        ChooseCity[] p_allowed_city = new ChooseCity[allowed_city.size()];
        for(int i = 0; i < p_allowed_city.length; i++) {
            if(i == p_allowed_city.length -1) {
                end_p = 1.0;
            } else {
                end_p = begin_p + p[i];
            }
            p_allowed_city[i] = new ChooseCity(begin_p, end_p, allowed_city.get(i));
            begin_p = end_p;
        }

        // 依概率选择一个城市
        double random = Math.random();
        for(int i = 0; i < p_allowed_city.length; i++) {
            if(p_allowed_city[i].random_in(random)) {
                back = p_allowed_city[i].getCity();
            }
        }
        return back;
    }

    public AntColony getAntColony() {
        return antColony;
    }

    public double[][] getDelta_tau_s() {
        return delta_tau_s;
    }

    public Way getBest_way() {
        return best_way;
    }

    public double[][] getTau_s() {
        return tau_s;
    }

    public void setTau_s(double[][] tau_s) {
        this.tau_s = tau_s;
    }
}

3.3.5 model.Way.java

记录了某只蚂蚁完成爬行后所走过的一条通路,实为问题的一个解。

package model;

import model.world.City;

/** * 一条通路 * @author WangYikai */
public class Way {
    /** * City[]型,通路 */
    private final City[] way;

    /** * double型,通路长度 */
    private final double l;

    /** * 构造函数 * @param way City[]型,通路 * @param l double型,通路长度 */
    public Way(City[] way, double l) {
        this.way = way;
        this.l = l;
    }

    public City[] getWay() {
        return way;
    }

    public double getL() {
        return l;
    }
}

3.3.6 model.antcolony.Ant.java

蚂蚁类

package model.antcolony;

import model.world.City;
import model.world.World;

/** * 蚂蚁 * @author WangYikai */
public class Ant {
    /** * int型,id */
    private final int id;

    /** * City[]型,禁忌表:蚂蚁已访问完成的城市 */
    private City[] tabu;

    /** * double型,蚂蚁经过的路径长度 */
    private double l;

    /** * 构造函数 * @param id int型,id * @param tabu_0 City型,蚂蚁的出生城市 */
    public Ant(int id, City tabu_0) {
        this.id = id;
        this.tabu = new City[World.getCITY_ARRAY().length];

        this.tabu[0] = tabu_0;

        this.l = 0.0;
    }

    /** * 判断某城市是否在该蚂蚁的禁忌表中 * @param step int型,本轮中某只蚂蚁已爬的步数 * @param city City型,城市 * @return true:在;false:不在 */
    public boolean inTabu(int step, City city) {
        boolean back = false;
        for(int i = 0; i < (step + 1); i++) {
            if(city.getId() == this.tabu[i].getId()) {
                back = true;
                break;
            }
        }
        return back;
    }

    public City[] getTabu() {
        return tabu;
    }

    public int getId() {
        return id;
    }

    public double getL() {
        return l;
    }

    public void setL(double l) {
        this.l = l;
    }
}

3.3.7 model.antcolony.AntColony.java

蚁群类

package model.antcolony;

import model.world.World;

/** * 蚁群 * @author WangYikai */
public class AntColony {
    /** * int型,蚁群规模 */
    private static final int ANT_ARRAY_LENGTH;

    /** * Ant[]型,蚁群中的所有蚂蚁 */
    private final Ant[] ant_array;

    static {
        ANT_ARRAY_LENGTH = World.getCITY_ARRAY().length;
    }

    /** * 构造函数 */
    public AntColony() {
        this.ant_array = new Ant[AntColony.ANT_ARRAY_LENGTH];

        // 将蚂蚁均匀放入所有城市中
        for(int i = 0; i < this.ant_array.length; i++) {
            this.ant_array[i] = new Ant(i, World.getCITY_ARRAY()[(i % World.getCITY_ARRAY().length)]);
        }
    }

    public Ant[] getAnt_array() {
        return ant_array;
    }
}

3.3.8 model.world.City.java

城市类

package model.world;

import tool.Point;

/** * 城市 * @author WangYikai */
public class City {
    /** * int型,城市在数组中的编号 */
    private final int id;

    /** * double型,城市坐标点 */
    private final Point point;

    /** * 构造函数 * @param x double型,x坐标 * @param y double型,y坐标 * @param id int型,城市在数组中的编号 */
    public City(int id, double x, double y) {
        this.id = id;
        this.point = new Point(x, y);
    }

    public int getId() {
        return id;
    }

    public Point getPoint() {
        return point;
    }
}

3.3.9 model.world.World.java

世界类。也是蚁群所存在的环境。在本问题中即为所有城市。

该类没有公有的构造函数;所有信息都在本类第一次访问时通过static代码块导入。

关于城市数据的获取参考了CSDN上Fashionxu所写的名为 JAVA[2] 的博客,其中提到可从TSPLIB中下载,得到*ALL_tsp.tar.gz压缩包,解压后其中有很多TSP城市的数据文件,以att48.tsp为例,下载下来的文件内容如下:

NAME : att48
COMMENT : 48 capitals of the US (Padberg/Rinaldi)
TYPE : TSP
DIMENSION : 48
EDGE_WEIGHT_TYPE : ATT
NODE_COORD_SECTION
1 6734 1453
2 2233 10
3 5530 1424
4 401 841
5 3082 1644
6 7608 4458
7 7573 3716
8 7265 1268
9 6898 1885
10 1112 2049
11 5468 2606
12 5989 2873
13 4706 2674
14 4612 2035
15 6347 2683
16 6107 669
17 7611 5184
18 7462 3590
19 7732 4723
20 5900 3561
21 4483 3369
22 6101 1110
23 5199 2182
24 1633 2809
25 4307 2322
26 675 1006
27 7555 4819
28 7541 3981
29 3177 756
30 7352 4506
31 7545 2801
32 3245 3305
33 6426 3173
34 4608 1198
35 23 2216
36 7248 3779
37 7762 4595
38 7392 2244
39 3484 2829
40 6271 2135
41 4985 140
42 1916 1569
43 7280 4899
44 7509 3239
45 10 2676
46 6807 2993
47 5185 3258
48 3023 1942
EOF

这里我们仿照Fashionxu的方法,对该文件做以下处理,变为:

48
1 6734 1453
2 2233 10
3 5530 1424
4 401 841
5 3082 1644
6 7608 4458
7 7573 3716
8 7265 1268
9 6898 1885
10 1112 2049
11 5468 2606
12 5989 2873
13 4706 2674
14 4612 2035
15 6347 2683
16 6107 669
17 7611 5184
18 7462 3590
19 7732 4723
20 5900 3561
21 4483 3369
22 6101 1110
23 5199 2182
24 1633 2809
25 4307 2322
26 675 1006
27 7555 4819
28 7541 3981
29 3177 756
30 7352 4506
31 7545 2801
32 3245 3305
33 6426 3173
34 4608 1198
35 23 2216
36 7248 3779
37 7762 4595
38 7392 2244
39 3484 2829
40 6271 2135
41 4985 140
42 1916 1569
43 7280 4899
44 7509 3239
45 10 2676
46 6807 2993
47 5185 3258
48 3023 1942

简单解释如下:

  1. 第一行的48即为城市数;以下的48行分别代表48个具体城市的参数
  2. 每个表示城市的行都有3个参数,以空格分割,第一个是序号,从1~48;后两个依次为直角坐标系下的x,y坐标
  3. Java可直接读取.tsp文件(详见代码);和读取txt的方式类似;先读取一行,而后以空格符分割一行中的内容;
package model.world;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

/** * 蚂蚁的活动空间 * @author WangYikai */
public class World {
    /** * String型,存放城市信息的tsp文件路径 */
    private static final String CITY_FILE;

    /** * BufferedReader型,城市tsp文件中的城市基本信息 */
    private static BufferedReader CITY_DATA;

    /** * int型,城市规模 */
    private static int CITY_COUNT;

    /** * City[]型,城市数组 */
    private static City[] CITY_ARRAY;

    static {
        // 存储城市信息文件的路径
        CITY_FILE = "data/city20.tsp";

        // 城市信息
        try {
            CITY_DATA = new BufferedReader(new InputStreamReader(new FileInputStream(World.CITY_FILE)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        // 城市规模
        try {
            CITY_COUNT = Integer.valueOf(World.CITY_DATA.readLine());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 城市数组
        String str_buff = null;
        CITY_ARRAY = new City[World.CITY_COUNT];
        for(int i = 0; i < World.CITY_ARRAY.length; i++) {
            try {
                str_buff = World.CITY_DATA.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }

            String[] coordinate = str_buff.split(" ");
            World.CITY_ARRAY[i] = new City(i, Double.valueOf(coordinate[1]), Double.valueOf(coordinate[2]));
        }
    }

    public static City[] getCITY_ARRAY() {
        return CITY_ARRAY;
    }

    /** * 计算两城市间的距离 * @param city_1 City型,城市 * @param city_2 City型,城市 * @return 两城市间的距离 */
    public static double d_city(City city_1, City city_2) {
        double back = Math.sqrt(Math.pow(city_1.getPoint().getX() - city_2.getPoint().getX(), 2) + Math.pow(city_1.getPoint().getY() - city_2.getPoint().getY(), 2));
        return back;
    }
}

model层结束,该层为算法的核心部分,实现了算法所有的理论功能。

参考文献

[1] 李世勇等.蚁群算法及其应用.哈尔滨.哈尔滨工业大学出版社.2004
[2]Fashionxu.CSDN.蚁群优化算法的JAVA实现.2010

    原文作者:蚁群算法
    原文地址: https://blog.csdn.net/u012605877/article/details/44562529
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞