-
Notifications
You must be signed in to change notification settings - Fork 355
/
Copy pathtarjan.rb
175 lines (152 loc) · 3.76 KB
/
tarjan.rb
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
class Node
attr_accessor :name, :idx, :lowlink, :on_stack
def initialize(name)
@name = name
end
def ==(other)
if other.class == Node
other.name == self.name
elsif other.class == String
other == self.name
else
false
end
end
def to_s
@name
end
end
class Edge
attr_accessor :from, :to
def initialize(node_from, node_to)
@from, @to = node_from, node_to
end
def ==(other)
self.from == other.from && self.to == other.to
end
def to_s
"(#{from.to_s} -> #{to.to_s})"
end
end
class Graph
attr_accessor :nodes, :edges, :adjmt
def initialize(load_dict = {})
@nodes = []
@edges = []
@adjmt = {}
if load_dict.size > 0 && load_dict.class == Hash
load_dict.each do |v, edges|
node_from = self.add_node(v)
adjmt[node_from] = []
edges.each do |w|
node_to = self.add_node(w)
adjmt[node_from] << node_to
self.add_edge(v, w)
end
end
end
end
def add_node(node_name)
if nodes.index(node_name)
return nodes[nodes.index(node_name)]
else
node = Node.new(node_name)
nodes << node
return node
end
end
def add_edge(node_name_from, node_name_to)
node_from = nodes[nodes.index(node_name_from)]
node_to = nodes[nodes.index(node_name_to)]
edges << Edge.new(node_from, node_to)
end
end
class Tarjan
attr_accessor :graph, :idx, :stack, :sccs
def initialize(graph)
if graph.class == Graph
@graph = graph
elsif graph.class == Hash
@graph = Graph.new(graph)
end
if graph != nil
@idx = 0
@stack = []
# Runs Tarjan
# Set all node index to None
self.graph.nodes.each do |v|
v.idx = nil
end
@sccs = []
self.graph.nodes.each do |v|
if v.idx == nil
self.strongconnect(v, sccs)
end
end
end
end
def strongconnect(v, sccs)
# Set the depth index for v to the smallest unused index
v.idx = self.idx
v.lowlink = self.idx
self.idx += 1
stack << v
v.on_stack = true
if graph.adjmt[v] != nil
# Consider successors of v
graph.adjmt[v].each do |w|
if w.idx == nil
# Successor w has not yet been visited; recurse on it
self.strongconnect(w, sccs)
v.lowlink = [v.lowlink, w.lowlink].min
elsif w.on_stack
# Successor w is in stack S and hence in the current SCC
# If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored
# Note: The next line may look odd - but is correct.
# It says w.index not w.lowlink; that is deliberate and from the original paper
v.lowlink = [v.lowlink, w.idx].min
end
end
end
# If v is a root node, pop the stack and generate an SCC
if v.lowlink == v.idx
# start a new strongly connected component
scc = []
while true
w = stack.pop()
w.on_stack = false
scc << w
if w == v
break
end
end
sccs << scc
end
end
end
# Graph from https://en.wikipedia.org/wiki/File:Scc.png
example = {
'A' => ['B'],
'B' => ['C', 'E', 'F'],
'C' => ['D', 'G'],
'D' => ['C', 'H'],
'E' => ['A', 'F'],
'F' => ['G'],
'G' => ['F'],
'H' => ['D', 'G']
}
g = Tarjan.new(example)
print g.sccs.map{|scc| scc.map{|v| v.to_s}}.to_s + "\n"
# Graph from https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm#/media/File:Tarjan%27s_Algorithm_Animation.gif
example = {
'A' => ['E'],
'B' => ['A'],
'C' => ['B', 'D'],
'D' => ['C'],
'E' => ['B'],
'F' => ['B', 'E', 'G'],
'G' => ['F', 'C'],
'H' => ['G', 'H', 'D']
}
g = Tarjan.new(example)
print g.sccs.map{|scc| scc.map{|v| v.to_s}}.to_s + "\n"