首先来看一张图
使用邻接矩阵储存形式如下
现在假设我们需要从1号节点走到4号节点最短路径权值是多少呢?我们来模拟一下
0 | 2 | 6 | 4 |
以上表格依次为1号节点到其他节点的路径权值。我们现在想求1号到4号的距离目前看是4.。这是直达的方式,可是我们还可以选择中转来达到4号点,如果选择2号点为中转呢?1到2权值为2,2到4不连通,说明次中转方案不可行。那只能选择3号点了,按照之前的方式计算中转路径权值为7,大于直达方式下的4。说明此时1到4最短路径为直达方式,权值最小为4。
试着计算自己一下1到3号的距离?
答案是5。选择2号节点作为中转。
那么问题又来了,这里每次只选择一个节点作为中转是否每次都可行?当然不。
0 | 1 | 5 | 10 |
inf | 0 | 1 | inf |
inf | inf | 0 | 1 |
inf | inf | inf | 0 |
在这个邻接矩阵中,要求1到4的最短路,直达是10,单单选择2号节点中转无法到达!而选择3号节点则为6。但这就是最短的了吗?显然,选择1->2->3->4的方式最短仅仅为3。也就是说选择一个节点作为中转后,也有可能可以再选一个节点更加合适?那怎么判断是否要再选取一个节点呢?最简单直观的想法便是构成邻接表再BFS暴搜,然而时间复杂度没算错的话是n!(不太会算这个东西),从表中我们不难发现,先计算1到3的最短路可以通过1->2->3路径最小权值为2,接下来就邻接矩阵变成
0 | 1 | 2 | 10 |
inf | 0 | 1 | 1 |
inf | inf | 0 | 1 |
inf | inf | inf | 0 |
这样就可以通过2号节点中转,1->3->4==3
因此Floyd-Warshall算法中选择的中转点并不是简单的为了中转点直接连通到目的节点的路径。而是为了知道它到目的节点的最短路径而推出起始点到终点的最短路径(换句话说,我不是看中你的直接价值,我是看中了你的间接价值),这里构建中转点到目的地的最短路径的过程就是一个动态规划的过程。
#include <stdio.h>
#include <string.h>
#define inf 0x3f3f3f3f
int map[1001][1001];
int main ()
{
int m,n;
while (~scanf ("%d%d",&m,&n)){
int i,j;
for (i = 1;i <= m; i++)//初始化邻接矩阵
for (j = 1 ;j <= m; j++)
if (i == j)
map[i][j] = 0;
else
map[i][j] = inf;
for (i = 1;i <= n; i++){
int a,b,w;
scanf ("%d%d%d",&a,&b,&w);//读入无向图
map[a][b] = w;
map[b][a] = w;
}
int k;
for (k = 1;k <= m; k++)//核心算法
for (i = 1;i <= m; i++)
for (j = 1;j <= m;j++)
if (map[i][j] > map[i][k]+map[k][j])
map[i][j] = map[i][k]+map[k][j];//只有五行
for (i = 1;i <= m; i++)
for (j = 1; j <= m; j++)
printf ("%d%c",map[i][j],j==m?'\n':' ');
}
return 0;
}
对于核心算法的三层循环解释如下:
第一层循环k代表所选择的中转点,接下来一层循环i代表起点,最后一层j为终点。
上一个的例子最后优化的邻接矩阵如下
可以看出1->3最短为2,到4最短为3。1到4选择的路径是1->3->4。所以算法中k从1枚举到n是一个巧妙的设计保证了选择要选择3做中转的时候3到4的路径已经经过优化。不会出现这样的情况:我需要选择2号节点做中转才能到达4号为最短,但是2号到4号最短需要3号来优化。那么在这种情况下k循环到了3,可以把2->3->4的路径进行优化,但是k又不能返回2去优化1->2->4。注意这样的情况不会出现,不会出现!可以自行将每次优化的结果打印(共打印k次)查看变化过程便一目了然。
还有一点就是由于Floyd-Warshall算法求A->C的最短路必须先B->C的最短路来验证。所以这个算法最后一定会算出所有点之间的最短路,这无法避免,因此时间复杂度为n^3。