学习笔记-迪杰斯特拉算法求最短路径

求最短路径问题

学习笔记-迪杰斯特拉算法求最短路径_第1张图片

迪杰斯特拉算法

迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个结点到其他结点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

  1. 设置出发顶点为 v,顶点集合 V{v1,v2,vi…},v 到 V 中各顶点的距离构成距离集合 Dis,Dis{d1,d2,di…},Dis集合记录着 v 到图中各顶点的距离(到自身可以看作 0,v 到 vi 距离对应为 di)
  2. 从 Dis 中选择值最小的 di 并移出 Dis 集合,同时移出 V 集合中对应的顶点 vi,此时的 v 到 vi 即为最短路径
  3. 更新 Dis 集合,更新规则为:比较 v 到 V 集合中顶点的距离值,与 v 通过 vi 到 V 集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为 vi,表明是通过 vi 到达的)
  4. 重复执行两步骤,直到最短路径顶点为目标顶点即可结束

图解过程

第一步,从起始点g开始,访问g的所以相邻顶点,g设为已访问
学习笔记-迪杰斯特拉算法求最短路径_第2张图片
一步过后,距离数组更新,选择距离最小的A,把A当作顶点,访问A的所有相邻未访问顶点,把A设为已访问。
学习笔记-迪杰斯特拉算法求最短路径_第3张图片
由于gab这条路是7大于了gb3,就保留gb这条路。接下来在没访问过的节点里找路径最短的,发现是e,把e当作顶点,访问e的所有相邻未访问顶点,把e设为已访问。
学习笔记-迪杰斯特拉算法求最短路径_第4张图片
两条路都不是最短的,都不保留,在没访问过的节点里找路径最短的,发现是b,把b当作顶点,访问b的所有相邻未访问顶点,把b设为已访问。
学习笔记-迪杰斯特拉算法求最短路径_第5张图片
在没访问过的节点里找路径最短的,发现是F,把F当作顶点,访问F的所有相邻未访问顶点,把F设为已访问。
学习笔记-迪杰斯特拉算法求最短路径_第6张图片
GFD比GBD短,更新为最短,在没访问过的节点里找路径最短的,发现是C,把C当作顶点,访问C的所有相邻未访问顶点,把C设为已访问。由于C的相邻节点都已经访问,就寻找下一个最短路径节点D,D的相邻节点也都访问,这时候每个节点都被访问过,退出程序,得到结果。

简单来说

迪杰斯特拉算法求最短路径的大概思路是,从第一个点开始,找它的所有相邻点(广度优先思想),记录累计路径值(第一次就是他们自己),找到这些路径值里最小的那个点(A),让它作为第二个点,从它开始,找到它的所有相邻点(当然可能有重复的),记录累计路径值(累计了第一个点(G)到第二个点(A)的路径值),找出并保留对每个相邻点最小的累计路径值,然后找到这些累计路径值里最小的那个点,让它作为第三个点,继续重复,直到每个点都作为过顶点(作为过顶点就认为它被访问过)。

代码

编写算法辅助类,该类由三个数组组成,
visit存放图的全部顶点,用来记录各个顶点的访问情况,0表示未访问1表示已访问,初始化时其他顶点都为0,传入的起始顶点为1表示已访问;
dis,长度和顶点个数相同,存放起始顶点到对应顶点的累计距离,初始化时其他位是一个极大数,代表路径暂时不通,起始顶点对应位数是0,代表自己到自己是0,对应上图每条线的蓝色数字和;
pre,存放各顶点的前驱顶点,用于记录最短路径,初始化时每位为-1,代表无前驱,对应上图每条线的蓝色字母,有几个顶点,就有几条路径,但是不必从头到尾记录一条路径,因为如上图,路径像树一样,只要记录直接前驱,就可以顺着一直找到根,因此用一个一维数组记录每一个顶点的直接前驱下标,然后到对应下标位置在找它的前驱,一直找到-1表示根的位置,就可以还原出一条路径。

辅助类需要具有的方法:

  1. 对三个数组初始化的方法,需要获得起始顶点下标,顶点个数
  2. 判断某顶点是否被访问
  3. 更新起始顶点到某顶点的距离为新值
  4. 获得起始顶点到某顶点的累计距离
  5. 更新某顶点的前驱顶点为某值
  6. 找到应该访问的下一个顶点。规则是,查找dis距离数组,找到未被访问过且距离最小的顶点,它就为应该访问的下一个顶点,把它设置成已访问。
  7. 输出三个数组,并输出每一条最短路径
//已访问顶点集合,用于帮助完成算法,辅助类
class VisitedVertex{
    int[] visit;//存放顶点,0未访问1已访问
    int[] dis;//存放出发顶点到各顶点的累计距离
    int[] pre;//存放各顶点的前驱顶点

    /**
     * 初始化方法,对三个数组初始化
     * @param index 代表起始顶点
     * @param num 顶点个数
     */
    public VisitedVertex(int index,int num){
        visit=new int[num];
        dis=new int[num];
        pre=new int[num];
        visit[index]=1;
        Arrays.fill(dis,65535);
        dis[index]=0;
        Arrays.fill(pre,-1);
    }

