如何在有向无环图中有效地找到由k个节点形成的所有路径?

时间:2016-08-27 19:22:31

标签: python algorithm graph networkx graph-traversal

我有一个看起来像这样的DAG: Example DAG

我想在此图中提取由4个节点构成的所有路径。

我的预期结果应如下所示:

N1 - > N2 - > N3 - > N4

N1 - > N2 - > N3 - > N5

N1 - > N3 - > N4 - > N5

N2 - > N3 - > N4 - > N5

我目前的尝试看起来像这样

def path_finder(n1):
    paths = []
    if DAG.has_node(n1):
        for n2 in DAG.successors(n1):
            for n3 in DAG.successors(n2):
                for n4 in DAG.successors(n3):
                    paths.append([n1, n2, n3, n4])
    return paths

我为每个节点调用此函数。 DAG是一个全局变量,更具体地说,它是一个networkx对象(DAG = networkx.DiGraph())这个天真的函数很慢。有没有更有效的策略来做到这一点?

我看过问题20262712,但是问题的作者以相当模糊的方式自我解决了问题。

由于

更新:

由于我无法获得任何令人满意的算法来解决这个问题,因此我最终使用我的天真函数将作业并行化,同时将所有数据转储到队列中。我使用pool.imap_unordered启动了worker函数并聚合了队列中的结果。它仍然很慢(5M节点需要几个小时)。我还应提供我正在处理的节点平均程度的数据,因为这会影响我的工作人员的运行速度。但是,我现在就把它留下来。

3 个答案:

答案 0 :(得分:1)

这是一个函数,它返回图中所有节点之间给定长度的路径。它在所有节点集之间迭代,并使用networkx.all_simple_paths来获取路径。

import networkx as nx

g = nx.DiGraph()

g.add_nodes_from(['A','B','C','D','E'])

g.add_path(['A','B','C','D'])
g.add_path(['A','B','C','E'])
g.add_path(['A','C','D','E'])
g.add_path(['B','C','D','D'])

def find_paths(graph, number_nodes=4):
    paths = []
    for source in graph.nodes_iter():
        for target in graph.nodes_iter():
            if not source==target:
                p_source_target = nx.all_simple_paths(graph, 
                                                      source, 
                                                      target, 
                                                      cutoff=number_nodes-1)
                paths.extend([p for p in p_source_target if len(p)==number_nodes])
    return paths

find_paths(g)
# output:
[['B', 'C', 'D', 'E'],
 ['A', 'C', 'D', 'E'],
 ['A', 'B', 'C', 'E'],
 ['A', 'B', 'C', 'D']]

答案 1 :(得分:0)

你的部分问题可能是,如果你遇到一个节点u作为路径中的第二个节点,那么你会进行所有计算以找到长度为3的所有路径。但是如果你遇到的话u再次作为第二个节点,重复所有这些计算。

所以尽量避免这种情况。我们将首先递归计算所有长度为3的路径(这需要计算长度为2的路径)

def get_paths(G, n):
    '''returns a dict, paths, such that paths[u] is a list of all paths 
       of length n that start from u'''
    if n == 1: #base case, return a dict so that D[u] is a
               #list of all length 1 paths starting from u.
               #it's a boring list.
        return {u: [[u]] for u in G.nodes()}
    #if we get to here n>1 (unless input was bad)
    subpath_dict = get_paths(G,n-1)  #contains all length n-1 paths, 
                                     #indexed by first node
    path_dict = {}
    for u in G:
        path_dict[u] = []  
        for v in G.successors(u):
            path_dict[u].extend([[u]+subpath for subpath in subpath_dict[v]])
    return(path_dict)

G=nx.DiGraph()
G.add_path([1,2,3,4,5,6])
G.add_path([1,3,6,8,10])

path_dict = get_paths(G,4)
path_list = []
for paths in path_dict.values():
    path_list.extend(paths)

答案 2 :(得分:0)

序列数为| V | * d ^ 3,其中d为平均节点输出度。从图形的创建方式来看,d是有界的。我想d不是很小(比如< 5)。这意味着,对于5M节点图,有> 1G路径。

由于找到一条路径很快(它们很短),因此不确定类似DP的算法是否有帮助。类似DP的算法试图利用部分计算的数据,因此存储和检索数据的开销可能比仅计算所需的部分数据更大。

一个想法是以后向拓扑顺序遍历DAG并执行两项操作的算法:

  • for node保留从长度为3的节点开始的所有路径,
  • 使用长度为3的后继路径打印长度为4的所有路径。

此方法可以使用大量内存,但是对于不是任何遍历边界节点后继节点的节点,可以释放其中一些内存。

其他想法只是使简单算法更加优化。在您的解决方案中,每个节点有三个for循环。这意味着所有路径都有四个for循环。请注意,每个循环都是通过节点。有可能的 通过迭代边连接前两个循环。这是因为每条路径都必须以一条边开始。算法就像:

for n1, n2 in DAG.edges():
  for n3 in DAG.successors(n2):
    for n4 in DAG.successors(n3):
      paths.append([n1, n2, n3, n4])

首先选择中间边缘甚至更简单:

for n2, n3 in DAG.edges():
  for n1, n4 in itertools.product(DAG.predecessors(n2), DAG.successors(n3)):
    paths.append([n1, n2, n3, n4])

可以通过不选择在源节点上开始或在目标节点上结束的中间边缘来优化外部循环。但是在product()方法中检测得非常快。也许这种优化可以通过不将不需要的数据发送到其他过程来提供帮助。