粒子群算法求解旅行商问题TSP (JAVA实现)

粒子群算法求解旅行商问题TSP

写在开头:
最近师妹的结课作业问我,关于使用粒子群求解TSP问题的思路。我想了想,自己去年的作业用的是遗传算法,貌似有些关联,索性给看了看代码。重新学习了一遍粒子群算法,在这里记录一下,算是对知识的总结,巩固一下。


正文部分

本文主要是使用粒子群来求解旅行商问题,即TSP问题,这里主要讲解代码和实现思路,原理会简单带过。详细的具体原理,请读者移步参考链接。
本文将从如下几个方面进行描述:

  • 1. 粒子群简单介绍
  • 2. TSP问题简单介绍
  • 3. JAVA代码实现
  • 4. 运行结果展示
  • 5. 源码下载链接
1. 粒子群简单介绍

粒子群算法简称PSO,它的基本思想是模拟鸟群的捕食行为。设想这样一个场景:一群鸟在随机搜索食物。在这个区域里只有一块食物。所有的鸟都不知道食物在那里。但是他们知道当前的位置离食物还有多远。那么找到食物的最优策略是什么呢。最简单有效的就是搜寻目前离食物最近的鸟的周围区域。

PSO从这种模型中得到启示并用于解决优化问题。PSO中,每个优化问题的解都是搜索空间中的一只鸟。我们称之为“粒子”。所有的粒子都有一个由被优化的函数决定的适应值(fitness value),每个粒子还有一个速度决定他们飞翔的方向和距离。然后粒子们就追随当前的最优粒子在解空间中搜索。

PSO 初始化为一群随机粒子(随机解)。然后通过迭代找到最优解。在每一次迭代中,粒子通过跟踪两个”极值”来更新自己。第一个就是粒子本身所找到的最优解,这个解叫做个体极值pBest。另一个极值是整个种群目前找到的最优解,这个极值是全局极值gBest。另外也可以不用整个种群而只是用其中一部分作为粒子的邻居,那么在所有邻居中的极值就是局部极值。

2. TSP问题简单介绍

TSP问题(Travelling Salesman Problem)即旅行商问题,又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

TSP问题是一个组合优化问题。该问题可以被证明具有NPC计算复杂性。TSP问题可以分为两类,一类是对称TSP问题(Symmetric TSP),另一类是非对称问题(Asymmetric TSP)。

3. JAVA代码实现

首先整理一下具体实现逻辑,具体流程如下:

  • 初始化种群
  • 初始化惯性因子,即自身的交换序列
  • 寻找每个粒子的历代中的最优解
  • 寻找全局最优解
  • 计算每个个体的自身最优解的交换序列
  • 计算每个个体对全局最优解的交换序列
  • 进化——其实就是交换
    交换序列对由3部分组成:自身交换序列、自身最优解的交换序列、全局最优解的交换序列。

  • 打印结果

使用面向对象的思想,首先定义一个city.java城市实体类,有id、name、经度和纬度属性。

public class City {
    private int mId;       //城市编号
    private String mName;  //城市名称
    private float mLongitude; //经度
    private float mLatitude; //经度

    public City(int id, String name, float longitude, float latitude) {
        mId = id;
        mName = name;
        mLongitude = longitude;
        mLatitude = latitude;
    }

定义一个粒子实体类Unit.java,包括行走城市的路径path和适应度fitness。并添加两个方法,输出城市序列和计算自己的适应度。

private int[] mPath;  //行走的城市路径,存储城市编号
    private int mFitness; //适应度值,为当前个体走这个路径的总距离。越小越好。

    public Unit(int[] path) {
        mPath = path;
        mFitness = calculateFitness();
    }
        public void printPath() {
        if (mPath == null) {
            System.out.println("mPath为null,当前个体的路径为空");
        } else {
            for (int i = 0; i < mPath.length - 1; i++) {
                System.out.print(CityLab.getInstance().getmCities().get(mPath[i]).getName() + "——》");
            }
            System.out.println(CityLab.getInstance().getmCities().get(mPath[mPath.length - 1]).getName());
        }
    }

    public void upDateFitness() {
        this.mFitness = calculateFitness();
    }

