[英]How to detect a cycle in a directed graph with Python?
I have some input like: [('A', 'B'),('C', 'D'),('D', 'C'),('C', 'D')]
.我有一些输入,如:
[('A', 'B'),('C', 'D'),('D', 'C'),('C', 'D')]
。 I want to look for if the existence of a cycle in a directed graph represented by this edgeList.我想在这个 edgeList 表示的有向图中寻找循环是否存在。
I read a discussion: https://www.geeksforgeeks.org/detect-cycle-in-a-graph/ , however it has some errors when the case is:我读了一个讨论: https : //www.geeksforgeeks.org/detect-cycle-in-a-graph/ ,但是当情况是这样时它有一些错误:
g = Graph(3)
g.addEdge('A', 'B')
g.addEdge('B', 'C')
g.addEdge('C', 'A')
Its result is 'Graph has no cycle'.它的结果是“图没有循环”。 This is clearly wrong.
这显然是错误的。 Can you help me to solve this problem?
你能帮我解决这个问题吗?
Using the networkx library, we can use the simple_cycles
function to find all simple cycles of a directed Graph.使用networkx库,我们可以使用
simple_cycles
函数查找有向图的所有简单循环。
Example Code:示例代码:
import networkx as nx
edges = [('A', 'B'),('C', 'D'),('D', 'C'),('C', 'D')]
G = nx.DiGraph(edges)
for cycle in nx.simple_cycles(G):
print(cycle)
G = nx.DiGraph()
G.add_edge('A', 'B')
G.add_edge('B', 'C')
G.add_edge('C', 'A')
for cycle in nx.simple_cycles(G):
print(cycle)
Output:输出:
['D', 'C']
['B', 'C', 'A']
The issue is the example given at [1]: https://www.geeksforgeeks.org/detect-cycle-in-a-graph/ works for integers only because they use the range()
function to create a list of nodes,see the line问题是 [1] 中给出的示例: https ://www.geeksforgeeks.org/detect-cycle-in-a-graph/ 仅适用于整数,因为它们使用
range()
函数来创建节点列表,看线
for node in range(self.V):
That makes the assumption that not only will all the nodes be integers but also that they will be a contiguous set ie [0,1,2,3]
is okay but [0,3,10]
is not.这使得假设不仅所有节点都是整数,而且它们将是一个连续的集合,即
[0,1,2,3]
是可以的,但[0,3,10]
不是。
You can fix the example if you like to work with any nodes by swapping the line given above with如果您想使用任何节点,可以通过将上面给出的行与
for node in self.graph.keys():
which will loop through all the nodes instead of a range of numbers :)这将遍历所有节点而不是一系列数字:)
My own implementation (non-recursive so without cycle length limit):我自己的实现(非递归所以没有循环长度限制):
from collections import defaultdict
def has_cycle(graph):
try:
next(_iter_cycles(graph))
except StopIteration:
return False
return True
def _iter_cycles(edges):
"""Iterate over simple cycles in the directed graph."""
if isinstance(edges, dict):
graph = edges
else:
graph = defaultdict(set)
for x, y in edges:
graph[x].add(y)
SEP = object()
checked_nodes = set() # already checked nodes
for start_node in graph:
if start_node in checked_nodes:
continue
nodes_left = [start_node]
path = [] # current path from start_node
node_idx = {} # {node: path.index(node)}
while nodes_left:
node = nodes_left.pop()
if node is SEP:
checked_node = path.pop()
del node_idx[checked_node]
checked_nodes.add(checked_node)
continue
if node in checked_nodes:
continue
if node in node_idx:
cycle_path = path[node_idx[node]:]
cycle_path.append(node)
yield cycle_path
continue
next_nodes = graph.get(node)
if not next_nodes:
checked_nodes.add(node)
continue
node_idx[node] = len(path)
path.append(node)
nodes_left.append(SEP)
nodes_left.extend(next_nodes)
assert not has_cycle({0: [1, 2], 1: [3, 4], 5: [6, 7]})
assert has_cycle([(0, 1), (1, 0), (1, 2), (2, 1)])
def assert_cycles(graph, expected):
detected = sorted(_iter_cycles(graph))
if detected != expected:
raise Exception('expected cycles:\n{}\ndetected cycles:\n{}'.format(expected, detected))
assert_cycles([('A', 'B'),('C', 'D'),('D', 'C'),('C', 'D')], [['C', 'D', 'C']])
assert_cycles([('A', 'B'),('B', 'A'),('B', 'C'),('C', 'B')], [['A', 'B', 'A'], ['B', 'C', 'B']])
assert_cycles({1: [2, 3], 2: [3, 4]}, [])
assert_cycles([(1, 2), (1, 3), (2, 3), (2, 4)], [])
assert_cycles({1: [2, 4], 2: [3, 4], 3: [1]}, [[1, 2, 3, 1]])
assert_cycles([(1, 2), (1, 4), (2, 3), (2, 4), (3, 1)], [[1, 2, 3, 1]])
assert_cycles({0: [1, 2], 2: [3], 3: [4], 4: [2]}, [[2, 3, 4, 2]])
assert_cycles([(0, 1), (0, 2), (2, 3), (3, 4), (4, 2)], [[2, 3, 4, 2]])
assert_cycles({1: [2], 3: [4], 4: [5], 5: [3]}, [[3, 4, 5, 3]])
assert_cycles([(1, 2), (3, 4), (4, 5), (5, 3)], [[3, 4, 5, 3]])
assert_cycles({0: [], 1: []}, [])
assert_cycles([], [])
assert_cycles({0: [1, 2], 1: [3, 4], 5: [6, 7]}, [])
assert_cycles([(0, 1), (0, 2), (1, 3), (1, 4), (5, 6), (5, 7)], [])
assert_cycles({0: [1], 1: [0, 2], 2: [1]}, [[0, 1, 0], [1, 2, 1]])
assert_cycles([(0, 1), (1, 0), (1, 2), (2, 1)], [[0, 1, 0], [1, 2, 1]])
EDIT:编辑:
I found that while has_cycle
seems to be correct, the _iter_cycles
does not iterate over all cycles!我发现虽然
has_cycle
似乎是正确的,但_iter_cycles
并没有遍历所有周期!
Example in which _iter_cycles
does not find all cycles: _iter_cycles
未找到所有循环的示例:
assert_cycles([
(0, 1), (1, 2), (2, 0), # Cycle 0-1-2
(0, 2), (2, 0), # Cycle 0-2
(0, 1), (1, 4), (4, 0), # Cycle 0-1-4
],
[
[0, 1, 2, 0], # Not found (in Python 3.7)!
[0, 1, 4, 0],
[0, 2, 0],
]
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.