1. Floyd算法
问题:对于一个各边权值均大于零的有向图,对每一对顶点i≠j,求出顶点i与顶点j之间的最短路径和最短路径长度。
对于这个问题,我们简单分析可以以每个顶点作为源点循环求出每对顶点之间的最短路径,采用Dijkstra算法,循环n次也可以完成,但是这样的话,无疑对Dijkstra算法复杂度增加到 了,所以说Dijkstra算法是比较适合求解稀疏图的,而对于稠密图来说则不太适合,时间复杂度过高。因此我们可以采用另一种弗洛伊德(Floyd)算法,时间复杂度依然是 。
图1-有向带权图
对于有向带权图G=(V,E)来说,还是采用邻接矩阵存储。同时设置一个二维数组A用于存放当前顶点之间的最短路径长度,分量A[i][j]表示当前顶点 i 到顶点 j 的最短路径长度。
弗洛伊德算法思想:递推产生一个矩阵序列A0,A1,…,Ak,…,An-1,其中Ak[i][j]表示从顶点i到顶点j的路径上所经过的顶点编号不大于k的最短路径长度。采用迭代的方法,已考虑 0、1、……k这k+1个顶点的基础上,再考虑顶点k+1,以此得到Ak+1。
图2-最短路径
也就是说原先顶点i到顶点j只有一条路径,在加入k+1顶点后,于是有路径1和路径2了,那么我们就要通过min { 路径1,路径2 },取这两条路径中较小的路径(这是Floyd算法最为核心的部分)。
2. Floyd算法执行过程
path数组中存放着顶点i到顶点j要经过的顶点k。
图3
在考虑顶点0时,经过顶点0的有以下几条路径:
路径1:2 -> 0 - 1 ,权值为3 + 5 =8
路径2:2 -> 0 -> 3 ,权值为 3 + 7 = 10
路径3:3 -> 2 -> 0 -> 1 ,权值为1 + 3 + 5 = 9
其实对于路径1来说,还有一条更短的路径从顶点2到达顶点1,即路径4:2 -> 1 。
通过 A0[i][j] = MIN { A-1[i][j] , A-1[i][0]+A-1[0][j] } 计算发现,min(路径4,路径1)求出的最短路径还是路径4,即2 -> 1 。也就是说对应的在邻接矩阵中存储的顶点2到顶点1的权值还是3,不需要改变。
对于路径2来说,同样还有一条更短的路径从顶点2到达顶点3,即路径5:2 -> 3。
min(路径5,路径2)求出的最短路径还是路径5,即2 -> 3,也就是说对应的在邻接矩阵中存储的顶点2到顶点3的权值还是2,不需要改变。
同理,对于路径3来说,同样也有一条最短路径从顶点3到达顶点1,即3 -> 2 -> 1。
图4
在考虑顶点1后,经过顶点1的路径有以下几条:
路径1:0 -> 1 -> 2 ,权值为: 5 + 4 = 9
路径2:2 -> 1 -> 3 ,权值为 3 + 2 = 5
对于路径1来说,在考虑顶点1之前,顶点0没有直接到顶点2的边,因此距离是无穷大的,在考虑顶点1之后,发现还有一条更短的路径从顶点0到达顶点2,即路径3:0 -> 1 -> 2。
通过min求出的最短路径也是路径1,因此对应的在邻接矩阵中存储的顶点0到顶点2的权值修改为9。同时在path数组中path[0][2]修改为1,表示顶点0到顶点2要经过顶点1。
对于路径2来说,这条路径的距离比原先的路径的距离要大,所以不需要修改。
在考虑顶点2和顶点3的时候都是同样的道理,这里就不再做详细介绍了,通过前面顶点0和顶点1的步骤来说,其实对于Floyd算法的关键部分,即每个顶点作为源点,循环求出每对顶点的最短路径
的介绍已经完成了。
3. Floyd算法实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXV 4
#define INF 99
//图的定义:邻接矩阵
typedef struct MGRAPH{
int n; //顶点数
int e; //边数
int edges[MAXV][MAXV]; //邻接矩阵
} MGraph;
void Ppath(int path[][MAXV],int i,int j)
{
int k;
k=path[i][j];
if (k==-1) return;
Ppath(path,i,k);
printf("%d,",k);
Ppath(path,k,j);
}
void Dispath(int A[][MAXV],int path[][MAXV],int n)
{
int i,j;
for (i=0; i<n; i++)
for (j=0; j<n; j++)
{
if (A[i][j]==INF)
{
if (i!=j)
printf("从%d到%d没有路径\n",i,j);
}
else
{
printf(" 从%d到%d=>路径长度:%d 路径:",i,j,A[i][j]);
printf("%d,",i); //输出路径上的起点
Ppath(path,i,j); //输出路径上的中间点
printf("%d\n\n",j); //输出路径上的终点
}
}
}
//Floyd算法
void Floyd(MGraph g)
{
//定义辅助存储并赋初值
int A[MAXV][MAXV],path[MAXV][MAXV];
int i,j,k;
for (i=0; i<g.n; i++)
for (j=0; j<g.n; j++)
{
A[i][j]=g.edges[i][j];
path[i][j]=-1;
}
//每个顶点作为源点循环求出每对顶点之间的最短路径
for (k=0; k<g.n; k++)
{
for (i=0; i<g.n; i++)
for (j=0; j<g.n; j++)
//选取最短路径
if (A[i][j]>A[i][k]+A[k][j])
{
//记录最短路径
A[i][j]=A[i][k]+A[k][j];
//记录顶点i到顶点j要经过的顶点k
path[i][j]=k;
}
}
//输出最短路径
printf("输出每对顶点的最短路径\n\n");
Dispath(A,path,g.n);
}
int main(void)
{
int A[MAXV][MAXV] = {
{0,5,INF,7},
{INF,0,4,2},
{3,3,0,2},
{INF,INF,1,0}
};
int path[MAXV][MAXV] = {0};
int i;
int j;
MGraph g;
g.n = 4;
g.e = 8;
for(i = 0; i < g.n; i++)
{
for(j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
//求最短路径
Floyd(g);
return 0;
}
测试结果: