繁体   English   中英

在具有多个起始节点的有向无环图 (DAG) 中查找所有路径的最快方法?

[英]Fastest way to find all paths in a directed acyclic graph (DAG) with multiple starting nodes?

我试图在具有多个起始节点的有向无环图 (DAG) 中找到所有路径。 我已经有了由列表列表表示的 DAG,以及从开始到终止的每个级别的节点

dag = [[4, 6, 7], [5, 6, 7], [4, 5, 6], [4, 5, 7],
       [], [], [], [], [1, 3], [1, 2], [0, 2, 3]]
levels = [[8, 9, 10], [0, 1, 2, 3], [4, 5, 6, 7]]

按级别着色的示例 DAG 如下所示: 在此处输入图片说明

由于我有 3 个起始节点[8, 9, 10] ,我的第一个想法是执行 3 个 DFS。 下面是我的代码:

def get_all_paths(dag, sources):

    def dfs(data, path, paths=None):
        if paths is None:
            paths = []
        prev = path[-1]
        if data[prev]:
            for val in data[prev]:
                new_path = path + [val]
                paths = dfs(data, new_path, paths)
        else:
            paths += [path]
        return paths

    all_paths = []
    for i in sources:
        paths = dfs(data=dag, path=[i], paths=[])
        all_paths += paths

    return all_paths

all_paths = get_all_paths(dag, sources=levels[0])
print(all_paths)

输出:

[[8, 1, 5], [8, 1, 6], [8, 1, 7], [8, 3, 4], [8, 3, 5], [8, 3, 7], 
 [9, 1, 5], [9, 1, 6], [9, 1, 7], [9, 2, 4], [9, 2, 5], [9, 2, 6], 
 [10, 0, 4], [10, 0, 6], [10, 0, 7], [10, 2, 4], [10, 2, 5], 
 [10, 2, 6], [10, 3, 4], [10, 3, 5], [10, 3, 7]]

%timeit 24.6 µs ± 577 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

但是,当图很大或有很多起始节点时,我的代码变得很慢。 众所周知,在 DAG G(V,E)中找到所有路径的 DFS 时间复杂度是O(V+E) 所以我的方法的时间复杂度为O(m(V+E)) ,其中m是起始节点的数量。 在我的代码中,每个节点都被访问了m次,但我觉得每个节点仍然只能访问一次,特别是考虑到levels并且我没有使用它。 也许 BFS 可以,但我不确定如何编写它。 有什么建议么?

编辑

以下是答案的一些基准测试

我已经根据@chepner 的评论调整了我的 BFS 代码。

def get_all_paths(dag, sources):

    def dfs(data, path, paths=None):
        if paths is None:
            paths = []
        prev = path[-1]
        if data[prev]:
            for val in data[prev]:
                new_path = path + [val]
                paths = dfs(data, new_path, paths)
        else:
            paths.append(path[1:])

        return paths

    dag.append(sources)
    
    return dfs(data=dag, path=[len(dag)-1], paths=[])

%timeit get_all_paths(dag, sources=levels[0])

输出:

9.73 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

测试@aminrd 的回答:

from collections import defaultdict

def aminrd(dag, levels):
    paths = defaultdict(list)
    levels_reversed = levels[::-1]

    for node in levels_reversed[0]:
        paths[node] = [[node]]

    for lvl in levels_reversed[1:]:
        for src in lvl:
            for dst in dag[src-1]:
                paths[src] += [[src] + p for p in paths[dst]]

    result = []
    for lvl_0 in levels[0]:
        result += paths[lvl_0]
        
%timeit aminrd(dag, levels)

输出

10.7 µs ± 164 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

在单个增广图中寻找路径。 如果您的起始节点的设置是S ,添加一个新的开始节点S0和边缘(S0, s)每个sS 单个 DFS 完成后,您可以简单地从每条路径中删除S0 ,从而保留原始图中的路径。

这可能会减少多次运行dfs造成的一些重复,但不会解决不可避免的事实,即您的运行时间与要找到的路径数量成正比。

通过从最后一层的节点开始,您可以创建一个散列,以便您首先从叶子开始构建,然后逐层进行合成。 我还猜您在问题中的dag与您发布的数字不完全相同:

from collections import defaultdict
dag = [[5, 4, 6, 7], [5, 6, 7], [4, 5, 6], [4, 5, 7],
       [], [], [], [1, 3], [1, 2], [0, 2, 3]]
levels = [[8, 9, 10], [0, 1, 2, 3], [4, 5, 6, 7]]


paths = defaultdict(list)
levels_reversed = levels[::-1]

for node in levels_reversed[0]:
    paths[node] = [[node]]

for lvl in levels_reversed[1:]:
    for src in lvl:
        for dst in dag[src-1]:
            paths[src] += [[src] + p for p in paths[dst]]

result = []
for lvl_0 in levels[0]:
    result += paths[lvl_0]

结果:

[[8, 1, 5], [8, 1, 4], [8, 1, 6], [8, 1, 7],
 [8, 3, 4], [8, 3, 5], [8, 3, 6], [9, 1, 5],
 [9, 1, 4], [9, 1, 6], [9, 1, 7], [9, 2, 5],
 [9, 2, 6], [9, 2, 7], [10, 2, 5], [10, 2, 6],
 [10, 2, 7], [10, 3, 4], [10, 3, 5], [10, 3, 6]]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM