您的位置:首页 > 其它

结点对最短路径之Floyd算法原理详解及实现

2016-06-02 14:08 986 查看
上两篇博客介绍了计算单源最短路径的Bellman-Ford算法和Dijkstra算法。Bellman-Ford算法适用于任何有向图,即使图中包含负环路,它还能报告此问题。Dijkstra算法运行速度比Bellman-Ford算法要快,但是其要求图中不能包含负权重的边。

单源最短路径之Bellman-Ford算法

单源最短路径之Dijkstra算法

在很多实际问题中,我们需要计算图中所有结点对间的最短路径。当然,我们可以使用上述两种算法来计算每一个顶点的单源最短路径,对于图G=(V,E)来说,使用Bellman-Ford算法计算结点对最短路径的时间复杂度为O(V^2 * E),使用Dijkstra算法计算结点对最短路径的时间复杂度为O(V^3)。本文将会介绍一种应用更广泛的算法,而且它可以应用于有负权重边但没有负环路的图中,其时间复杂度为O(V^3),那就是Floyd-Warshall算法。

1. Floyd算法的原理

在上一篇博客 单源最短路径之Dijkstra算法 中,提到了图的一个重要性质:一条最短路径的子路径也是一条最短路径。因此,一条最短路径要么只包含一条直接相连的边,要么就经过一条或多条到达其它顶点的最短路径。



上图给出的是顶点i到顶点j的路径示意图。i到j的路径为<i,...,k,...,j>,其中顶点k是路径i到j的一个编号最大的中间顶点,即路径<i,...,k>中的所有顶点编号求取自集合{1,2,3,...,k-1},路径<k,...,j>也是一样的。因为路径<i,...,k,...,j>为最短路径,那么路径<i,...,k>和路径<k,...,j>也是最短路径。对路径<i,...,k>和路径<k,...,j>也可以递归做出上述操作。

于是,我们可以推出如下递归公式。

dij(k) = wij                                                        当k=0;

dij(k) = min(dij(k-1), dik(k-1)+dkj(k-1))             当k>0;

上述公式中dij为顶点i到顶点j的当前路径的长度,k是当前递归中路径的最大顶点编号。当k=0时,路径的中间顶点的编号不大于0,即不存在任何中间顶点,这种情况顶点i到顶点j的路径必然只是一条连接这两个顶点的边,因此其长度为该边的权重。当k>0,每次递归时加入编号为k的顶点,可以根据其它"当前最短路径"构造顶点i到顶点j的一条新路径,并与其原路径进行比较,从中选择更短的。这是一种自底向上的动态规划算法。

2. Floyd算法的C实现

本文实现的Floyd算法所需要的输入与前面的博客介绍的不一样。前面介绍的所有图算法需要的图都是用邻接表表示的。下面给出的Floyd算法需要的图使用邻接矩阵表示的,即权重图。该实现使用前驱子图(二维矩阵)来记录结点对的最短路径的目的顶点的前驱顶点编号(前一个顶点的编号)。