    /**
     * 计算当前路径的适应值,即为路径长度
     */
    public int calculateFitness() {
        //根据经纬度计算距离
        //近似计算:0.00001度,距离相差约1米;0.01,距离相差1000米.1度,距离相差100km
        int distance = 0;  //单位千米(km)
        int n = mPath.length;
        for (int i = 1; i < n; i++) {
            City c1 = CityLab.getInstance().getmCities().get(mPath[i - 1]);
            City c2 = CityLab.getInstance().getmCities().get(mPath[i]);
            distance += Math.sqrt(Math.pow(100 * (c1.getLatitude() - c2.getLatitude()), 2) + Math.pow(100 * (c1.getLongitude() - c2.getLongitude()), 2));
        }
        distance += Math.sqrt(Math.pow(100 * (CityLab.getInstance().getmCities().get(mPath[0]).getLatitude() - CityLab.getInstance().getmCities().get(mPath[n - 1]).getLatitude()), 2) + Math.pow(100 * (CityLab.getInstance().getmCities().get(mPath[0]).getLongitude() - CityLab.getInstance().getmCities().get(mPath[n - 1]).getLongitude()), 2));
        return distance;
    }

接下来,定义交换对实体类,SO.java,包含两个属性x和y,用来做交换的。

public class SO {
    private int x;
    private int y;

    public SO(int x, int y) {
        this.x = x;
        this.y = y;
    }

最后使用单例模式来管理所有的城市列表CityLab.java

/**
 * 城市单例
 */
public class CityLab {
    private static CityLab mCityLab = null;
    private ArrayList<City> mCities = new ArrayList<>();

