算法(五)最短路径之Floyed-Warshall算法

前言

算法中的最短路径问题,是一个经典的算法问题。旨在寻找图(由顶点和边组成)中顶点到顶点间的最短路径。而我们接下来的几篇文章就会一起学习最短路径问题中常用的Floyed-Warshall、Dijkstra、Bellman-Ford以及Bellman-Ford的队列优化这四种算法。这些最短路径的算法在实际运用中也有不可忽视的作用。接下来,我们就来学习一下Floyed-Warshall算法。


具体问题

在前面几篇文章中,我们好不容易从迷宫中利用广度优先搜索和深度优先搜索找到了女朋友。准备带她出门旅游一趟,放松一下,毕竟困在迷宫中的她也吓坏了。比如,你们计划去 A、B、C、D四个城市,两个城市之间的路途是不同的,如下图所示

算法(五)最短路径之Floyed-Warshall算法_第1张图片

    上图中的A、B、C、D表示四座城市,其中从A到C的箭头表示,A城市到C城市的距离是6,而C到A的箭头表示,C城市到A城市的距离是7。如果两个城市之间只有一条线吗,就表示是单边的路,并没有回去的路线。现在我们需要知道任意两个城市之间最短的路程,比如A到C的路程有以下几条。

    1、A直接到C  路程是6
    2、A到B再到C  路程是 2 + 3 = 5
    3、A到D再到C  路程是 4 + 12 = 16
   所以A到C的最短路径就是A—>B—>C 一共5
   现在我们需要知道任意两点之间的最短路径(多源最短路径),我们应该怎么做呢?

Floyd-Warshall算法

    问题数据化
现在,我们需要知道任意两点直接的最短路径,其实也叫“多源最短路径”,我们可以怎么样去做呢?首先我们需要将地图数据化,其实我
们可以用一个二维数组来表示上图的各个点和他们之间的距离关系。
算法(五)最短路径之Floyed-Warshall算法_第2张图片

   这个图的意思是:

    A到A,标注为0,就是这个城市到自己为0

    B到A标注为无穷的符号,意思就是B到A没有之间的道路,可能需要通过其他的城市中转,

    A到B,标注为2,意思是从A之间到B的路径长度是2。其他的同理

    这样一来,我们就通过一个简单的图表,就把问题具体化了,我们就可以通过一个二维数组来表示任意两点之间的路径。如果我们有需要改变的项,之间更新这个二维数组就可以了。最后我们通过这个二维数组就可以轻松的得出任意两点之间的最短路径。

    思考过程

上面的数组是最初的状态,并没有借助其他的城市作为中转。那如何计算允许借助所有城市进行中转的任意两个城市的最短路径呢。首先