/**
* Floyd 寻找结点对的最短路径算法
* w 权重图
* vertexNum 顶点个数
* lenMatrix 计算结果的最短路径长度存储矩阵(二维)
* priorMatrix 前驱子图(二维),路径<i, ..., j>重点j的前一个顶点k存储在priorMatrix[i][j]中
*/
void Floyd_WallShall(int **w, int vertexNum, int **lenMatrix, int **priorMatrix)
{
// 初始化
for (int i = 0; i < vertexNum; i++)
{
for (int j = 0; j < vertexNum; j++)
{
*((int*)lenMatrix + i*vertexNum + j) = *((int*)w + i*vertexNum + j);
if (*((int*)w + i*vertexNum + j) != INF && i != j)
{
*((int*)priorMatrix + i*vertexNum + j) = i;
}
else
{
*((int*)priorMatrix + i*vertexNum + j) = -1;
}
}
}

// Floyd算法
for (int k = 0; k < vertexNum; k++)
{
for (int i = 0; i < vertexNum; i++)
{
for (int j = 0; j < vertexNum; j++)
{
int Dij = *((int*)lenMatrix + i*vertexNum + j);
int Dik = *((int*)lenMatrix + i*vertexNum + k);
int Dkj = *((int*)lenMatrix + k*vertexNum + j);
if (Dik != INF && Dkj != INF && Dij > Dik + Dkj)
{
*((int*)lenMatrix + i*vertexNum + j) = Dik + Dkj;
*((int*)priorMatrix + i*vertexNum + j) = *((int*)priorMatrix + k*vertexNum + j);
}
}
}
}
}上述程序需要输入一个邻接矩阵,顶点的个数,以及用于存储结果路径长度的矩阵和前驱子图矩阵。这些矩阵本质上均是一个二维数组。该算法首先对长度矩阵和前驱子图进行初始化,也就是递推公式当k=0时的操作,然后就进入循环反复更新结点对的路径。算法没计算一次所有结点对的路径,需要进行V^2次运算,而算法需要从小到大依次将V个顶点加入到图中进行运算,于是整个算法的时间复杂度为O(V^3)。
这里简单说一下前驱子图priorMatrix。我们可以通过前驱子图找到任意结点对的最短路径。例如我们要找到顶点i到顶点j的一条最短路径,可以先找到k=priorMatrix[i][j],此时就知道路径为<i,...,k,j>,然后我们再找到路径<i,...,k>的前驱顶点,即priorMatrix[i][k],如此类推。这一操作的正确性由上面提到的性质(一条最短路径的子路径也是一条最短路径)保证。

下面给出一个应用上述算法的例子。

int w[5][5] = { 0, 3, 8, INF, -4,
INF, 0, INF, 1, 7,
INF, 4, 0, INF, INF,
2, INF, -5, 0, INF,
INF, INF, INF, 6, 0};
int lenMatrix[5][5];
int priorMatrix[5][5];

Floyd_WallShall((int**)w, 5, (int**)lenMatrix, (int**)priorMatrix);
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if (lenMatrix[i][j] == INF)
{
printf("从%d到%d\t\t长度:INF\n", i, j);
}
else
{
printf("从%d到%d\t\t长度:%d\t\t路径:", i, j, lenMatrix[i][j]);
printIJPath((int**)priorMatrix, 5, i + 1, j + 1);
}
}
}printIJPath方法的定义如下。
/**
* 根据前驱子图打印i到j的路径,输入顶点编号从1开始,输出顶点编号从1开始
*/
void printIJPath(int **prior, int vertexNum, int i, int j)
{
i--; j--;
printf("%d", j + 1);
int k = *((int*)prior + i*vertexNum + j);
while (k != -1)
{
printf(" <- %d", k + 1);
k = *((int*)prior + i*vertexNum + k);
}
printf("\n");
}上述例程构造的图以及运行结果如下图所示。前驱子图总priorMatrix[i][i]=-1。



长度矩阵
01-32-4
30-41-1
74053
2-1-50-2
85160
前驱子图
-12340
3-1310
32-110
323-10
3234-


3. 总结

Floyd算法的时间复杂度为O(V^3),因为其实现代码很紧凑,所以时间复杂度的常数项很小。Floyd算法是一种应用非常广泛的计算结点对最短路径的算法。其实还有一种结合了Bellman-Ford算法和Dijkstra算法的Johnson算法,该算法在用于稀疏图时运行速度比Floyd算法更快,并且能够报告图中存在负环路的情况(得益于Bellman-Ford算法)。Johnson算法的时间复杂度为Bellman-Ford算法的时间复杂度加上Dijkstra算法的时间复杂度。如果使用二叉堆实现Dijkstra算法的最小优先队列,那么Johnson算法时间复杂度为O(VElgV+VE)=O(VElgV)。Johnson算法的具体介绍可以参考其它资料,下面给出的个github项目中也有具体的C实现代码。

完整的程序可以看到我的github项目
数据结构与算法

这个项目里面有本博客介绍过的和没有介绍的以及将要介绍的《算法导论》中部分主要的数据结构和算法的C实现,有兴趣的可以fork或者star一下哦~ 由于本人还在研究《算法导论》,所以这个项目还会持续更新哦~ 大家一起好好学习~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息