TSP问题及蚁群算法理解与实现

来源:互联网 发布:淘宝买啥弓箭好 编辑:程序博客网 时间:2024/05/01 01:17

一. TSP问题理解

tsp(Travelling Salesman Problem)问题是一个著名的NPC问题。
可以描述为:给定一个图G=(V,E)或<V ,E>,从图中的某一个顶点出发遍历所有的顶点且每个顶点只能被遍历一次后回到起点,即完成一个哈密尔顿环,在这个过程中要使得路径上的开销最小(此处的“开销”可以由边的权值、顶点之间的距离等来量化表示)

二.蚁群算法思想理解

蚁群算法的思想很好理解:模拟蚂蚁的行为。
通常蚂蚁在寻找食物的过程中,会沿途留下一些类似气味的东西以供后来者参考,在蚁群算法中,我们把它称为信息素。后来的蚂蚁会根据前面的蚂蚁留下的信息素的值来选择路径,从而获取最优的路径。
蚁群算法主要通过以下步骤完成:
1.初始化阶段:初始化蚂蚁的选路(我采用的是初始时蚂蚁随机选择)、初始化某些参数(比如信息素的挥发率、初始的信息素值等)
2.选择下一个节点:在这一个阶段,有专门研究选择下一个节点的算法的,在我的实验中采用的是轮盘赌的方法。后文会展开叙述。
3.更新信息素矩阵:根据上一只蚂蚁经过某条路径后留下的信息素,来更新信息素矩阵,为后面的蚂蚁导航。具体的选择方法后文会展开叙述。
4.检查结束标志,完成输出:结束标志通常来说有两种。一种是理想意义上的达到了收敛;另一种就是目前大多数采用的(也是我所采用的),就是设置一个迭代次数,假设在迭代次数完成的时候达到收敛。

三. 我的蚁群算法

通常来说,蚁群算法的起点终点是相同的(为了形成一个哈密尔顿环),而且起点是随机选择的。
我主要是为了自己在做的项目后面的任务,所以将最原始的TSP问题改成了下面的问题:
给定起点和终点,从起点出发,经过图中的每个点且只经过一次,到达终点,使得路径开销最小。


我采用是att48这个数据集,点是数据集中的48个不同的城市,边的开销是用两点间的欧式距离来表示的。
在开始我的蚁群之前,需要先解释一些代码中和后面都会用到的变量:
tabu表:禁忌表(这个名字也不知道是谁取的,特别中二)用来保存已经访问过的点。原因就是为了保证每个点只能被访问一次。tabu里的点就是不能再被访问的;
allowCities表:这个表里存放的是允许访问的点;
distance[ ][ ]:这个就是用来记录两点之间距离的数组;
firstCity:起点城市   lastCity:终点城市     ;--------起点和终点我采用的是在48个城市中产生40个随机数对
pheromone[ ][ ]:信息素矩阵,用来存放每两个点之间的路径的信息素值;
还有一些参数:
alpha,beta,rho ;  ----更新信息素矩阵的时候会用到一个概率计算公式(一会儿会贴出来)中的一些参数
MAX_GEN ;; -----迭代次数
中间过程保存变量:
tourLength:用来保存每只蚂蚁每次走过的路径
selectCity:用来保存当前选择的城市
保存结果所需变量:
besTour[ ]:保存最佳路径。每次迭代的时候只要最新的路径比原来短(更优)就更新;
bestLength:最小开销。


实现构思:
由于是java实现的,面向对象的思想很重要,所有大体上来说,需要实现两个类。
一个是针对蚂蚁对象所实现的类Ant,主要是实现单只蚂蚁完成的动作:初始化单只蚂蚁所需变量()、初始化信息素矩阵、初始化tabu、allowCities、选择下一个城市();
第二个类是针对由单只蚂蚁组成的类蚁群,该类中的每个对象都是一群蚂蚁(因此需要定义一个由蚂蚁对象组成的类,每只蚂蚁都需要完成Ant () 类中定义的功能),所以需要实现由一群蚂蚁才能完成的功能:初始计算图中两两点之间的距离(我采用的是计算两个点之间的欧式距离)、更新信息素矩阵、按设定的次数迭代获得结果、打印输出。
由main( )方法产生随机起点和随机终点。

四. java实现