,我们考虑如果只允许借助A城市进行中转的话,我们应该怎么样写代码呢?(在数组中,我们用4表示城市D,用3表示城市C,2表示城市B

,1表示 城市A,paths[A][A] = 0表示A城市到A城市距离是0,paths[B][A] = 无穷(我们可以用99999表示无穷)

比如,我们要计算D到C城市的最短路径就是

    paths[4][3] = 12;


如果只允许通过A城市中转,那么用A进行中转的结果就是

   paths[4][1]+ paths[1][3] = 5 + 6;

这样的话,其实我们就可以编写如下代码:

   if (paths[4][3] > paths[4][1]+paths[1][3]){
         paths[4][3] = paths[4][1]+paths[1][3];
   }

那么如果要计算任意两点,在只允许通过A城市中转的情况下的,最短路径其实就可以写出下面的代码:

   for (int i = 1; i <= n; i++) {
          for (int j = 1; j <= n; j++) {
                if (paths[i][j] > paths[i][1]+paths[1][j]){
                    paths[i][j] = paths[i][1]+paths[1][j];
                }
          }
   }

    上面代码就将所有的点运行通过A城市中转的情况,进行了一次遍历更新。那么如果也允许通过B 城市中转的情况,代码如下:
    for (int i = 1; i <= n; i++) {
          for (int j = 1; j <= n; j++) {
                if (paths[i][j] > paths[i][2]+paths[2][j]){
                    paths[i][j] = paths[i][2]+paths[2][j];
                }
          }
    }
那么根据上面的代码,我们可以得出,如果允许通过任意城市中转的代码如下:
      for (int k = 1; k <= n; k++) {
      	    for (int i = 1; i <= n; i++) {
          	for (int j = 1; j <= n; j++) {
               	     if (paths[i][j] > paths[i][k]+paths[k][j]){
                          paths[i][j] = paths[i][k]+paths[k][j];
                     }
           	}
      	    }
	}

上面的代码,其实就是floyd算法,通过他可以计算出任意两个城市间的最短距离。

这时候可能有人就会有疑问,不对啊,你的核心两层for循环,计算的只是借助某一个城市进行中转的情况,但是如果是D到C,有下面三
种情况:
    1、D到C,12
    2、D到A到C,5 + 6 = 11
    3、D到A到B到C,5 +2 +3 = 10
     好像并没有考虑到借助多点进行中转的情况,然而,真的是这样吗?真的我们没有考虑到吗?首先,让我们用事实说话,我们运行一下代
码。
    初始化以及调用代码
    public void shortestPath(){
        //初始化城市地图
        int[][] paths = new int[5][5];//因为我们后面为了好理解,是从paths[1]开始,paths[4]结束的,所以这里初始化为5*5的数组
        /**0表示它自己到他自己,999999这里用来表示无穷远,意思就是没有之间的线路相通*/
        paths[1][1] = 0; paths[1][2] = 2;paths[1][3] = 6;paths[1][4] = 4;
        paths[2][1] = 999999;paths[2][2] = 0;paths[2][3] = 3;paths[2][4] = 999999;
        paths[3][1] = 7; paths[3][2] = 999999; paths[3][3] = 0;paths[3][4] = 1;
        paths[4][1] = 5; paths[4][2] = 999999; paths[4][3] = 12;paths[4][4] = 0;
        floydWarshall(paths,4);
    }
floyd算法核心代码
     /**
     * 最短路径
     * Floyd-Warshall算法
     * 计算四个城市中,任意两个城市间的最短距离
     *
     * @param paths 传递过来的是,城市和路径的二维数组
     * paths[1][3] = a,表示的就是,1号城市到3号城市的最短路径是a
     * @param n 表示的是城市的数量
     * */
     public void floydWarshall(int[][] paths,int n){
        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (paths[i][j] > paths[i][k]+paths[k][j]){
                        paths[i][j] = paths[i][k]+paths[k][j];
                    }
                }
            }
        }
        //我们排列完成了,现在来打印一下这个数组
        StringBuilder buil = new StringBuilder();
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                buil.append("  "+paths[j][i]);
            }
            buil.append("\n");
        }
        Log.i("hero","---排列结果===\n"+buil.toString());
    }
运行结果
算法(五)最短路径之Floyed-Warshall算法_第3张图片

从结果上看,其实已经吧通过多个城市中转的情况也考虑进行了,其实是因为这个路径数组一直是动态变化的,什么意思呢?
最初的数组 表示的是,两个城市之间到之间到达的的路径的长度,这时候D到C是12,而D到B的是99999。
       第一次最外层循环结束之后,数组就表示,考虑通过A中转情况下,任意两点间的最短距离,这时候是D到C的距离更新成了D到A到C, 为11,而D到B的是,D到A到B,将99999更新成了,5 + 2 = 7。

第二次最外层循环结束的时候,数组表示,允许通过B中转情况下,虽然最初的时候,D到B没有之间的路联通,但是第一次循环结束的时

候,已经借助A点进行了联通,现在的D到B,其实距离就是D到A再到B = 7,所以现在D到B到C的距离就是7 + 3 = 10。
那么如果第一次循环D到B也没有联通呢?,下面的循环就会继续尝试去联通他们,直到所有的点都尝试一遍之后,如果还是没法联通,

说明他们两个怎么样也不能到达。

     小结

上面就是floyd算法,通过这种算法,我们可以求出任意两点间的最短路径,它的时间复杂度是O(N * N * N),他的写法是如此的简单,

简单到让人难以置信,所以在对时间要求不高的情况下,我们完全可以使用这种算法,当然,该算法不能解决带有“负权回路”的问题,如图
算法(五)最短路径之Floyed-Warshall算法_第4张图片

如果出现了这样的回路,每次A—》B—》C,路径就会2 + 3 + (-6) = -1,这样就会永远也找不到最短路径,因为每次经过这个回路,

路径都会-1。

总结

上面我们就深入学习了最短路径的第一种算法Floyd-Warshall算法。它的核心代码相当简单,而且原理也非常容易理解。它是求任意两

点间的最短路径的非常容易学习的一种算法。那么如果我们要求解某一点到其他各点的最短路径,我们应该怎么做呢?如果用floyd算法其实也

是可以达到目的,只需要把结果中所有源点出发的列举出来就行,但是下一篇文章,我们会去学习求解单源最短路径的一种算法Dijkstra算法。

因个人水平有限,难免有错误和不准确之处,请大家指正批评。

算法的学习永不止境,我们才刚刚开始而已,继续加油哟。


你可能感兴趣的:(算法和数据结构)