-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjohnson.py
114 lines (98 loc) · 3.92 KB
/
johnson.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# copied from https://github.com/qpwo/python-simple-cycles
# A dependency-free version of networkx's implementation of Johnson's cycle finding algorithm
# Original implementation: https://github.com/networkx/networkx/blob/master/networkx/algorithms/cycles.py#L109
# Original paper: Donald B Johnson. "Finding all the elementary circuits of a directed graph." SIAM Journal on Computing. 1975.
from collections import defaultdict
def simple_cycles(G):
# Yield every elementary cycle in python graph G exactly once
# Expects a dictionary mapping from vertices to iterables of vertices
def _unblock(thisnode, blocked, B):
stack = set([thisnode])
while stack:
node = stack.pop()
if node in blocked:
blocked.remove(node)
stack.update(B[node])
B[node].clear()
G = {v: set(nbrs) for (v,nbrs) in G.items()} # make a copy of the graph
sccs = strongly_connected_components(G)
while sccs:
scc = sccs.pop()
startnode = scc.pop()
path=[startnode]
blocked = set()
closed = set()
blocked.add(startnode)
B = defaultdict(set)
stack = [ (startnode,list(G[startnode])) ]
while stack:
thisnode, nbrs = stack[-1]
if nbrs:
nextnode = nbrs.pop()
if nextnode == startnode:
yield path[:]
closed.update(path)
elif nextnode not in blocked:
path.append(nextnode)
stack.append( (nextnode,list(G[nextnode])) )
closed.discard(nextnode)
blocked.add(nextnode)
continue
if not nbrs:
if thisnode in closed:
_unblock(thisnode,blocked,B)
else:
for nbr in G[thisnode]:
if thisnode not in B[nbr]:
B[nbr].add(thisnode)
stack.pop()
path.pop()
remove_node(G, startnode)
H = subgraph(G, set(scc))
sccs.extend(strongly_connected_components(H))
def strongly_connected_components(graph):
# Tarjan's algorithm for finding SCC's
# Robert Tarjan. "Depth-first search and linear graph algorithms." SIAM journal on computing. 1972.
# Code by Dries Verdegem, November 2012
# Downloaded from http://www.logarithmic.net/pfh/blog/01208083168
index_counter = [0]
stack = []
lowlink = {}
index = {}
result = []
def _strong_connect(node):
index[node] = index_counter[0]
lowlink[node] = index_counter[0]
index_counter[0] += 1
stack.append(node)
successors = graph[node]
for successor in successors:
if successor not in index:
_strong_connect(successor)
lowlink[node] = min(lowlink[node],lowlink[successor])
elif successor in stack:
lowlink[node] = min(lowlink[node],index[successor])
if lowlink[node] == index[node]:
connected_component = []
while True:
successor = stack.pop()
connected_component.append(successor)
if successor == node: break
result.append(connected_component[:])
for node in graph:
if node not in index:
_strong_connect(node)
return result
def remove_node(G, target):
# Completely remove a node from the graph
# Expects values of G to be sets
del G[target]
for nbrs in G.values():
nbrs.discard(target)
def subgraph(G, vertices):
# Get the subgraph of G induced by set vertices
# Expects values of G to be sets
return {v: G[v] & vertices for v in vertices}
##example:
#graph = {0: [7, 3, 5], 1: [2], 2: [7, 1], 3: [0, 5], 4: [6, 8], 5: [0, 3, 7], 6: [4, 8], 7: [0, 2, 5, 8], 8: [4, 6, 7]}
#print(tuple(simple_cycles(graph)))