图的最短路径算法


  

引言:

图的最短路径问题是求图中两顶点间的最短路径问题。

  在网图和非网图中,最短路径的含义不同。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;而对于网图来说,最短路径是指两顶点之间经过的边上权值之和最少的路径,并且将路径上的第一个顶点称为源点,最后一个顶点称为终点。其实非网图完全可以理解成所有边的权值都为1的网。

迪杰斯特拉(Dijkstra)算法

  这是一个按路径长度递增的次序产生最短路径的算法。并不是一下子求出两顶点间的最短路径,而是一步步求出它们之间顶点的最短路径,过程中都是基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到想要的结果。

//图数据结构(邻接矩阵的方式)
class Graph {
    final int INFINITY = Integer.MAX_VALUE;//很大很大的数
    int vexNum = 9;//顶点数目
    //邻接矩阵
    int[][] adj = {{0, 1, 5, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY},
            {1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY, INFINITY},
            {5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY},
            {INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY},
            {INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY},
            {INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY},
            {INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7},
            {INFINITY, INFINITY, INFINITY, INFINITY, 9, 5, 2, 0, 4},
            {INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 7, 4, 0}};
}

public class GrapTest {
    @Test
    public void testDijkstra() {
        Graph g = new Graph();
        int from = 0;//起点
        List<int[]> ans = new Dijkstra().shortestPath(g, from);
        int[] shortDis = ans.get(0);
        int[] path = ans.get(1);
        new Dijkstra().showPath(g, from, shortDis, path);
    }
}

//迪杰斯特拉(Dijkstra)算法,计算起点到其他点的最短路径 O(n^2)
class Dijkstra{
    public List<int[]> shortestPath(Graph g, int from) {
        int[] D = new int[g.vexNum];   //D[w]表示顶点到from的最短路径
        int[] P = new int[g.vexNum];   //P[w]表示最后到达w经过的顶点,即最后一步为P[w]-->w
        int[] find = new int[g.vexNum];//find[w]=1表示求得顶点from至w的最短路径
        //初始化数据
        for (int v = 0; v < g.vexNum; v++) {
            D[v] = g.adj[from][v];
            P[v] = from;
        }
        D[from] = 0;
        find[from] = 1;

        //开始主循环,每次求得from到某个顶点v的最短路径
        int k=0;
        long min;
        for (int v = 0; v < g.vexNum; v++) {
            min = Integer.MAX_VALUE;
            for (int w = 0; w < g.vexNum; w++) {
                if (find[w] == 0 && D[w] < min) {
                    min = D[w];
                    k = w;
                }
            }
            find[k] = 1;//表示已求得from到k的最短路径
            /*当顶点from到k的目前最短距离找到后,k到w若还有距离,就得考虑是否要修改D[w]。因为from到k的距离加上k到w的距离可能小于当前的D[w]*/
            for (int w = 0; w < g.vexNum; w++) {
                if (find[w] == 0 && (min + g.adj[k][w]) < D[w]) {
                    D[w] = (int)min + g.adj[k][w];
                    P[w] = k;//k到w若还有距离,就得考虑是否要修改D[w]。因为from到k的距离加上k到w的距离可能小于当前的D[w]
                }
            }
        }
         return Arrays.asList(D, P);
    }

    public void showPath(Graph g, int from, int[] D, int[] P) {
        //打印shortDis数组
        Arrays.stream(D).forEach(each -> System.out.print(each + " "));
        System.out.println();
        //打印从from顶点到各顶点最短距离的路径
        for (int i = 0; i < g.vexNum; i++) {
            int a = i;
            System.out.print(a + " <-- ");
            while ((a = P[a]) != from) {
                System.out.print(a+" <-- ");
            }
            System.out.println(from);
        }
    }
}
0 1 4 7 5 8 10 12 16
0 <-- 0
1 <-- 0
2 <-- 1 <-- 0
3 <-- 4 <-- 2 <-- 1 <-- 0
4 <-- 2 <-- 1 <-- 0
5 <-- 4 <-- 2 <-- 1 <-- 0
6 <-- 3 <-- 4 <-- 2 <-- 1 <-- 0
7 <-- 6 <-- 3 <-- 4 <-- 2 <-- 1 <-- 0
8 <-- 7 <-- 6 <-- 3 <-- 4 <-- 2 <-- 1 <-- 0

  通过迪杰斯特拉(Dijkstra)算法,可以解决从某个顶点到其余各顶点的最短路径问题。从循环嵌套可以很容易得到此算法的时间复杂度为 O(n2)。能不能只找到从源点到某一个特定终点的最短路径呢?其实这个问题和求解源点到其他所有顶点的最短路径一样复杂,时间复杂度依然是 O(n2)。
  如果需要知道V3到V5、V1到V7这样的任一顶点到其余所有顶点的最短路径怎么办呢?此时简单的办法就是对每个顶点当做源点运行一次迪杰斯特拉(Dijktra)算法,等于在原有算法的基础上,再来一个循环,此时整个算法的时间复杂度就成了 O(n3)。

弗洛伊德(Floyd)算法

  该算法求得所有顶点到所有顶点的最短路径的时间复杂度也是 O(n3),但其算法非常简洁优雅。

//图数据结构(邻接矩阵的方式)
class Graph {
    final int INFINITY = Integer.MAX_VALUE;//很大很大的数
    int vexNum = 9;//顶点数目
    //邻接矩阵
    int[][] adj = {{0, 1, 5, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY},
            {1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY, INFINITY},
            {5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY},
            {INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY},
            {INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY},
            {INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY},
            {INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7},
            {INFINITY, INFINITY, INFINITY, INFINITY, 9, 5, 2, 0, 4},
            {INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 7, 4, 0}};
}

