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

Python 如何在有向无环图中有效地找到由k个节点组成的所有路径?,python,algorithm,graph,networkx,graph-traversal,Python,Algorithm,Graph,Networkx,Graph Traversal,我有一个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.su

我有一个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()
)这个简单的函数速度非常慢。有没有更有效的策略来做到这一点

我看过这个问题,但问题的作者以一种相当模糊的方式自我解决了

谢谢

更新:


因为我无法得到任何令人满意的算法来解决这个问题,所以我最终以一个工作者的身份使用我的天真函数来并行化作业,同时将所有数据转储到一个队列中。我使用
pool.imap_unordered
启动worker函数,并聚合队列中的结果。它仍然很慢(500万个节点需要几个小时)。我还应该提供我正在处理的节点的平均程度的数据,因为这将影响我的工作人员的运行速度。但是,我暂时不提这个问题。

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

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


下面是一个函数,它返回图形中所有节点之间给定长度的路径。它在所有节点集之间迭代,并使用
networkx.all\u simple\u path
获取路径

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']]

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

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

一个想法是算法,它以向后的拓扑顺序遍历DAG,并完成两件事:

  • 对于节点,保留从长度为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()方法中可以很快检测到这一点。也许这种优化可以帮助您不将不需要的数据发送到其他进程。

这将找到所有节点对之间的所有路径。然后选择4条路径的长度。您可以通过将截止值设置为4来显著加快速度,因此当路径长度超过4时,它将停止。谢谢James。我担心这种方法的复杂性可能是O^2或更糟,因为您正在对所有节点进行双重迭代。我对您的代码进行了基准测试,它比我的天真策略和上面Joel建议的递归策略慢得多。我很欣赏使用
所有简单路径的想法。思考如何以更好的方式构建它。更具体地说,我尝试在1K节点的图形上运行您的代码,大约需要4分钟。上面Joel的策略大约花了2.7秒,而我的策略则花了3.5秒。请注意,在您链接的问题的答案中描述的回溯基本上是利用了这样一个事实,即一旦您计算了来自某个节点的所有路径,如果您再次遇到该节点(如果您保存了该数据),则无需再次这样做。我的回答用了另一种方式。你能说说你为什么需要这个吗?你确定你需要列表而不是生成器吗?这是我试图开发的更大算法的一部分,用于查找人类基因组中的特定重复序列(基本上是一个由四个字母a、T、G、C组成的大字符串)。此处的每个节点标记特定重复边的位置及其距离。仅当节点的距离小于定义的值时,才会连接节点。现在我想确定这个重复的块,因为它们在四个重复的任意组合中都有意义。我想将所有路径转储到一个HDF5文件中。我希望这不会是一个快速的过程,因为我可能有多达100万个节点。因此,我需要在所有昂贵的图遍历之后转储。我没有看到一个好的解决方案。您应该检查它,但我怀疑您的运行时由
路径控制。append([n1,n2,n3,n4])
。如果是这样的话,你就无能为力了。谢谢乔尔。在这里使用递归是非常周到和恰当的。然而,当我对这段代码进行基准测试时,我没有发现比我天真的策略有任何性能提升。此外,我有一个拥有数百万个节点的大型网络。我想跟踪路径搜索的进度,递归使其难以处理。我们能进一步改进吗?这让我很惊讶,但从更详细的角度来看,它开始有意义了。我必须做
[u]+su
for n2, n3 in DAG.edges():
  for n1, n4 in itertools.product(DAG.predecessors(n2), DAG.successors(n3)):
    paths.append([n1, n2, n3, n4])