有向无环图中从源到汇的所有路径的列表

时间:2010-07-19 04:36:41

标签: python networkx directed-acyclic-graphs

  

可能重复:
  [python]: path between two nodes

有人能指出一些有关如何做到这一点的资源吗?我正在使用networkx作为我的python库。

谢谢!

3 个答案:

答案 0 :(得分:6)

这是基于Alex Martelli的答案,但它应该有效。它取决于表达式source_node.children,它产生一个迭代,它将遍历source_node的所有子元素。它还依赖于==运算符的工作方式来比较两个节点以查看它们是否相同。使用is可能是更好的选择。显然,在您正在使用的库中,获取所有子项的可迭代语法是graph[source_node],因此您需要相应地调整代码。

def allpaths(source_node, sink_node):
    if source_node == sink_node: # Handle trivial case
        return frozenset([(source_node,)])
    else:
        result = set()
        for new_source in source_node.children:
            paths = allpaths(new_source, sink_node, memo_dict)
            for path in paths:
                path = (source_node,) + path
                result.add(path)
        result = frozenset(result)
        return result

我主要担心的是,这会进行深度优先搜索,当从源到多个路径时,它会浪费精力,这些路径是孙子,曾孙等所有源,但不一定是接收器的父级。如果它记住给定源和汇节点的答案,则可以避免额外的努力。

这是一个如何工作的例子:

def allpaths(source_node, sink_node, memo_dict = None):
    if memo_dict is None:
        # putting {}, or any other mutable object
        # as the default argument is wrong 
        memo_dict = dict()

    if source_node == sink_node: # Don't memoize trivial case
        return frozenset([(source_node,)])
    else:
        pair = (source_node, sink_node)
        if pair in memo_dict: # Is answer memoized already?
            return memo_dict[pair]
        else:
            result = set()
            for new_source in source_node.children:
                paths = allpaths(new_source, sink_node, memo_dict)
                for path in paths:
                    path = (source_node,) + path
                    result.add(path)
            result = frozenset(result)
            # Memoize answer
            memo_dict[(source_node, sink_node)] = result
            return result

这也允许您在调用之间保存memoization字典,因此如果您需要为多个源节点和汇聚节点计算答案,则可以避免大量额外的工作。

答案 1 :(得分:2)

这个实际上适用于networkx,它是非递归的,对于大型图形来说可能很好。

def find_all_paths(graph, start, end):
    path  = []
    paths = []
    queue = [(start, end, path)]
    while queue:
        start, end, path = queue.pop()
        print 'PATH', path

        path = path + [start]
        if start == end:
            paths.append(path)
        for node in set(graph[start]).difference(path):
            queue.append((node, end, path))
    return paths

答案 2 :(得分:1)

我不确定是否有可用的特殊优化 - 在查找其中任何一个之前,我会做一个简单的递归解决方案,例如(使用networkx只有通过节点索引图形的功能) iterable产生它的邻居节点[[dict,在networkx的情况下,但我没有特别使用它]])...:

def allpaths(G, source_nodes, set_of_sink_nodes, path_prefix=()):
  set_of_result_paths = set()
  for n in source_nodes:
    next_from_n = []
    for an in G[n]:
      if an in set_of_sink_nodes:
        set_of_result_paths.add(path_prefix + (n, an))
      else:
        next_from_n.append(an)
    if next_from_n:
      set_of_result_paths.update(
          allpaths(G, next_from_n, set_of_sink_nodes, path_prefix + (n,)))
  return set_of_result_paths

这应该是可证明的正确(但我不打算做证明,因为它已经很晚了,我累了,头脑模糊;-)并且可以用来验证任何进一步的优化; - )。

我尝试的第一个优化是某种简单的memoizing:如果我已经计算了从某个节点N到任何目标节点的路径集(无论在我进行该计算时导致N的前缀是什么),我可以把它藏在关键N下的一个字典中,如果我再次以不同的路线到达N,我可以避免进一步的重新计算; - )。