针对对称邻接矩阵优化 Floyd-Warshall

Posted

技术标签:

【中文标题】针对对称邻接矩阵优化 Floyd-Warshall【英文标题】:Optimise Floyd-Warshall for symmetric adjacency matrix 【发布时间】:2011-01-03 12:14:16 【问题描述】:

如果保证有对称邻接矩阵,是否有降低 Floyd-Warshall 运行时常数因子的优化?

【问题讨论】:

不是总是对称的吗? O_o 你有时可能有有向边,但它不是对称的。 【参考方案1】:

经过一番思考,我想出了:

for (int k = 0; k < N; ++k)
    for (int i = 0; i < N; ++i)
        for (int j = 0; j <= i; ++j)
            dist[j][i] = dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);

当然,现在我们都需要证明它是正确的和更快的。

正确性更难证明,因为它依赖于 Floyd-Warshall 的非平凡证明。这里给出了一个很好的证明:Floyd-Warshall proof

输入矩阵是symmetric。现在证明的其余部分使用修改后的 Floyd-Warshall 证明来表明 2 个内部循环中的计算顺序无关紧要,并且图 保持 对称每一步之后。如果我们证明这两个条件都为真,那么两种算法都会做同样的事情。

让我们将dist[i][j][k] 定义为从ij 的距离,仅使用集合0, ..., k 中的顶点作为从ij 的路径上的中间顶点。

dist[i][j][k-1] 定义为从ij 的边的权重。如果中间没有边,则该权重被视为无穷大。

现在使用与上面链接的证明相同的逻辑:

dist[i][j][k] = min(dist[i][j][k-1], dist[i][k][k-1] + dist[k][j][k-1])

现在在计算dist[i][k][k]dist[k][i][k] 也是如此):

dist[i][k][k] = min(dist[i][k][k-1], dist[i][k][k-1] + dist[k][k][k-1])

现在因为dist[k][k][k-1] 不能为负数(或者我们在图中会有一个negative loop),这意味着dist[i][k][k] = dist[i][k][k-1]。因为如果dist[k][k][k-1] = 0那么两个参数是一样的,否则选择min()的第一个参数。

所以现在,因为 dist[i][k][k] = dist[i][k][k-1],在计算 dist[i][j][k] 时,dist[i][k]dist[k][j] 是否已经允许 k 在它们的路径中并不重要。由于dist[i][j][k-1] 仅用于dist[i][j][k] 的计算,dist[i][j] 将在矩阵中保留dist[i][j][k-1],直到dist[i][j][k] 被计算出来。如果 ij 等于 k 则适用上述情况。

因此,计算的顺序无关紧要。

现在我们需要在算法的所有步骤之后显示dist[i][j] = dist[j][i]

我们从一个对称网格开始,因此dist[a][b] = dist[b][a],对于所有ab

dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
           = min(dist[j][i], dist[k][i] + dist[j][k])
           = min(dist[j][i], dist[j][k] + dist[k][i])
           = dist[j][i]

因此,我们的赋值是正确的,并且它会保持dist[a][b] = dist[b][a] 的不变量。因此在算法的所有步骤之后dist[i][j] = dist[j][i]

因此,两种算法都会产生相同的正确结果。

速度更容易证明。内部循环的调用次数是正常调用次数的一半多一点,因此该函数的速度大约是其两倍。只是稍微慢了一点,因为您仍然分配相同的次数,但这并不重要,因为 min() 占用了您大部分时间。

如果您发现我的证明有任何问题,无论技术性如何,请随时指出,我会尝试修复它。

编辑:

您可以通过这样更改循环来加快速度并节省一半的内存:

for (int k = 0; k < N; ++k) 
    for (int i = 0; i < k; ++i)
        for (int j = 0; j <= i; ++j)
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[j][k]);
    for (int i = k; i < N; ++i) 
        for (int j = 0; j < k; ++j)
            dist[i][j] = min(dist[i][j], dist[k][i] + dist[j][k]);
        for (int j = k; j <= i; ++j)
            dist[i][j] = min(dist[i][j], dist[k][i] + dist[k][j]);
    

这只是拆分了优化算法的上述for循环,所以它仍然是正确的,它可能会获得相同的速度,但使用一半的内存。

感谢 Chris Elion 的创意。

【讨论】:

请注意,上面的两个代码在实验上不会产生相同的结果。 第二个代码中的第一个更新应该是:dist[i][j] = min(dist[i][j], dist[k][i] + dist[k][j ]);第二次更新应该是:dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);第三次更新是正确的。 在假设无向和无权重的情况下,是否可以使用第二个代码进行其他改进?【参考方案2】:

(使用***文章中伪代码中的符号)我相信(但尚未测试)如果 edgeCost 矩阵是对称的,那么每次迭代后路径矩阵也将是对称的。因此,您只需在每次迭代时更新一半的条目。

在较低的层次上,只需要存储一半的矩阵(因为 d(i,j) = d(j,i)),这样可以减少使用的内存量,并有望减少缓存未命中,因为您将多次访问相同的数据。

【讨论】:

以上是关于针对对称邻接矩阵优化 Floyd-Warshall的主要内容,如果未能解决你的问题,请参考以下文章

数据结构----图(略)

数据结构----图(略)

lisp 中的邻接矩阵/Floyd/Warshall

Floyd-Warshall 算法:获得最短路径

图的邻接矩阵算法

图-图的表示方法