最短路径之Floyd-Warshall算法

参考《啊哈!算法》p.148

现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储。比如1号城市到2号城市的路程为2,则设e[1][2]的值为2。2号城市无法到达4号城市,则设置e[2][4]的值为∞。另外此处约定一个城市自己到自己的路程也是0,例如e[1][1]为0,具体如下。

现在回到问题:如何求任意两点之间的最短路径呢?通过之前的学习,我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行n^2 遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?

我们来想一“想,根据以往的经验,如果要让任意两点(例如从顶点a到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a→k→b,才可能缩短原来从顶点a到顶点b的路程。那么这个中转的顶点k是1~n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a→kl→k2→b或者a→kl→k2→...kn→b。比如上图中从4号城市到3号城市(4→3) 的路程e[4][3]原本是12,如果只通过1号城市中转(4→1→3),路程将缩短为11 (e[4][1]+e[1][3]=5+6=11)。 其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(e[1][2]+e[2][3]=2+3=5)。所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。通过这个例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。

当任意两点之间不允许经过第三个点时,这些城市之间的最短路程就是初始路程,如下。

假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断e[i][1]+e[1][j]是否比 e[i][j]要小即可。e[i][j]表示的是从i号顶点到j号顶点之间的路程。e[i][1]+e[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。其中i是1~n循环,j也是1~n循环,代码实现如下。

for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) {
        if(e[i][j]>e[i][1]+e[1][j])
            e[i][j]=e[i][1]+e[1][j];
    }
}
 在只允许经过1号顶点的情况下,任意两点之间的最短路程更新为: 

 

通过上图我们发现:在只通过1号顶点中转的情况下,3号顶点到2号顶点(e[3][2])、4号顶点到2号顶点(e[4][2])以及4号顶点到3号顶点(e[4][3]) 的路程都变短了。
接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短路程。如何做呢?我们需要在只允许经过1号顶点时任意两点的最短路程的结果下,再判断如果经过2号顶点是否可以使得i号顶点到j号顶点之间的路程变得更短,即判断e[][2]+e[2][]是 否比e[i][j]要小,代码实现为如下:

//先算出经过1号点
for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) {
        if(e[i][j]>e[i][1]+e[1][j])
            e[i][j]=e[i][1]+e[1][j];
    }
}
//再更新经过2号点是否有更加短的路径
for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= n; ++j) {
        if(e[i][j]>e[i][2]+e[2][j])
            e[i][j]=e[i][2]+e[2][j];
    }
}

 在只允许经过1和2号顶点的情况下,,任意两点之间的最短路程更新为: 通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得e[1][3]和e[4][3]的路程变得更短了。同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:

最后,允许经过所有点的最短路径为:

核心代码如下所示:

for(k=1; k<=n; k++)
        for(i=1; i<=n; i++)
            for(j=1; j<=n; j++)
                if(e[i][j]>e[i][k]+e[k][j])

                    e[i][j]=e[i][k]+e[k][j];

这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转.....允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一-种“动态规划”的思想。下 面给出这个算法的完整代码:

#include<stdio.h>
int main()
{
    int e[10][10],k,i,j,n,m,t1,t2,t3;
    int inf=99999999;//用inf存储一个我们认为的正无穷值
    //读入n和m,n表示顶点个数,m表示边的条数
    scanf("%d%d",&n,&m);
    //初始化
    for(i=1; i<=n; i++)
        for(j=1; j<=n; j++)
        {
            if(i==j)
                e[i][j]=0;
            else
                e[i][j]=inf;
        }
    //读入边
    for(i=0; i<m; i++)
    {
        scanf("%d%d%d",&t1,&t2,&t3);
        e[t1][t2]=t3;
    }
    //Floyd-Warshall算法核心语句
    for(k=1; k<=n; k++)
        for(i=1; i<=n; i++)
            for(j=1; j<=n; j++)
                if(e[i][j]>e[i][k]+e[k][j])

                    e[i][j]=e[i][k]+e[k][j];
    //输出最终结果
    for(i=1; i<=n; i++)
    {
        for(j=1; j<=n; j++)
            printf("%10d",e[i][j]);
        printf("
");
    }
    return 0;
}
/*
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
图
0 2 5 4
9 0 3 4
6 8 0 1
5 7 10 0
*/

有一点需要注意的是:如何表示正无穷。我们通常将正无穷定义为9999999 因为这样即使两个正无穷相加,其和仍然不超过int类型的范围(C语言int类型可以存储的最大正整数是2147483647)。在实际应用中最好估计一下最短路径的上限,只需要设置比它大一点即可。例如有100条边,每条边不超过100的话,只需将正无穷设置为10001即可。如果你认为正无穷和其他值相加得到一个大于正无穷的数是不被允许的话,我们只需在比较的时候加两个判断条件就可以了,请注意下面代码中带有下划线的语句。

 //Floyd-Warshall算法核心语句
    for(k=1; k<=n; k++)
        for(i=1; i<=n; i++)
            for(j=1; j<=n; j++)
                if(e[i][j]>e[i][k]+e[k][j]&&e[i][k]<inf&&e[k][j]<inf)

                    e[i][j]=e[i][k]+e[k][j];

 第一行两个数为n和m, n表示顶点个数,m表示边的条数。

接下来m行,每一行有三个数t1、t2和t3,表示顶点t1到顶点t2的路程是t3。得到最终结果如下:

通过这种方法我们可以求出任意两个点之间的最短路径。它的时间复杂度是0(N^3)。令人很震撼的是它竟然只有五行代码,实现起来非常容易。正是因为它实现起来非常容易,如果时间复杂度要求不高,使用Floyd-Warshall 来求指定两点之间的最短路径或者指定一一个点到其余各个顶点的最短路径也是可行的。当然也有更快的算法。另外需要注意的是,Floyd-Warshall 算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路径。例如下面这个图就不存在1号顶点到3号顶点的最短路径,因为1→2>3→1>2>3.1→2→3这样路径中,每绕- -次 1→2>3这样的环,最短路径就会减少1,永远找不到最短路径。其实如果一个图中带有 “负权回路”,那么这个图则没有最短路径。

加油啦!加油鸭,冲鸭!!!
原文地址:https://www.cnblogs.com/clarencezzh/p/10382158.html