    private CityLab() {
        //读取文件,添加城市信息到mcities中
        String filename = "d://city.csv";
        String strbuff;
        BufferedReader data = null;
        try {
            data = new BufferedReader(new InputStreamReader(
                    new FileInputStream(filename)));
            int id = 1;
            while (true) {
                //读取一行数据:北京,116.41667,39.91667
                strbuff = data.readLine();
                if (strbuff == null) {
                    break;
                }
                String[] arr = strbuff.split(",");
                City c = new City(id, arr[1], valueOf(arr[2]), valueOf(arr[3]));
                id++;
                mCities.add(c);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static CityLab getInstance() {
        if (mCityLab == null) {
            mCityLab = new CityLab();
        }
        return mCityLab;
    }

    public ArrayList<City> getmCities() {
        return mCities;
    }
}

实体类我们定义好了,接下来看具体实现粒子群求解TSP问题的过程。
具体PSO.java类如下:

/**
 * 粒子群求解TSP旅行商问题
 * 更新公式:Vii=wVi+ra(Pid-Xid)+rb(Pgd-Xid)
 */
public class PSO {
    private int scale; //种群规模
    private ArrayList<Unit> mUnits = new ArrayList<>();   //粒子群

    private int MAX_GEN;// 迭代次数
    private float w;    //惯性权重
    private int cityNum = 25;    //城市数量

    private HashMap<Integer, Unit> Pd = new HashMap<>();    //一颗粒子历代中出现最好的解
    private Unit Pgd;   // 整个粒子群经历过的的最好的解,每个粒子都能记住自己搜索到的最好解
    private int bestT;// 最佳出现代数

    private ArrayList<ArrayList<SO>> listV = new ArrayList<>();    //自身交换序列,即所谓的惯性因子

    Random random = new Random();

    /**
     * 构造方法
     *
     * @param scale
     * @param MAX_GEN
     * @param w
     */
    public PSO(int scale, int MAX_GEN, float w) {
        this.scale = scale;
        this.MAX_GEN = MAX_GEN;
        this.w = w;
    }

    /**
     * 初始化参数配置
     */
    private void init() {
        cityNum = CityLab.getInstance().getmCities().size();
        random = new Random(System.currentTimeMillis());

    }

    /**
     * 初始化种群
     */
    private void initGroup() {
        for (int k = 0; k < scale; k++) {
            int[] path = new int[cityNum];
            for (int i = 0; i < cityNum; ) {
                //随机生成一个城市路径
                //int s = random.nextInt(max)%(max-min+1) + min;
                int s = random.nextInt(65535) % cityNum;
                int j;
                for (j = 0; j < i; j++) {
                    if (s == path[j]) {
                        break;
                    }
                }
                if (i == j) {
                    path[i] = s;
                    i++;
                }
            }
            Unit unit = new Unit(path);
            mUnits.add(unit);
        }
    }

    /**
     * 初始化自身的交换序列即惯性因子
     */
    private void initListV() {
        for (int i = 0; i < scale; i++) {
            ArrayList<SO> list = new ArrayList<>();
            int n = random.nextInt(cityNum - 1) % (cityNum);    //随机生成一个数,表示当前粒子需要交换的对数
            for (int j = 0; j < n; j++) {
                //生成两个不相等的城市编号x,y
                int x = random.nextInt(cityNum - 1) % (cityNum);
                int y = random.nextInt(cityNum - 1) % (cityNum);
                while (x == y) {
                    y = random.nextInt(cityNum - 1) % (cityNum);
                }

                //x不等于y
                SO so = new SO(x, y);
                list.add(so);
            }
            listV.add(list);
        }
    }

    public void solve() {
        initGroup();

        initListV();

        //挑选最好的个体
        for (int i = 0; i < scale; i++) {
            Pd.put(i, mUnits.get(i));
        }
        Pgd = Pd.get(0);
        for (int i = 0; i < scale; i++) {
            if (Pgd.getFitness() > Pd.get(i).getFitness()) {
                Pgd = Pd.get(i);
            }
        }
        System.out.println("初始化最好结果为:" + Pgd.getFitness());
        Pgd.printPath();

        // 进化
        evolution();

        // 打印
        System.out.println("==================最后粒子群=====================");

        System.out.println("最佳长度出现代数:");
        System.out.println(bestT);
        System.out.println("最佳长度");
        System.out.println(Pgd.getFitness());
        System.out.println("最佳路径:");
        Pgd.printPath();

    }

    /**
     * 进化
     */
    private void evolution() {
        for (int t = 0; t < MAX_GEN; t++) {
            for (int k = 0; k < scale; k++) {
                ArrayList<SO> vii = new ArrayList<>();
                //更新公式:Vii=wVi+ra(Pid-Xid)+rb(Pgd-Xid)
                //第一部分,自身交换对
                int len = (int) (w * listV.get(k).size());
                for (int i = 0; i < len; i++) {
                    vii.add(listV.get(k).get(i));
                }

                //第二部分,和当前粒子中出现最好的结果比较,得出交换序列
                //ra(Pid-Xid)
                ArrayList<SO> a = minus(mUnits.get(k).getPath(), Pd.get(k).getPath());
                float ra = random.nextFloat();
                len = (int) (ra * a.size());
                for (int i = 0; i < len; i++) {
                    vii.add(a.get(i));
                }

                //第三部分,和全局最优的结果比较,得出交换序列
                //rb(Pgd-Xid)
                ArrayList<SO> b = minus(mUnits.get(k).getPath(), Pgd.getPath());
                float rb = random.nextFloat();
                len = (int) (rb * b.size());
                for (int i = 0; i < len; i++) {
                    vii.add(b.get(i));
                }

                listV.remove(0);    //移除当前,加入新的
                listV.add(vii);

                //执行交换,生成下一个粒子
                exchange(mUnits.get(k).getPath(), vii);
            }

            //更新适应度的值,并挑选最好的个体
            for (int i = 0; i < scale; i++) {
                mUnits.get(i).upDateFitness();
                if (Pd.get(i).getFitness() > mUnits.get(i).getFitness()) {
                    Pd.put(i, mUnits.get(i));
                }
                if (Pgd.getFitness() > Pd.get(i).getFitness()) {
                    Pgd = Pd.get(i);
                    bestT = t;
                }
            }
            //打印当前代的结果
            if (t % 100 == 0) {
                // 打印
                System.out.println("--------第"+t+"代的最佳结果为-----------");
                System.out.println(Pgd.getFitness());
                System.out.println("最佳路径:");
                Pgd.printPath();
            }
        }
    }

    /**
     * 执行交换,更新粒子
     *
     * @param path
     * @param vii  存储的是需要交换的下标对
     */
    private void exchange(int[] path, ArrayList<SO> vii) {
        int tmp;
        for (SO so : vii) {
            tmp = path[so.getX()];
            path[so.getX()] = path[so.getY()];
            path[so.getY()] = tmp;
        }
    }


    /**
     * 生成交换对,把a变成和b一样,返回需要交换的下标对列表
     *
     * @param a
     * @param b
     * @return
     */
    private ArrayList<SO> minus(int[] a, int[] b) {
        int[] tmp = a.clone();
        ArrayList<SO> list = new ArrayList<>();
        int index = 0;
        for (int i = 0; i < b.length; i++) {
            if (tmp[i] != b[i]) {
                //在tmp中找到和b[i]相等的值,将下标存储起来
                for (int j = i + 1; j < tmp.length; j++) {
                    if (tmp[j] == b[i]) {
                        index = j;
                        break;
                    }
                }
                SO so = new SO(i, index);
                list.add(so);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        PSO pso = new PSO(30, 5000, 0.5f);
        pso.init();
        pso.solve();

    }
}

注释写得很详细,这里不再赘述,各位可以直接运行代码,通过debug的方式,这样我觉得很容易理解的。看源码是学习编程最快的方式

最后感谢大家的观看,如果有问题请评论区留言,本人将尽力解答。

4. 运行结果展示

这里写图片描述

5. 源码下载链接

附上源码下载链接:
https://download.csdn.net/download/u012324136/10513646

声明
在参考下面链接的基础上,对代码进行重构,使用java面向对象的思想,是代码可读性大大增强

参考链接:
https://blog.csdn.net/zuochao_2013/article/details/53431767?ref=myread
https://blog.csdn.net/wangqiuyun/article/details/12515203
https://blog.csdn.net/guognib/article/details/30034821

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值