Ant类
/** * Created by coco on 17-10-20. * 蚂蚁类,进行路径搜索的载体 */import java.util.Random;import java.util.Vector;public class Ant implements Cloneable {    private Vector tabu; // 禁忌表    private Vector allowedCities; // 允许搜索的城市    private float[][] delta; // 信息数变化矩阵    private int[][] distance; // 距离矩阵    private float alpha;    private float beta;    private int tourLength; // 路径长度    private int cityNum; // 城市数量    private int firstCity; // 起始城市    private int lastCity;//终点城市    private int currentCity; // 当前城市    public Ant() {        cityNum = 30;        tourLength = 0;    }    /**     * Constructor of Ant     *     * @param num     *            城市数量     */    public Ant(int num) {        cityNum = num;        tourLength = 0;    }    /**     * 初始化蚂蚁,采用固定起始位置和终点位置     *     * @param distance     *            距离矩阵     * @param a     *            alpha     * @param b     *            beta     *     */    public void init(int[][] distance, float a, float b,int firstCity,int lastCity) {        alpha = a;        beta = b;        // 初始允许搜索的城市集合        allowedCities = new Vector();        // 初始禁忌表        tabu = new Vector();        // 初始距离矩阵        this.distance = distance;        // 初始信息数变化矩阵为0        delta = new float[cityNum][cityNum];        for (int i = 0; i < cityNum; i++) {            Integer integer = i;            allowedCities.add(integer);            for (int j = 0; j < cityNum; j++) {                delta[i][j] = 0.f;            }        }        // 设置起始城市和终点城市        this.firstCity = firstCity;        this.lastCity = lastCity;        // 在允许搜索的城市集合中查找并移除起始城市        for (Integer i : allowedCities) {            if (i.intValue() == firstCity) {                allowedCities.remove(i);                break;            }        }        // 将起始城市添加至禁忌表        tabu.add(Integer.valueOf(firstCity));        // 当前城市为起始城市        currentCity = firstCity;    }    /**     *     * 选择下一个城市     *     * @param pheromone     *            信息素矩阵     */    public void selectNextCity(float[][] pheromone) {        float[] p = new float[cityNum];        float sum = 0.0f;        // 计算分母部分        for (Integer i : allowedCities) {            sum += Math.pow(pheromone[currentCity][i.intValue()], alpha)                    * Math.pow(1.0 / distance[currentCity][i.intValue()], beta);        }        // 计算概率矩阵        for (int i = 0; i < cityNum; i++) {            boolean flag = false;            for (Integer j : allowedCities) {                if (i == j.intValue()) {                    p[i] = (float) (Math.pow(pheromone[currentCity][i], alpha) * Math                            .pow(1.0 / distance[currentCity][i], beta)) / sum;                    flag = true;                    break;                }            }            if (flag == false) {                p[i] = 0.f;            }        }        // 轮盘赌选择下一个城市        Random random = new Random(System.currentTimeMillis());        float selectP ;        int selectCity = 0;        if (allowedCities.size() == 1)        {            selectCity = lastCity;        }        else        {            selectP = random.nextFloat()*(1 - p[lastCity]);            float sum1 = 0.f;            for (int i = 0; i < cityNum; i++)            {                if (i == lastCity)                {                    selectP = selectP + p[lastCity];                    sum1 += p[i];                }                else                {                    sum1 += p[i];                }                if (sum1 >= selectP)                {                    selectCity = i;                    break;                }            }        }        // 从允许选择的城市中去除select city        for (Integer i : allowedCities) {            if (i == selectCity) {                allowedCities.remove(i);                break;            }        }        // 在禁忌表中添加select city        tabu.add((selectCity));        // 将当前城市改为选择的城市        currentCity = selectCity;    }    /**     * 计算路径长度     *     * @return 路径长度     */    private int calculateTourLength() {        int len = 0;        //禁忌表tabu最终形式:起始城市,城市1,城市2...城市n,终点城市        for (int i = 0; i < this.tabu.size()-1; i++) {            len += distance[this.tabu.get(i).intValue()][this.tabu.get(i + 1)                    .intValue()];        }        return len;    }    public Vector getAllowedCities() {        return allowedCities;    }    public void setAllowedCities(Vector allowedCities) {        this.allowedCities = allowedCities;    }    public int getTourLength() {        tourLength = calculateTourLength();        return tourLength;    }    public void setTourLength(int tourLength) {        this.tourLength = tourLength;    }    public int getCityNum() {        return cityNum;    }    public void setCityNum(int cityNum) {        this.cityNum = cityNum;    }    public Vector getTabu() {        return tabu;    }    public void setTabu(Vector tabu) {        this.tabu = tabu;    }    public float[][] getDelta() {        return delta;    }    public void setDelta(float[][] delta) {        this.delta = delta;    }    public int getFirstCity() {        return firstCity;    }    public void setFirstCity(int firstCity) {        this.firstCity = firstCity;    }    public int getLastCity() {        return lastCity;    }}

ACO类
/** * Created by coco on 17-10-20. */import java.io.*;import java.util.logging.Logger;import static java.util.logging.Logger.getLogger;public class ACO {    private Ant[] ants; // 蚂蚁    private int antNum; // 蚂蚁数量    private int cityNum; // 城市数量    private int MAX_GEN; // 运行代数    private float[][] pheromone; // 信息素矩阵    private int[][] distance; // 距离矩阵    private int bestLength; // 最佳长度    private int[] bestTour; // 最佳路径    // 三个参数    private float alpha;    private float beta;    private float rho;    public ACO() {    }    /**     * constructor of ACO     *     * @param n     *            城市数量     * @param m     *            蚂蚁数量     * @param g     *            运行代数     * @param a     *            alpha     * @param b     *            beta     * @param r     *            rho     *     **/    public ACO(int n, int m, int g, float a, float b, float r) {        cityNum = n;        antNum = m;        ants = new Ant[antNum];        MAX_GEN = g;        alpha = a;        beta = b;        rho = r;    }    /**     * 初始化ACO算法类     * @param filename 数据文件名,该文件存储所有城市节点坐标数据     * @throws IOException     */    private void init(String filename,int firstCity,int lastCity) throws IOException {        // 读取数据        int[] x;        int[] y;        String strbuff;        BufferedReader data = new BufferedReader(new InputStreamReader(                new FileInputStream(filename)));        distance = new int[cityNum][cityNum];        x = new int[cityNum];        y = new int[cityNum];        for (int i = 0; i < cityNum; i++) {            // 读取一行数据,数据格式1 6734 1453            strbuff = data.readLine();            // 字符分割            String[] strcol = strbuff.split(" ");            x[i] = Integer.valueOf(strcol[0]);// x坐标            y[i] = Integer.valueOf(strcol[1]);// y坐标        }        // 计算距离矩阵        // 针对具体问题,距离计算方法也不一样,此处用的是att48作为案例,它有48个城市,距离计算方法为伪欧氏距离,最优值为10628        for (int i = 0; i < cityNum - 1; i++) {            distance[i][i] = 0; // 对角线为0            for (int j = i + 1; j < cityNum; j++) {                double rij = Math                        .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])                                * (y[i] - y[j])) / 10.0);                // 四舍五入,取整                int tij = (int) Math.round(rij);                if (tij < rij) {                    distance[i][j] = tij + 1;                    distance[j][i] = distance[i][j];                } else {                    distance[i][j] = tij;                    distance[j][i] = distance[i][j];                }            }        }        distance[cityNum - 1][cityNum - 1] = 0;        // 初始化信息素矩阵        pheromone = new float[cityNum][cityNum];        for (int i = 0; i < cityNum; i++) {            for (int j = 0; j < cityNum; j++) {                pheromone[i][j] = 0.1f; // 初始化为0.1            }        }        bestLength = Integer.MAX_VALUE;        bestTour = new int[cityNum + 1];        // 随机放置蚂蚁        for (int i = 0; i < antNum; i++) {            ants[i] = new Ant(cityNum);            ants[i].init(distance, alpha, beta,firstCity,lastCity);        }    }    public void solve() {        // 迭代MAX_GEN次        for (int g = 0; g < MAX_GEN; g++) {//            if(g%100 == 0)//                System.out.println("正在进行第 "+g+" 次迭代……");            // antNum只蚂蚁            for (int i = 0; i < antNum; i++) {                // i这只蚂蚁走cityNum步,完整一个TSP                for (int j = 1; j < cityNum; j++) {                    ants[i].selectNextCity(pheromone);                }                // 把这只蚂蚁起始城市加入其禁忌表中                // 禁忌表最终形式:起始城市,城市1,城市2...城市n,终点城市                      // 查看这只蚂蚁行走路径距离是否比当前距离优秀                if (ants[i].getTourLength() < bestLength) {                    // 比当前优秀则拷贝优秀TSP路径                    bestLength = ants[i].getTourLength();                 for (int k = 0; k < ants[i].getTabu().size() ; k++) {                        bestTour[k] = ants[i].getTabu().get(k).intValue();                    }              }                // 更新这只蚂蚁的信息数变化矩阵,对称矩阵                for (int j = 0; j < ants[i].getTabu().size() - 1; j++) {                    ants[i].getDelta()[ants[i].getTabu().get(j).intValue()][ants[i]                            .getTabu().get(j + 1).intValue()] = (float) (1. / ants[i]                            .getTourLength());                    ants[i].getDelta()[ants[i].getTabu().get(j + 1).intValue()][ants[i]                            .getTabu().get(j).intValue()] = (float) (1. / ants[i]                            .getTourLength());                }            }            // 更新信息素            updatePheromone();            for (int i = 0; i < antNum; i++) {                ants[i].init(distance, alpha, beta,ants[i].getFirstCity(),ants[i].getLastCity());            }        }        // 打印最佳结果         writeToFile();         System.out.flush();    }    // 更新信息素    private void updatePheromone() {        // 信息素挥发        for (int i = 0; i < cityNum; i++)            for (int j = 0; j < cityNum; j++)//                pheromone[i][j] = pheromone[i][j] * (1 - rho);                pheromone[i][j] = pheromone[i][j] * rho ;        // 信息素更新        for (int i = 0; i < cityNum; i++) {            for (int j = 0; j < cityNum; j++) {                for (int k = 0; k < antNum; k++) {                    pheromone[i][j] += ants[k].getDelta()[i][j];                }            }        }    }    //输出到文件    private void writeToFile(){        File of = new File("/home/coco/Downloads/result.txt");        try {            FileWriter fw = new FileWriter(of,true);            fw.append("The optimal length is: " + bestLength + "\r\n");            fw.append("The optimal tour is:");            for (int i = 0; i < cityNum; i++) {                fw.append(bestTour[i] + " ");            }            fw.append("\r\n");            fw.close();        }catch(Exception e){e.printStackTrace();}    }    /**     * @param args     * @throws IOException     */    public static void main(String[] args) throws Exception {        System.out.println("Start....");        ACO aco = new ACO(48, 100, 300, 1.f, 5.f, 0.5f);        for (int i = 0; i < 40; i++) {            int firstCity = (int)(Math.random()*48);            int lastCity = (int)(Math.random()*48);                        System.out.println("第"+ i+ "对起点终点\tfirstCity:"+firstCity + "\tlastCity:"+lastCity);            System.err.flush();            aco.init("/home/coco/Downloads/data.txt",firstCity,lastCity);            aco.solve();        }    }}

五.实验结果及结论分析



六.代码中小模块实现思考

1.初始化:设置初始化时信息素值为零,tabu表初始值设置为起点城市

2.选择下一个节点:我采用的是轮盘赌的方法

所谓轮盘赌方法就是根据概率选择,概率越大在整个圆盘中所占的比重越大。然后产生一个随机数,看它落在哪个区域,就选中哪个。很显然,概率越大的越容易被选中(在整个圆盘中所占的比重大嘛)。



具体实现是这样做的:

float selectP ;        int selectCity = 0;        if (allowedCities.size() == 1)        {            selectCity = lastCity;        }        else        {            selectP = random.nextFloat()*(1 - p[lastCity]);            float sum1 = 0.f;            for (int i = 0; i < cityNum; i++)            {                if (i == lastCity)                {                    selectP = selectP + p[lastCity];                    sum1 += p[i];                }                else                {                    sum1 += p[i];                }                if (sum1 >= selectP)                {                    selectCity = i;                    break;                }            }

为什么要这样做。原因就是,终点也是作为可选的点进入allowCities中的,如果在某一时刻,根据信息素计算出来选择终点的概率远远大于其他点的时候,就会出现进程很长时间卡住。但又不能将终点剔除。

所以如果一不小心选择了终点,那就把终点的概率加到产生的这个随机数身上,然后再在剩下的点里面采用轮盘赌,就能够实现在其他点里面选择概率最大的那个点了。


3.更新信息素矩阵:根据公式更新信息素矩阵。

4.检查终止条件,输出最优解


感谢 博客http://www.cnblogs.com/biaoyu/ (作者Alex Yu ) 对我的指导。

我在这篇博客的基础上进行了一定程度的修改。


感谢实验室大迷毛帮助我理解甚至手把手教我轮盘赌 大迷毛的blog


对于蚁群算法我的理解还不是特别的深入,在实验的过程中我还遇到过会出现一些坏数据的情况,如下:

起点为0,终点为44


原因是什么我还不是特别清楚,不知道是我实现的问题还是算法本身就存在的不确定性造成的。

还请各位大佬指正