在Igraph/Networkx中实现k最短路径(Yen算法)

3

经过深入研究并基于thisthis和更多建议,我被建议在大型无向、循环、加权图中实现k短路径算法,以找到第一、第二、第三... k个最短路径。约2000个节点。

维基百科上的伪代码如下:

function YenKSP(Graph, source, sink, K):
  //Determine the shortest path from the source to the sink.
 A[0] = Dijkstra(Graph, source, sink);
 // Initialize the heap to store the potential kth shortest path.
 B = [];

for k from 1 to K:
   // The spur node ranges from the first node to the next to last node in the shortest path.
   for i from 0 to size(A[i]) − 1:

       // Spur node is retrieved from the previous k-shortest path, k − 1.
       spurNode = A[k-1].node(i);
       // The sequence of nodes from the source to the spur node of the previous k-shortest path.
       rootPath = A[k-1].nodes(0, i);

       for each path p in A:
           if rootPath == p.nodes(0, i):
               // Remove the links that are part of the previous shortest paths which share the same root path.
               remove p.edge(i, i) from Graph;

       // Calculate the spur path from the spur node to the sink.
       spurPath = Dijkstra(Graph, spurNode, sink);

       // Entire path is made up of the root path and spur path.
       totalPath = rootPath + spurPath;
       // Add the potential k-shortest path to the heap.
       B.append(totalPath);

       // Add back the edges that were removed from the graph.
       restore edges to Graph;

   // Sort the potential k-shortest paths by cost.
   B.sort();
   // Add the lowest cost path becomes the k-shortest path.
   A[k] = B[0];
return A;

主要问题在于我还无法编写正确的Python脚本来实现这一点(删除边缘并将它们正确地放回原位),因此我只能像往常一样依靠Igraph,目前只做到了这一步:
def yenksp(graph,source,sink, k):
    global distance
    """Determine the shortest path from the source to the sink."""
    a = graph.get_shortest_paths(source, sink, weights=distance, mode=ALL, output="vpath")[0]
    b = [] #Initialize the heap to store the potential kth shortest path
    #for xk in range(1,k):
    for xk in range(1,k+1):
        #for i in range(0,len(a)-1):
        for i in range(0,len(a)):
            if i != len(a[:-1])-1:
                spurnode = a[i]
                rootpath = a[0:i]
                #I should remove edges part of the previous shortest paths, but...:
                for p in a:
                    if rootpath == p:
                        graph.delete_edges(i) 

            spurpath = graph.get_shortest_paths(spurnode, sink, weights=distance, mode=ALL, output="vpath")[0]
            totalpath = rootpath + spurpath
            b.append(totalpath)
            # should restore the edges
            # graph.add_edges([(0,i)]) <- this is definitely not correct.
            graph.add_edges(i)
        b.sort()
        a[k] = b[0]
    return a

这只是一个很差的尝试,它只返回一个列表中的列表。

我已经不太确定我在做什么了,我已经非常绝望了,在过去的几天里,我的观点已经改变了180度,甚至有一次。我只是一个尽力而为的新手,请帮忙。还可以建议Networkx实现。

附言:很可能没有其他有效的方法,因为我们已经在这里进行了研究。我已经收到了很多建议,并且感谢社区。深度优先搜索(DFS)或广度优先搜索(BFS)都行不通。图形很大。

编辑:我一直在纠正Python脚本。简而言之,这个问题的目的是正确的脚本。

2个回答

2

在Github上有一个Yen KSP的Python实现,YenKSP。完全归功于作者,该算法的核心代码如下:

def ksp_yen(graph, node_start, node_end, max_k=2):
    distances, previous = dijkstra(graph, node_start)

    A = [{'cost': distances[node_end], 
          'path': path(previous, node_start, node_end)}]
    B = []

    if not A[0]['path']: return A

    for k in range(1, max_k):
        for i in range(0, len(A[-1]['path']) - 1):
            node_spur = A[-1]['path'][i]
            path_root = A[-1]['path'][:i+1]

            edges_removed = []
            for path_k in A:
                curr_path = path_k['path']
                if len(curr_path) > i and path_root == curr_path[:i+1]:
                    cost = graph.remove_edge(curr_path[i], curr_path[i+1])
                    if cost == -1:
                        continue
                    edges_removed.append([curr_path[i], curr_path[i+1], cost])

            path_spur = dijkstra(graph, node_spur, node_end)

            if path_spur['path']:
                path_total = path_root[:-1] + path_spur['path']
                dist_total = distances[node_spur] + path_spur['cost']
                potential_k = {'cost': dist_total, 'path': path_total}

                if not (potential_k in B):
                    B.append(potential_k)

            for edge in edges_removed:
                graph.add_edge(edge[0], edge[1], edge[2])

        if len(B):
            B = sorted(B, key=itemgetter('cost'))
            A.append(B[0])
            B.pop(0)
        else:
            break

    return A

它确实让我更接近解决方案,因为我可以分析他是如何存储-删除边缘,然后再添加它们的。无论如何,很难跟随这个示例,因为它调用了他为涉及许多库的DiGraph编写的脚本,希望在这种情况下不是那么必要(包括调用外部函数的库)。我继续使用Igraph来处理,我们将看到结果。谢谢。这可能是答案,但我还没有看到它。我正在分析它。 - Laci
我在维基百科上花时间研究算法,并根据上面的示例构建了它。在使用Igraph(以及可能是其他库)时,只需注意在方法中最好使用图的深拷贝。这样,在添加边缘后,它不会搞乱原始图的边缘ID,因此您可以继续使用它。 - Laci

2
我和你遇到了同样的问题,所以我将维基百科Yen算法的伪代码移植到Python中,并使用igraph库进行了实现。
你可以在这里找到它:https://gist.github.com/ALenfant/5491853

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接