算法 - 最短路径(一)- Floyd

  • 核心代码
  • 算法过程详解
  • 基本思想
  • 需要注意


核心代码

floyd的核心代码极度简单,时间复杂度为O(n3),代码实现部分只有五行:

for(k=0;k<=n;k++)							//遍历可经过的中点k
        for(i=0;i<=n;i++)						//遍历起点i
            for(j=0;j<=n;j++)					//遍历终点j
                if(e[i][j]>e[i][k]+e[k][j])		//判断是否是最短路径
                     e[i][j]=e[i][k]+e[k][j];	//从i到j经过k的最短路径

算法过程详解

假设存在图如下图所示:

python 最短路径 库 最短路径代码_python 最短路径 库

我们需要找出任意一点到另一点的最短路径

首先需要用一个数据结构来存储图的信息,可以用4x4的矩阵来存储。比如节点1到节点2直接的路程为2,则设d[1][2]=2;2号节点不能直接到4号节点,则设d[2][4]=99999;另外,1号节点到自己的路程为0,所以d[1][1]=0。则矩阵如下图所示:

python 最短路径 库 最短路径代码_python 最短路径 库_02

回到最短路径的问题:若要缩短点i到点j之间的距离,必须要引入第三个点(点k),并通过这个顶点进行中转:i -> k -> j。这样才能缩短原来从点i到点j之间的距离。即:d[i][k] + d[k][j] < d[i][j]。那么问题来了,这个点k应该怎么找到呢?

对于Floyd算法,找法是简单粗暴的:简单来说就是逐个点进行遍历。

我们首先考虑,但任意两点之间不能经过第三个点中转时,点与点之间的最短路径就是上图矩阵中的值。

现在加一个条件:若只允许经过1号节点,求任意两点之间的最短路程,应该如何求呢?只需要判断d[i][1] + d[1][j]的值是否比d[i][j]的值要小即可。其中i是1~n层循环,j也是1~n层循环。代码实现如下:

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

在只允许经过1号节点的情况下,任意两点之间的最短路径更新为:

python 最短路径 库 最短路径代码_算法_03

通过上图,我们可以发现:在只通过1号节点中转的情况下,有三对节点之间的最短路径都变短了,换句话说在遍历完k = 1之后,最短路径表更新为了如上图所示的当前状态。

接下来继续求在允许经过1号节点中转的情况下(在基于上表的情况下),若还允许经过2号节点中转,任意两点之间的最短路径。实际上当我们得到上表后,我们需要做的事只是重复上一步,再将1改为2而已。代码实现如下:

for (i = 1; i < n; i++)
	for (j = 1; j < n; j++)
		if( d[i][j] > d[i][2] + d[2][j] )
			d[i][j] = d[i][2] + d[2][j];

将两步合并起来写,就是这样:

for (i = 1; i < n; i++)
	for (j = 1; j < n; j++)
		if( d[i][j] > d[i][1] + d[1][j] )
			d[i][j] = d[i][1] + d[1][j];
for (i = 1; i < n; i++)
	for (j = 1; j < n; j++)
		if( d[i][j] > d[i][2] + d[2][j] )
			d[i][j] = d[i][2] + d[2][j]

为了让代码看上去更简洁,我们可以写成这样(只允许经过1、2两个节点的最短路径):

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

怎么样?是不是看到了熟悉的5行代码?Floyd的思想就是这样:不断增加允许中转的中间点,并在此过程中更新最短路径表。

同样继续下去:在只允许经过1和2号节点的情况下,任意两点之间的最短路径更新为:

python 最短路径 库 最短路径代码_c语言_04

同理,继续增加中转点3,在只允许1、2和3号节点进行中转的情况下,求任意两点之间的最短路程,在执行完后最短路径表更新为:

python 最短路径 库 最短路径代码_python 最短路径 库_05

最后允许所有顶点作中转,任意两点之间最终的最短路程为:

python 最短路径 库 最短路径代码_最短路径_06

整个算法过程虽然说起来麻烦,但核心代码实现只有5行:

就是文章开头那5行

基本思想

整个代码的基本思想就是:最开始只允许经过1号顶点中转,接下来只允许经过1号和2号节点进行中转…最后允许经过1~n号所有节点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号节点的最短路程。也就是我在前文中提到的不断增加允许中转的中间点,并在此过程中更新最短路径表。这就是一种“动态规划”的思想。

需要注意

需要注意的一点是:我们应该如何表示两个点之间无穷远。在上文中我用的是99999来表示正无穷,其实我们通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不超过int类型的范围(C语言int类型可以存储的最大正整数是2147483647)。在实际应用中最好估计一下最短路径的上限,只需要设置比它大一点既可以。例如有100条边,每条边不超过100的话,只需将正无穷设置为10001即可。

另外一点需要注意的是,Floyd算法不能解决带有“负权回路”的图,因为带有“负权回路”的图没有最短路。例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。其实如果一个图中带有“负权回路”那么这个图则没有最短路。

python 最短路径 库 最短路径代码_c语言_07