Floyd-Warshall:所有最短路径

Posted

技术标签:

【中文标题】Floyd-Warshall:所有最短路径【英文标题】:Floyd-Warshall: all shortest paths 【发布时间】:2012-07-07 09:00:17 【问题描述】:

我已实现 Floyd-Warshall 以返回每对节点/顶点之间的最短路径的距离以及每对节点/顶点之间的单个最短路径。

有没有办法让它返回每对最短的路径,即使有多个路径被绑定为最短,对于每对节点? (我只是想知道我是否在浪费时间尝试)

【问题讨论】:

key=path-lengthvalue=set of shortest paths at this length 将所有“最短路径”保存在HashMap 中。将 shotest-path 长度保存在单独的变量中,算法完成后,只需从 HashMap 中提取最小值。 【参考方案1】:

如果您只需要计算存在多少不同的最短路径,您可以在shortestPath 数组之外保留一个count 数组。这是对wiki 的伪代码的快速修改。

procedure FloydWarshall ()
    for k := 1 to n
        for i := 1 to n
            for j := 1 to n
                if path[i][j] == path[i][k]+path[k][j] and k != j and k != i
                    count[i][j] += 1;
                else if path[i][j] > path[i][k] + path[k][j]
                    path[i][j] = path[i][k] + path[k][j]
                    count[i][j] = 1

如果您需要找到所有路径的方法,您可以为每一对存储一个类似vector/arraylist 的结构以展开和折叠。这是对同一 wiki 的伪代码的修改。

procedure FloydWarshallWithPathReconstruction ()
    for k := 1 to n
        for i := 1 to n
            for j := 1 to n
                if path[i][k] + path[k][j] < path[i][j]
                    path[i][j] := path[i][k]+path[k][j];
                    next[i][j].clear()
                    next[i][j].push_back(k) // assuming its a c++ vector
                else if path[i][k] + path[k][j] == path[i][j] and path[i][j] != MAX_VALUE and k != j and k != i
                    next[i][j].push_back(k)

注意:如果k==jk==i,这意味着,您正在检查path[i][i]+path[i][j]path[i][j]+path[j][j],两者都应该等于path[i][j],并且不会被推入next[i][j]

应该修改路径重建以处理vector。在这种情况下,计数将是每个 vector 的大小。这是对来自同一 wiki 的伪代码 (python) 的修改。

procedure GetPath(i, j):
    allPaths = empty 2d array
    if next[i][j] is not empty:
        for every k in next[i][j]:
            if k == -1: // add the path = [i, j]
                allPaths.add( array[ i, j] ) 
            else: // add the path = [i .. k .. j]
                paths_I_K = GetPath(i,k) // get all paths from i to k
                paths_K_J = GetPath(k,j) // get all paths from k to j
                for every path between i and k, i_k in paths_I_K:
                    for every path between k and j, k_j in paths_K_J:
                        i_k = i_k.popk() // remove the last element since that repeats in k_j
                        allPaths.add( array( i_k + j_k) )

    return allPaths

注意:path[i][j] 是一个邻接列表。在初始化path[i][j] 的同时,您还可以通过在数组中添加-1 来初始化next[i][j]。例如,next[i][j] 的初始化将是

for every edge (i,j) in graph:
   next[i][j].push_back(-1)

这将处理一条边作为最短路径本身。您必须在路径重建中处理这种特殊情况,这就是我在 GetPath 中所做的。

编辑:“MAX_VALUE”是距离数组中的初始化值。

【讨论】:

我设法通过在else if 语句中添加and k != j 使其工作。然后我编写了一个递归函数来单步执行next。它将next 中的值解释为等于当前节点的值,这意味着可以直接访问下一个节点。这是解释/解开下一个矩阵的合理方式吗?还是有更清洁/更清晰的方法? @user1507844 忽略了k != j 部分。编辑了我的答案以反映它。 @user1507844 我添加了路径重建的代码。如果有直接边缘,我使用-1 作为索引;但是您存储其中一个节点的技术也很好。 我注意到您还添加了k != i,这是为了什么?不确定我是否理解 -1 部分。 next 中的条目在哪里设置为 -1?那是它初始化的吗?再次感谢您的帮助 @user1507844 添加注释以进行更多说明。【参考方案2】:

在某些情况下,当前批准的答案中的“计数”功能会失败。更完整的解决方案是:

procedure FloydWarshallWithCount ()
for k := 1 to n
    for i := 1 to n
        for j := 1 to n
            if path[i][j] == path[i][k]+path[k][j]
                count[i][j] += count[i][k] * count[k][j]
            else if path[i][j] > path[i][k] + path[k][j]
                path[i][j] = path[i][k] + path[k][j]
                count[i][j] = count[i][k] * count[k][j]

这样做的原因是对于任何三个顶点 i、j 和 k,可能存在从 i 到 k 到 j 的多条最短路径。例如在图中:

       3             1
(i) -------> (k) ---------> (j)
 |            ^
 |            |
 | 1          | 1
 |     1      |
(a) -------> (b)

从 i 到 j 通过 k 有两条路径。 count[i][k] * count[k][j]求出从i到k的路径数,从k到j的路径数,乘以求出i -> k -> j的路径数。

【讨论】:

【参考方案3】:

most voted answer的补充:

    GetPath函数中,命令

i_k = i_k.popk() // 删除最后一个元素,因为它在 k_j 中重复

应该向前移动一行,换句话说,进入paths_I_K的循环。

    在 GetPath 结束时,应删除重复的路径。

对应的Python代码如下,已检查正确性:

def get_all_shortest_paths_from_router(i, j, router_list, it=0):                                                                                                    
    all_paths = []
    if len(router_list[i][j]) != 0:
        for k in router_list[i][j]:
            if k == -1: # add the path = [i, j]
                all_paths.append([i, j])
            else: # add the path = [i .. k .. j]
                paths_I_K = get_all_shortest_paths_from_router(i,k,router_list,it+1) # get all paths from i to k
                paths_K_J = get_all_shortest_paths_from_router(k,j,router_list,it+1) # get all paths from k to j
                for p_ik in paths_I_K:
                    if len(p_ik) != 0:
                        p_ik.pop() # remove the last element since that repeats in k_j
                    for p_kj in paths_K_J:
                        all_paths.append(p_ik + p_kj)

    if it==0:
        all_paths.sort()
        all_paths = [all_paths[i] for i in range(len(all_paths)) if i == 0 or all_paths[i] != all_paths[i-1]]
    return all_paths

【讨论】:

以上是关于Floyd-Warshall:所有最短路径的主要内容,如果未能解决你的问题,请参考以下文章

使用 Floyd-Warshall 查找所有最短路径和距离

最短路径之Dijkstra算法和Floyd-Warshall算法

如何在 Floyd-Warshall 算法中输出最短路径?

Floyd-Warshall 算法返回具有相同权重的每条最短路径

多源最短路径--Floyd-Warshall算法

如何在 Floyd-Warshall 算法中找到最短路径和最短成本