    /**
     * 判断index是否被访问过
     * @param index 下标
     * @return
     */
    public boolean in(int index){
        return visit[index]==1;
    }

    /**
     * 更新dis的值,出发点到index的最新距离为len
     * @param index
     * @param len
     */
    public void updateDis(int index,int len){
        dis[index]=len;
    }

    /**
     * 更新index的前驱顶点为v
     * @param index
     * @param v
     */
    public void updatePre(int index,int v){
        pre[index]=v;
    }

    /**
     * 返回从出发顶点到index的距离
     * @param index
     * @return
     */
    public int getDis(int index){
        return dis[index];
    }

    /**
     * 继续选择并返回新的访问顶点, 比如这里的 G 完后,就是 A 点作为新的访问顶点(注意不是出发顶点)
     */
    public int nextVisit(){
        int min=65535;
        int index=0;
        for (int i = 0; i <visit.length ; i++) {
            if(visit[i]==0&& dis[i]<min){
                min=dis[i];
                index=i;
            }
        }
        visit[index]=1;
        return index;
    }

    /**
     * 展示三个数组
     */
    public void show(char[] vertex){
        System.out.println("visit: "+Arrays.toString(visit));
        System.out.println("dis: "+Arrays.toString(dis));
        System.out.println("pre: "+Arrays.toString(pre));
		//路径,一个循环一条
        for (int i = 0; i <pre.length ; i++) {
            int j=pre[i];
            System.out.print(vertex[i]);
            while (j!=-1){
                System.out.print("->"+vertex[j]);
                j=pre[j];
            }
            System.out.println();
        }
    }

}

编写完辅助类后,编写图类,包括基本的顶点数组,邻接矩阵,辅助类对象实体。
图类应有的方法:

  1. 初始化方法
  2. 展示邻接矩阵
  3. 迪杰斯特拉算法辅助方法-用于更新三个数组(核心)。这个方法的功能是以传入的点为顶点,访问该顶点所有相邻顶点,根据规则更新三个数组。具体来说,传入的点为index,更新数组的条件是距离,所以遍历临界矩阵index的那一行,每次获得index到该相邻点的距离,由于要考虑累计最短,还要加上起始节点到index的距离,也就是dis[index]{如上图F点,fd一段是index到该相邻点D的距离,还要加上起始节点到index的距离GF一段,方得出累计距离},如果此距离小于起始顶点到该相邻点的累计距离(也就是dis[该相邻点]){如图上GFD小于GBD}(这也是dis数组起始值设为极大的原因),就将起始顶点到该相邻点的累计距离更新为那个新值,而那个相邻顶点的前驱更新为index(但此时不将此相邻结点设置成已访问,因为不到最后,这条路径不可保证为最小)
  4. 迪杰斯特拉算法: 需要传入一个起始顶点。根据起始顶点与顶点总数,获得辅助类实体对象(三个核心数组),首先以起始顶点作为顶点,更新三个数组(起始顶点默认已访问,辅助方法),即获取起始顶点的所有相邻顶点,将他们的距离更新到dis数组,并将他们的前驱顶点更新为起始顶点。接下来开始循环,每次循环以当前距离起始顶点最近的那个顶点作为新顶点(辅助方法,此时将该顶点更新为已访问),更新三个数组(获取该顶点的所有相邻顶点,比较距离,将最小距离更新到dis数组,并将他们的前驱顶点更新为该顶点。辅助方法),循环进行顶点减1次(每次选一个没访问的最近的,一共就选n-1次就全部选完,所以不用特别设计退出条件,只让循环进行n-1次就好)。
//图
class DijkstraGraph{
    public char[] vertex;//顶点集合
    public int[][] matrix;//邻接矩阵
    VisitedVertex vv;//算法的辅助类对象
    public DijkstraGraph(char[] vertex,int[][] matrix){
        this.vertex=vertex;
        this.matrix=matrix;
    }
    //展示图
    public void show(){
        for (int i = 0; i <matrix.length ; i++) {
            System.out.println(Arrays.toString(matrix[i]));
        }
    }

    /**
     * 迪杰斯特拉算法-求最短路径
     * @param index 出发顶点
     */
    public void dijkstra(int index){
        vv=new VisitedVertex(index,vertex.length);
        update(index);
        for (int i = 1; i <vertex.length ; i++) {
            int nextVisit = vv.nextVisit();
            update(nextVisit);
        }
        vv.show(vertex);

    }
    /**
     * 访问index点,更新三个数组
     * @param index 当前访问顶点
     */
    public void update(int index){
        for (int i = 0; i <matrix[index].length ; i++) {
            int len=matrix[index][i]+vv.getDis(index);
            if(!vv.in(i)&&len<vv.getDis(i)){
                vv.updateDis(i,len);
                vv.updatePre(i,index);
            }
        }
    }
}

你可能感兴趣的:(java,算法)