[英]Fastest way to convert an undirected cyclic graph (UCG) to a directed acyclic graph (DAG) in Python?
[英]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]]
由于我有 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)
每个s
的S
。 单个 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.