本来以为那个SPAF也是一个最短路算法,原来是Bell..什么算法的中国别名,三大最短路,凑齐了,感觉看了好久了,
参考资料:《啊哈算法》《信息学奥赛一本通》
处理问题:
多源最短路(不能处理负权回路);
算法思想:
假设上图中有4个城市8条公路,公路上的数字表示这条公路的长短【公路单向】。现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径。
用一个4*4的矩阵(二维数组e)来存储该图的 信息。比如1号城市到2号城市的路程为2,则设e[1][2]的值为2。2号城市无法到达4号城市,则设置e[2][4]的值为∞。另外此处约定一一个城市自己到自己的路程也是0,例如e[1][1]为0,具体如下:
回到问题上来:怎样求任意两点之间的最短路径?
根据以往的经验,如果要让任意两点(例如从顶点a到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a→k→b,才可能缩短原来从顶点a到顶点b的路程。但这个中转的顶点k是1~n中的哪个点?有时候甚至不只通过一个点,而是经过两个点或者更多点中转会更短,即a→k1→k2→b或者a>k1→k2→ki...→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(i=1;i<=n;i++){
for(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[ i ][ 2 ]+e[ 2 ][ j ]是否比 e[ i ][ j ]要小,代码实现为如下。
//经过1号顶点
for(i=1;i<=n;i++)
for(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(i=1;i<=n;i++)
for(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>
#include<cstdio>
#include<iostream>
#include<map>
#include<vector>
using namespace std;
int main()
{
int e[10][10],k,i,j,n,m,t1,t2,t3;
int inf=99999999;//用inf(infinity的缩写)存储一个我们认为的正无穷值
//读入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=1;i<=m;i++){
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2]=t3;
}
//Floyd-Warshal1算法核心语句
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("\n");
}
return 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][k]<inf&&e[k][j]<inf&&e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];/*
上面代码的输入数据样式为:
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*/
第一行两个数为n和m, n表示顶点个数,m表示边的条数。
接下来m行,每一行有三个数1、口2和13,表示顶点tl到顶点2的路程是t3。得到最终结果如下:
通过这种方法我们可以求出任意两个点之间的最短路径。它的时间复杂度是O(N)。令人很震撼的是它竟然只有五行代码,实现起来非常容易。正是因为它实现起来非常容易,如果时间复杂度要求不高,使用Floyd-Warshall来求指定两点之间的最短路径或者指定一个点到其余各个顶点的最短路径也是可行的。