浅谈 最短路径O(n^3)万(蒟)能(蒻)算法——————Floyd
《最短路径·O(n^3)Floyd篇》
暑假,小哼准备去一些城市旅游。有些城市之间有公路,有些城市之间则没有,如下图。为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程。
上图是公路,有8条公路,且都是单向的。(不要在意为什么往返距离居然不相等 )题目要求,求某个城市到某个城市的最短距离???这个问题这也被称为“多源最短路径”问题。
我们首先用个邻接矩阵来存一下每个位置的关系和距离。
ASK:
现在回到问题:如何求任意两点之间最短路径呢?通过之前的学习我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行O(n^2)遍深度或广度优先搜索,即对每两个点都进行一次DFS或BFS,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?
ANSWER:
我们来想一想,根据我们以往的经验,如果要让任意两点(例如从顶点a点到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a->k->b,才可能缩短原来从顶点a点到顶点b的路程。那么这个中转的顶点k是1n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2b->或者a->k1->k2…->k->i…->b。比如上图中从4号城市到3号城市(4->3)的路程a[4][3]原本是12。如果只通过1号城市中转(4->1->3),路程将缩短为11(a[4][1]+a[1][3]=5+6=11)。神器哟!~ 其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(a[1][2]+a[2][3]=2+3=5)。所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。
有没有感觉发现新大陆的感觉,是不是很像喊一声巨佬<<—怎么可能,先讲怎么实践吧。
当任意两点之间不允许经过第三个点时,这些城市之间最短路程就是初始路程,如下。
假如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断a[i][1]+e[1][j]是否比a[i][j]要小即可。a[i][j]表示的是从i号顶点到j号顶点之间的路程。a[i][1]+a[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。其中i是1n循环,j也是1n循环,代码实现如下。
附上C++ 代码
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
if (a[i][j] > a[i][1] + a[1][j])
{
a[i][j] = a[i][1] + a[1][j];
}
}
}
不是说好O(N^3),哎,兄弟请你看清点,是a[i][1]+a[1][j],题目限制,只能经过1点,所以别着急先,慢慢来。
附上Pascal 代码
for i:=1 to n do
begin
for j:=1 to n do
begin
if a[i,j]>a[i,1]+a[1,j] then
begin
a[i,j]:=a[i,1]+a[1,j];
end;
end;
end;
在只允许经过1号顶点的情况下,任意两点之间的最短路程更新为:
综上所述,我们知道了有时候借助一个点可以缩短距离,我们再看一下,如果可以借助1,2号点,则
通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得a[1][3]和a[4][3]的路程变得更短了。
同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:
WOW!出来了,这么说找中间点还得需要一个循环。但是,顺序该怎么打呢?
最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:
最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
WOW!超短哟!这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
很多人看了别人的BLOG,发现都没解释为什么k要在外面,其实不是别人不会,实际上是太简单了自己理解吧没这个必要。但我还是讲一讲吧,毕竟Floyd,我看别人的BLOG看了一个星期才弄懂。
先理解每个循环变量的意义:
k: 中间的过渡点
i: 前面的点
j: 后面的点
分析:
我们是从前往后枚举,然后每次就试一个过渡点即可,所以k写最外层,i是前面的,j是后面的,得出最后的结果。k->i->j
另外需要注意的是:Floyd算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。其实如果一个图中带有“负权回路”那么这个图则没有最短路。