Reconstructing the paths for multiple shortest paths between 2 vertices

帅比萌擦擦* 提交于 2020-01-16 09:39:07

问题


I'm trying to write an algorithm which will reconstruct the shortest path/s (multiple paths tied for the shortest if there are any) between all pairs of vertices in the Floyd-Warshall algorithm. I took some hints from the question here: https://stackoverflow.com/a/11371588/7447425

Based on this, I've modified the Floyd-Warshall algorithm:

from math import inf

def floyd_warshall(n, edge):
    rn = range(n)
    dist = [[inf] * n for i in rn]
    next  = [[-1]   * n for i in rn]

    for i in rn:
        for j in rn:
            next[i][j]=[-1]

    for i in rn:
        dist[i][i] = 0

    for u, v, w in edge:
        dist[u][v] = w
        next[u][v]=[v]

    for k in rn:
        for i in rn:
            for j in rn:   
                sum_ik_kj = dist[i][k] + dist[k][j]
                if dist[i][j] > sum_ik_kj:
                   dist[i][j] = sum_ik_kj
                   next[i][j]=nxt[i][k]

                elif(sum_ik_kj==dist[i][j] and dist[i][j]!=inf and k!=j and k!=i):
                   next[i][j].extend(next[i][k])

return next

The graph is in the form of edge-list for e.g.,:

edge = [[0,2,2],[2,3,2],[3,1,1],[1,0,4],[1,2,3],[0,3,4],[3,0,5]]
# Here n is the value of the highest numbered-vertex. In the above graph, n=4
n=4
next=floyd_warshall(n,edge)

Everything seems to be working well till this point.

For the path-reconstruction,

for i in range(n):
    for j in range(n):
        if(i!=j):
            allPaths=[]
            allPaths=getpath(i,j,next,allPaths)
            print(allPaths)

def getpath(i,j,nxt,allPaths):
    for k in next[i][j]:
        if(k==-1):
            allPaths.extend([i,j])

        elif(k==j):
            allPaths.append(j)

        else:
            paths_I_K=getpath(i,k,next,allPaths)
            paths_K_J=getpath(k,j,next,allPaths)
            for i_k in paths_I_K:
                for k_j in paths_K_J:
                    i_k.pop()
                    allPaths.append(i_k+k_j)
    return allPaths

But this isn't working. So, can anyone kindly rectify the getpath function (or write a more efficient one) so that I can get all the shortest paths (paths tied for shortest paths) between every pair of vertices?

For the graph above, I've got

next=
[[[-1], [3, 2], [2], [3, 2]],
 [[0], [-1], [2], [2]],
 [[3], [3], [-1], [3]],
 [[0, 1], [1], [1], [-1]]]

which is accurate, but path reconstruction through this is becoming quite a hassle.


回答1:


Here are the changes I made to your function.

  1. I renamed next to next_node because next is actually a Python keyword.
  2. I renamed dist to cost to be more descriptive.
  3. I stored next_node as a set() to avoid having the same element added twice.
  4. I made sure to make a new set() when paths lead through k. That is to avoid unintentional data aliasing. Your code had a bug where if the route from 1 - 3 - 2 matches 1 - 4 - 2 that you alias next[1][2] to next[1][3] then add 4 to it, which could be wrong for next[1][3].
  5. I took into account the fact that your format allows multiple edges between nodes.

This gave me the following function that is very similar to yours:

def floyd_warshall(n, edge):
    rn = range(n)
    cost = [[inf] * n for i in rn]
    next_node = [[set() for j in rn] for i in rn]

    for i in rn:
        cost[i][i] = 0

    for u, v, w in edge:
        # The data format allows multiple edges between two nodes.
        if w < cost[u][v]:
            cost[u][v] = w
            next_node[u][v] = set([v])
        elif w == cost[u][v] and w < inf:
            next_node[u][v].add(v)

    for k in rn:
        for i in rn:
            for j in rn:
                cost_ik_kj = cost[i][k] + cost[k][j]
                if cost_ik_kj < cost[i][j]:
                    cost[i][j] = cost_ik_kj
                    next_node[i][j] = set(next_node[i][k]) # Want a copy.
                elif cost_ik_kj == cost[i][j] and cost_ik_kj < inf:
                    next_node[i][j].update( next_node[i][k] )

    return next_node

I then wrote all_paths as an iterator. This made it very simple. It is also possible that there will be many, many paths between two points, and an iterator avoids using too much memory in that case. And if you want, you can always turn it from an iterator into an array very easily. Here is that function:

def all_paths(next_node, i, j):
    if 0 == len(next_node[i][j]):
        if i == j:
            yield [j]
        else:
            pass # There is no path.
    else:
        for k in next_node[i][j]:
            for rest in all_paths(next_node, k, j):
                yield [i] + rest

And here is some test code to demonstrate it:

edge = [[0,2,2],[2,3,2],[3,1,1],[1,0,4],[1,2,3],[0,3,4],[3,0,5]]
# Here n is the value of the highest numbered-vertex. In the above graph, n=4
n=4
next_node = floyd_warshall(n,edge)
for i in range(4):
    for j in range(4):
        for path in all_paths(next_node, i, j):
            print((i, j, path))


来源:https://stackoverflow.com/questions/57165167/reconstructing-the-paths-for-multiple-shortest-paths-between-2-vertices

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!