有没有一种有效的算法来检测有向图中的循环?
我有一个有向图,表示需要执行的作业计划,作业是一个节点,依赖项是一个边。我需要检测这个图中导致循环依赖关系的循环的错误情况。
有没有一种有效的算法来检测有向图中的循环?
我有一个有向图,表示需要执行的作业计划,作业是一个节点,依赖项是一个边。我需要检测这个图中导致循环依赖关系的循环的错误情况。
当前回答
https://mathoverflow.net/questions/16393/finding-a-cycle-of-fixed-length我最喜欢这个解决方案,特别是4个长度:)
物理向导还说你必须做O(V^2)。我相信我们只需要O(V)/O(V+E)。 如果图是连通的,那么DFS将访问所有节点。如果图有连通的子图,那么每次我们在这个子图的顶点上运行DFS时,我们都会找到连通的顶点,并且不必为下一次运行DFS考虑这些。因此,对每个顶点运行的可能性是不正确的。
其他回答
最简单的方法是对图进行深度优先遍历(DFT)。
如果图有n个顶点,这是一个O(n)时间复杂度算法。因为你可能必须从每个顶点开始进行DFT,所以总复杂度变成O(n^2)。
您必须维护一个包含当前深度第一次遍历的所有顶点的堆栈,其第一个元素是根节点。如果在DFT期间遇到一个元素已经在堆栈中,那么就有一个循环。
There is no algorithm which can find all the cycles in a directed graph in polynomial time. Suppose, the directed graph has n nodes and every pair of the nodes has connections to each other which means you have a complete graph. So any non-empty subset of these n nodes indicates a cycle and there are 2^n-1 number of such subsets. So no polynomial time algorithm exists. So suppose you have an efficient (non-stupid) algorithm which can tell you the number of directed cycles in a graph, you can first find the strong connected components, then applying your algorithm on these connected components. Since cycles only exist within the components and not between them.
根据Cormen et al., Introduction to Algorithms (CLRS)引理22.11:
有向图G是无环的当且仅当深度优先搜索G没有得到后边。
在几个回答中已经提到了这一点;在这里,我还将提供一个基于CLRS第22章的代码示例。示例图如下所示。
CLRS深度优先搜索的伪代码如下:
在CLRS图22.4中的示例中,图由两棵DFS树组成:一棵由节点u、v、x和y组成,另一棵由节点w和z组成。每棵树都包含一条后边:一条从x到v,另一条从z到z(一个自循环)。
关键的实现是,在DFS-VISIT函数中,当在u的邻居v上迭代时,遇到一个带有灰色的节点时,就会遇到后边缘。
下面的Python代码是CLRS伪代码的改编,添加了一个if子句,用于检测周期:
import collections
class Graph(object):
def __init__(self, edges):
self.edges = edges
self.adj = Graph._build_adjacency_list(edges)
@staticmethod
def _build_adjacency_list(edges):
adj = collections.defaultdict(list)
for edge in edges:
adj[edge[0]].append(edge[1])
adj[edge[1]] # side effect only
return adj
def dfs(G):
discovered = set()
finished = set()
for u in G.adj:
if u not in discovered and u not in finished:
discovered, finished = dfs_visit(G, u, discovered, finished)
def dfs_visit(G, u, discovered, finished):
discovered.add(u)
for v in G.adj[u]:
# Detect cycles
if v in discovered:
print(f"Cycle detected: found a back edge from {u} to {v}.")
break
# Recurse into DFS tree
if v not in finished:
dfs_visit(G, v, discovered, finished)
discovered.remove(u)
finished.add(u)
return discovered, finished
if __name__ == "__main__":
G = Graph([
('u', 'v'),
('u', 'x'),
('v', 'y'),
('w', 'y'),
('w', 'z'),
('x', 'v'),
('y', 'x'),
('z', 'z')])
dfs(G)
注意,在本例中,CLRS伪代码中的时间没有被捕获,因为我们只对检测周期感兴趣。还有一些样板代码,用于从边列表构建图的邻接表表示。
当这个脚本执行时,它输出如下:
Cycle detected: found a back edge from x to v.
Cycle detected: found a back edge from z to z.
这些正是CLRS图22.4示例中的后边缘。
如果一个图满足这个性质
|e| > |v| - 1
那么图至少包含一个周期。
我已经在sml(命令式编程)中实现了这个问题。这是大纲。找到所有入度或出度为0的节点。这样的节点不能成为循环的一部分(因此将它们删除)。接下来,从这些节点中删除所有传入或传出边。 递归地将此过程应用于结果图。如果最后你没有剩下任何节点或边,图就没有任何循环,否则就有。