前言
我们之前介绍过 D i j k s t r a Dijkstra Dijkstra和 B e l l m a n − F o r d / S P F A Bellman-Ford/SPFA Bellman−Ford/SPFA算法,这些算法解决的都是单源最短路的问题,那么有没有一个算法,可以计算出任意两点之间的最短路呢?答案是– F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall.
原理
和所有最短路算法一样, F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall算法也需要借助边的"松弛"操作来缩短最短路径,不同的是,对于每一条边, F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall试图用剩余的所有顶点作为中间点去松弛,这样得到的就会是任意两点间的最短路径.关于这个算法的正确性,简单说明一下:
- 假设两个点之间没有边,通过中间点建立一条边,显然这两点的最短路缩短了;
- 假设两个点有边,通过若干个中间点缩短了最短路径,则设该图含有 V V V个顶点,可得两点之间最多通过 V − 2 V-2 V−2个中间点缩短最短路径,此时路径包含 V − 1 V-1 V−1条边,显然在不包含负环的图中,最短路不可能包括比这更多的边数,因此最多通过 V − 2 V-2 V−2个点松弛;
- 在图中若存在负环,则2不成立,但此时也会不存在最短路径,因此在有解的情况下1,2始终正确.
F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall的执行过程如下(来源:啊哈算法):
![Floyd-Warshall](https://i-blog.csdnimg.cn/blog_migrate/75272968f33bfbdb218bfe5d8b77e82b.png)
实现
F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall算法通过邻接矩阵可以简单地实现:
#include<iostream>
#define maxn 10000
#define inf 0x3f3f3f3f
using namespace std;
int e[maxn][maxn];
void init(int n) {
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
if(i == j)
e[i][j] = 0;
else
e[i][j] = inf;
}
}
}
void Floyd_Warshall(int n) {
for(int k = 0; k <= n; k++) {
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
e[i][j] = min(e[i][j],e[i][k]+e[k][j]);
}
}
}
}
int main() {
int n,m,u,v,w;
while(cin>>n>>m) {
init(n);
for(int i = 0; i < m; i++) {
cin>>u>>v>>w;
e[u][v] = w;
// e[v][u] = w; //无向图;
}
Floyd_Warshall(n);
for(int i = 0; i <= n; i++) { //输出最短路;
for(int j = 0; j <=n ;j++) {
cout<<e[i][j]<<" ";
}
cout<<endl;
}
}
return 0;
}
因为要方便查询到任意两点之间的所有边,所以 F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall算法最理想的实现方式就是邻接矩阵,如果要用邻接表实现,需要有两个邻接表,一个是入边表,一个是出边表,并且要维护每一个公共中间点的入边和出边的关系,这样的写法不但复杂,而且没有意义,时间和空间都没有太大的提升,因此只介绍邻接矩阵实现.
显然, F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall的时间复杂度是 O ( V 3 ) O(V^3) O(V3),这是一个很高的时间复杂度,但是考虑到求得任意两点之间的最短路径,这个时间复杂度是可以接受的.