public class GrapTest {
    @Test
    public void testFloyd(){
        Graph g = new Graph();
        List<int[][]> ans = new Floyd().shortestPath(g);
        int[][] shortDis = ans.get(0);
        int[][] path = ans.get(1);
        new Floyd().showPath(g, shortDis, path);
    }
}

//弗洛伊德(Floyd)算法,计算任意两顶点间的最短路径 O(n^3)
class Floyd{
    public List<int[][]> shortestPath(Graph g) {
        int[][] D = new int[g.vexNum][g.vexNum];//顶点到顶点的最短路径长度
        int[][] P = new int[g.vexNum][g.vexNum];//对应顶点的最小路径的前驱矩阵
        int v, w;
        for (v = 0; v < g.vexNum; v++) {/*初始化D与P*/
            for (w = 0; w < g.vexNum; w++) {
                D[v][w] = g.adj[v][w];
                P[v][w] = w;
            }
        }
        int k;
        for (k = 0; k < g.vexNum; k++) {
            for (v = 0; v < g.vexNum; v++) {
                for (w = 0; w < g.vexNum; w++) {
                    if (D[v][k] == g.INFINITY || D[k][w] == g.INFINITY) continue;
                    if (D[v][w] > D[v][k] + D[k][w]) {
                        //如果经过下标为k顶点路径比原两顶点路径更短,将当前两点间权值设为更小的一个
                        D[v][w] = D[v][k] + D[k][w];
                        P[v][w] = P[v][k];/*路径设置为经过下标k的顶点*/
                    }
                }
            }
        }
        return Arrays.asList(D, P);
    }

    public void showPath(Graph g, int[][] D, int[][] P) {
        int v, w, k;
        for (v = 0; v < g.vexNum; ++v) {
            for (w = v + 1; w < g.vexNum; ++w) {
                System.out.print("[" + v + "]" + "--" + D[v][w] + "-->[" + w + "]: ");
                k = P[v][w];
                System.out.print(v + " -> ");
                while (k != w) {
                    System.out.print(k + " -> ");
                    k = P[k][w];
                }
                System.out.println(w);
            }
        }
    }
}
[0]--1-->[1]: 0 -> 1
[0]--4-->[2]: 0 -> 1 -> 2
[0]--7-->[3]: 0 -> 1 -> 2 -> 4 -> 3
[0]--5-->[4]: 0 -> 1 -> 2 -> 4
[0]--8-->[5]: 0 -> 1 -> 2 -> 4 -> 5
[0]--10-->[6]: 0 -> 1 -> 2 -> 4 -> 3 -> 6
[0]--12-->[7]: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7
[0]--16-->[8]: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8
[1]--3-->[2]: 1 -> 2
[1]--6-->[3]: 1 -> 2 -> 4 -> 3
[1]--4-->[4]: 1 -> 2 -> 4
[1]--7-->[5]: 1 -> 2 -> 4 -> 5
[1]--9-->[6]: 1 -> 2 -> 4 -> 3 -> 6
[1]--11-->[7]: 1 -> 2 -> 4 -> 3 -> 6 -> 7
[1]--15-->[8]: 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8
[2]--3-->[3]: 2 -> 4 -> 3
[2]--1-->[4]: 2 -> 4
[2]--4-->[5]: 2 -> 4 -> 5
[2]--6-->[6]: 2 -> 4 -> 3 -> 6
[2]--8-->[7]: 2 -> 4 -> 3 -> 6 -> 7
[2]--12-->[8]: 2 -> 4 -> 3 -> 6 -> 7 -> 8
[3]--2-->[4]: 3 -> 4
[3]--5-->[5]: 3 -> 4 -> 5
[3]--3-->[6]: 3 -> 6
[3]--5-->[7]: 3 -> 6 -> 7
[3]--9-->[8]: 3 -> 6 -> 7 -> 8
[4]--3-->[5]: 4 -> 5
[4]--5-->[6]: 4 -> 3 -> 6
[4]--7-->[7]: 4 -> 3 -> 6 -> 7
[4]--11-->[8]: 4 -> 3 -> 6 -> 7 -> 8
[5]--7-->[6]: 5 -> 7 -> 6
[5]--5-->[7]: 5 -> 7
[5]--9-->[8]: 5 -> 7 -> 8
[6]--2-->[7]: 6 -> 7
[6]--6-->[8]: 6 -> 7 -> 8
[7]--4-->[8]: 7 -> 8

  弗洛伊德(Floyd)算法的代码非常简洁,是一个二重循环初始化加一个三重循环权值修正,其完成了所有顶点到所有顶点的最短路径计算。但是很可惜,它的时间复杂度是 O(n3)。


文章作者: YangChongZhi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangChongZhi !
评论
 上一篇
vim用户手册 vim用户手册
   引言: Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。简单的来说, vi 是老式的字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 vim 则可以说是程序
2021-01-24
下一篇 
连通图的最小生成树算法 连通图的最小生成树算法
   引言: 在线性表中,数据元素之间是被串起来的,仅有线性关系,每个数据元素只有一个直接前驱和一个直接后继。在树形结构中,数据元素之间有着明显的层次关系,并且每一层上的数据元素可能和下一层中多个元素相关,但只能和上一层中一个元素想关。这和
2021-01-21
  目录