gratr 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/Grater.xcf +0 -0
  2. data/README +328 -0
  3. data/Rakefile +220 -0
  4. data/examples/graph_self.rb +54 -0
  5. data/examples/module_graph.jpg +0 -0
  6. data/examples/module_graph.rb +12 -0
  7. data/examples/self_graph.jpg +0 -0
  8. data/examples/visualize.jpg +0 -0
  9. data/examples/visualize.rb +8 -0
  10. data/install.rb +49 -0
  11. data/lib/gratr.rb +33 -0
  12. data/lib/gratr/adjacency_graph.rb +230 -0
  13. data/lib/gratr/base.rb +34 -0
  14. data/lib/gratr/biconnected.rb +116 -0
  15. data/lib/gratr/chinese_postman.rb +123 -0
  16. data/lib/gratr/common.rb +73 -0
  17. data/lib/gratr/comparability.rb +92 -0
  18. data/lib/gratr/digraph.rb +113 -0
  19. data/lib/gratr/digraph_distance.rb +185 -0
  20. data/lib/gratr/dot.rb +90 -0
  21. data/lib/gratr/edge.rb +145 -0
  22. data/lib/gratr/graph.rb +315 -0
  23. data/lib/gratr/graph_api.rb +82 -0
  24. data/lib/gratr/import.rb +44 -0
  25. data/lib/gratr/labels.rb +103 -0
  26. data/lib/gratr/maximum_flow.rb +107 -0
  27. data/lib/gratr/rdot.rb +326 -0
  28. data/lib/gratr/search.rb +409 -0
  29. data/lib/gratr/strong_components.rb +127 -0
  30. data/lib/gratr/undirected_graph.rb +153 -0
  31. data/tests/TestBiconnected.rb +53 -0
  32. data/tests/TestChinesePostman.rb +53 -0
  33. data/tests/TestComplement.rb +54 -0
  34. data/tests/TestDigraph.rb +333 -0
  35. data/tests/TestDigraphDistance.rb +138 -0
  36. data/tests/TestEdge.rb +171 -0
  37. data/tests/TestInspection.rb +57 -0
  38. data/tests/TestMultiEdge.rb +57 -0
  39. data/tests/TestNeighborhood.rb +64 -0
  40. data/tests/TestProperties.rb +160 -0
  41. data/tests/TestSearch.rb +257 -0
  42. data/tests/TestStrongComponents.rb +85 -0
  43. data/tests/TestTriagulated.rb +137 -0
  44. data/tests/TestUndirectedGraph.rb +219 -0
  45. metadata +92 -0
@@ -0,0 +1,409 @@
1
+ #--
2
+ # Copyright (c) 2006 Shawn Patrick Garbett
3
+ # Copyright (c) 2002,2004,2005 by Horst Duchene
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without modification,
6
+ # are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice(s),
9
+ # this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * Neither the name of the Shawn Garbett nor the names of its contributors
14
+ # may be used to endorse or promote products derived from this software
15
+ # without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ #++
28
+
29
+
30
+ module GRATR
31
+ module Graph
32
+ module Search
33
+
34
+ # Options are mostly callbacks passed in as a hash.
35
+ # The following are valid, anything else is ignored
36
+ # :enter_vertex => Proc Called upon entry of a vertex
37
+ # :exit_vertex => Proc Called upon exit of a vertex
38
+ # :root_vertex => Proc Called when a vertex the a root of a tree
39
+ # :start_vertex => Proc Called for the first vertex of the search
40
+ # :examine_edge => Proc Called when an edge is examined
41
+ # :tree_edge => Proc Called when the edge is a member of the tree
42
+ # :back_edge => Proc Called when the edge is a back edge
43
+ # :forward_edge => Proc Called when the edge is a forward edge
44
+ # :adjacent => Proc that given a vertex returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms
45
+ #
46
+ # :start => Vertex Specifies the vertex to start search from
47
+ #
48
+ # If a &block is specified it defaults to :enter_vertex
49
+ #
50
+ # Returns the list of vertexes as reached by enter_vertex
51
+ # This allows for calls like, g.bfs.each {|v| ...}
52
+ #
53
+ # Can also be called like bfs_examine_edge {|e| ... } or
54
+ # dfs_back_edge {|e| ... } for any of the callbacks
55
+ #
56
+ # A full example usage is as follows:
57
+ #
58
+ # ev = Proc.new {|x| puts "Enter Vertex #{x}"}
59
+ # xv = Proc.new {|x| puts "Exit Vertex #{x}"}
60
+ # sv = Proc.new {|x| puts "Start Vertex #{x}"}
61
+ # ee = Proc.new {|x| puts "Examine Arc #{x}"}
62
+ # te = Proc.new {|x| puts "Tree Arc #{x}"}
63
+ # be = Proc.new {|x| puts "Back Arc #{x}"}
64
+ # fe = Proc.new {|x| puts "Forward Arc #{x}"}
65
+ # Digraph[1,2,2,3,3,4].dfs({
66
+ # :enter_vertex => ev,
67
+ # :exit_vertex => xv,
68
+ # :start_vertex => sv,
69
+ # :examine_edge => ee,
70
+ # :tree_edge => te,
71
+ # :back_edge => be,
72
+ # :forward_edge => fe })
73
+ #
74
+ # Which outputs:
75
+ #
76
+ # Start Vertex 1
77
+ # Enter Vertex 1
78
+ # Examine Arc (1=2)
79
+ # Tree Arc (1=2)
80
+ # Enter Vertex 2
81
+ # Examine Arc (2=3)
82
+ # Tree Arc (2=3)
83
+ # Enter Vertex 3
84
+ # Examine Arc (3=4)
85
+ # Tree Arc (3=4)
86
+ # Enter Vertex 4
87
+ # Examine Arc (1=4)
88
+ # Back Arc (1=4)
89
+ # Exit Vertex 4
90
+ # Exit Vertex 3
91
+ # Exit Vertex 2
92
+ # Exit Vertex 1
93
+ def bfs(options={}, &block) gratr_search_helper(:shift, options, &block); end
94
+
95
+ # See options for bfs method
96
+ def dfs(options={}, &block) gratr_search_helper(:pop, options, &block); end
97
+
98
+ # Routine to compute a spanning forest for the given search method
99
+ # Returns two values, first is a hash of predecessors and second an array of root nodes
100
+ def spanning_forest(start, routine)
101
+ predecessor = {}
102
+ roots = []
103
+ te = Proc.new {|e| predecessor[e.target] = e.source}
104
+ rv = Proc.new {|v| roots << v}
105
+ method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
106
+ [predecessor, roots]
107
+ end
108
+
109
+ # Return the dfs spanning forest for the given start node, see spanning_forest
110
+ def dfs_spanning_forest(start) spanning_forest(start, :dfs); end
111
+
112
+ # Return the bfs spanning forest for the given start node, see spanning_forest
113
+ def bfs_spanning_forest(start) spanning_forest(start, :bfs); end
114
+
115
+ # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph
116
+ # then it will be a spanning tree and contain all vertices. An easier way to tell if it's a spanning tree is to
117
+ # use a spanning_forest call and check if there is a single root node.
118
+ def tree_from_vertex(start, routine)
119
+ predecessor={}
120
+ correct_tree = false
121
+ te = Proc.new {|e| predecessor[e.target] = e.source if correct_tree}
122
+ rv = Proc.new {|v| correct_tree = (v == start)}
123
+ method(routine).call :start => start, :tree_edge => te, :root_vertex => rv
124
+ predecessor
125
+ end
126
+
127
+ # Returns a hash of predecessors for the depth first search tree rooted at the given node
128
+ def dfs_tree_from_vertex(start) tree_from_vertex(start, :dfs); end
129
+
130
+ # Returns a hash of predecessors for the depth first search tree rooted at the given node
131
+ def bfs_tree_from_vertex(start) tree_from_vertex(start, :bfs); end
132
+
133
+ # An inner class used for greater efficiency in lexicograph_bfs
134
+ #
135
+ # Original desgn taken from Golumbic's, "Algorithmic Graph Theory and
136
+ # Perfect Graphs" pg, 87-89
137
+ class LexicographicQueue
138
+
139
+ # Called with the initial values (array)
140
+ def initialize(values)
141
+ @node = Struct.new(:back, :forward, :data)
142
+ @node.class_eval { def hash() @hash; end; @@cnt=0 }
143
+ @set = {}
144
+ @tail = @node.new(nil, nil, Array.new(values))
145
+ @tail.instance_eval { @hash = (@@cnt+=1) }
146
+ values.each {|a| @set[a] = @tail}
147
+ end
148
+
149
+ # Pop an entry with maximum lexical value from queue
150
+ def pop()
151
+ return nil unless @tail
152
+ value = @tail[:data].pop
153
+ @tail = @tail[:forward] while @tail and @tail[:data].size == 0
154
+ @set.delete(value); value
155
+ end
156
+
157
+ # Increase lexical value of given values (array)
158
+ def add_lexeme(values)
159
+ fix = {}
160
+ values.select {|v| @set[v]}.each do |w|
161
+ sw = @set[w]
162
+ if fix[sw]
163
+ s_prime = sw[:back]
164
+ else
165
+ s_prime = @node.new(sw[:back], sw, [])
166
+ s_prime.instance_eval { @hash = (@@cnt+=1) }
167
+ @tail = s_prime if @tail == sw
168
+ sw[:back][:forward] = s_prime if sw[:back]
169
+ sw[:back] = s_prime
170
+ fix[sw] = true
171
+ end
172
+ s_prime[:data] << w
173
+ sw[:data].delete(w)
174
+ @set[w] = s_prime
175
+ end
176
+ fix.keys.select {|n| n[:data].size == 0}.each do |e|
177
+ e[:forward][:back] = e[:back] if e[:forward]
178
+ e[:back][:forward] = e[:forward] if e[:back]
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ # Lexicographic breadth-first search, the usual queue of vertices
185
+ # is replaced by a queue of unordered subsets of the vertices,
186
+ # which is sometimes refined but never reordered.
187
+ #
188
+ # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic
189
+ # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283
190
+ # MR53 #12077
191
+ #
192
+ # Implementation taken from Golumbic's, "Algorithmic Graph Theory and
193
+ # Perfect Graphs" pg, 84-90
194
+ def lexicograph_bfs(&block)
195
+ lex_q = GRATR::Graph::Search::LexicographicQueue.new(vertices)
196
+ result = []
197
+ num_vertices.times do
198
+ v = lex_q.pop
199
+ result.unshift(v)
200
+ lex_q.add_lexeme(adjacent(v))
201
+ end
202
+ result.each {|r| block.call(r)} if block
203
+ result
204
+ end
205
+
206
+
207
+ # A* Heuristic best first search
208
+ #
209
+ # start is the starting vertex for the search
210
+ #
211
+ # func is a Proc that when passed a vertex returns the heuristic
212
+ # weight of sending the path through that node. It must always
213
+ # be equal to or less than the true cost
214
+ #
215
+ # options are mostly callbacks passed in as a hash, the default block is
216
+ # :discover_vertex and weight is assumed to be the label for the Arc.
217
+ # The following options are valid, anything else is ignored.
218
+ #
219
+ # * :weight => can be a Proc, or anything else is accessed using the [] for the
220
+ # the label or it defaults to using
221
+ # the value stored in the label for the Arc. If it is a Proc it will
222
+ # pass the edge to the proc and use the resulting value.
223
+ # * :discover_vertex => Proc invoked when a vertex is first discovered
224
+ # and is added to the open list.
225
+ # * :examine_vertex => Proc invoked when a vertex is popped from the
226
+ # queue (i.e., it has the lowest cost on the open list).
227
+ # * :examine_edge => Proc invoked on each out-edge of a vertex
228
+ # immediately after it is examined.
229
+ # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v].
230
+ # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above).
231
+ # * :black_target => Proc invoked when a vertex that is on the closed
232
+ # list is "rediscovered" via a more efficient path, and is re-added
233
+ # to the OPEN list.
234
+ # * :finish_vertex => Proc invoked on a vertex when it is added to the
235
+ # closed list, which happens after all of its out edges have been
236
+ # examined.
237
+ #
238
+ # Returns array of nodes in path, or calls block on all nodes,
239
+ # upon failure returns nil
240
+ #
241
+ # Can also be called like astar_examine_edge {|e| ... } or
242
+ # astar_edge_relaxed {|e| ... } for any of the callbacks
243
+ #
244
+ # The criteria for expanding a vertex on the open list is that it has the
245
+ # lowest f(v) = g(v) + h(v) value of all vertices on open.
246
+ #
247
+ # The time complexity of A* depends on the heuristic. It is exponential
248
+ # in the worst case, but is polynomial when the heuristic function h
249
+ # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h*
250
+ # is the optimal heuristic, i.e. the exact cost to get from x to the goal.
251
+ #
252
+ # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm
253
+ #
254
+ def astar(start, goal, func, options, &block)
255
+ options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end"
256
+ options.instance_eval "def handle_edge(sym,u,v) self[sym].call(#{edge_class}[u,v]) if self[sym]; end"
257
+
258
+ d = { start => 0 }
259
+ f = { start => func.call(start) }
260
+ color = {start => :gray}
261
+ p = Hash.new {|k| p[k] = k}
262
+ queue = [start]
263
+ block.call(start) if block
264
+ until queue.empty?
265
+ u = queue.pop
266
+ options.handle_vertex(:examine_vertex, u)
267
+ adjacent(u).each do |v|
268
+ e = edge_class[u,v]
269
+ options.handle_edge(:examine_edge, u, v)
270
+ w = cost(e, options[:weight])
271
+ raise ArgumentError unless w
272
+ if d[v].nil? or (w + d[u]) < d[v]
273
+ options.handle_edge(:edge_relaxed, u, v)
274
+ d[v] = w + d[u]
275
+ f[v] = d[v] + func.call(u)
276
+ p[v] = u
277
+ unless color[v] == :gray
278
+ options.handle_vertex(:black_target, v) if color[v] == :black
279
+ color[v] = :gray
280
+ options.handle_vertex(:discover_vertex, v)
281
+ queue << v
282
+ block.call(v) if block
283
+ return [start]+queue if v == goal
284
+ end
285
+ else
286
+ options.handle_edge(:edge_not_relaxed, u, v)
287
+ end
288
+ end # adjacent(u)
289
+ color[u] = :black
290
+ options.handle_vertex(:finish_vertex,u)
291
+ end # queue.empty?
292
+ nil # failure, on fall through
293
+ end # astar
294
+
295
+ # Best first has all the same options as astar with func set to h(v) = 0.
296
+ # There is an additional option zero which should be defined to zero
297
+ # for the operation '+' on the objects used in the computation of cost.
298
+ # The parameter zero defaults to 0.
299
+ def best_first(start, goal, options, zero=0, &block)
300
+ func = Proc.new {|v| zero}
301
+ astar(start, goal, func, options, &block)
302
+ end
303
+
304
+ alias_method :pre_search_method_missing, :method_missing # :nodoc:
305
+ def method_missing(sym,*args, &block) # :nodoc:
306
+ m1=/^dfs_(\w+)$/.match(sym.to_s)
307
+ dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1
308
+ m2=/^bfs_(\w+)$/.match(sym.to_s)
309
+ bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2
310
+ pre_search_method_missing(sym, *args, &block) unless m1 or m2
311
+ end
312
+
313
+ private
314
+
315
+ def gratr_search_helper(op, options={}, &block) # :nodoc:
316
+ return nil if size == 0
317
+ result = []
318
+ # Create options hash that handles callbacks
319
+ options = {:enter_vertex => block, :start => to_a[0]}.merge(options)
320
+ options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end"
321
+ options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end"
322
+ # Create waiting list that is a queue or stack depending on op specified.
323
+ # First entry is the start vertex.
324
+ waiting = [options[:start]]
325
+ waiting.instance_eval "def next() #{op.to_s}; end"
326
+ # Create color map with all set to unvisited except for start vertex
327
+ # will be set to waiting
328
+ color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a}
329
+ color_map.merge!(waiting[0] => :waiting)
330
+ options.handle_vertex(:start_vertex, waiting[0])
331
+ options.handle_vertex(:root_vertex, waiting[0])
332
+ # Perform the actual search until nothing is waiting
333
+ until waiting.empty?
334
+ # Loop till the search iterator exhausts the waiting list
335
+ visited_edges={} # This prevents retraversing edges in undirected graphs
336
+ until waiting.empty?
337
+ gratr_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop)
338
+ end
339
+ # Waiting list is exhausted, see if a new root vertex is available
340
+ u=color_map.detect {|key,value| value == :unvisited}
341
+ waiting.push(u[0]) if u
342
+ options.handle_vertex(:root_vertex, u[0]) if u
343
+ end; result
344
+ end
345
+
346
+ def gratr_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc:
347
+ # Get the next waiting vertex in the list
348
+ u = waiting.next
349
+ options.handle_vertex(:enter_vertex,u)
350
+ result << u
351
+ # Examine all adjacent outgoing edges, not previously traversed
352
+ adj_proc = options[:adjacent] || self.method(:adjacent).to_proc
353
+ adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e|
354
+ e = e.reverse unless directed? or e.source == u # Preserves directionality where required
355
+ v = e.target
356
+ options.handle_edge(:examine_edge, e)
357
+ visited_edges[e]=true
358
+ case color_map[v]
359
+ # If it's unvisited it goes into the waiting list
360
+ when :unvisited
361
+ options.handle_edge(:tree_edge, e)
362
+ color_map[v] = :waiting
363
+ waiting.push(v)
364
+ # If it's recursive (i.e. dfs) then call self
365
+ gratr_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive
366
+ when :waiting
367
+ options.handle_edge(:back_edge, e)
368
+ else
369
+ options.handle_edge(:forward_edge, e)
370
+ end
371
+ end
372
+ # Finished with this vertex
373
+ options.handle_vertex(:exit_vertex, u)
374
+ color_map[u] = :visited
375
+ end
376
+
377
+ public
378
+ # Topological Sort Iterator
379
+ #
380
+ # The topological sort algorithm creates a linear ordering of the vertices
381
+ # such that if edge (u,v) appears in the graph, then u comes before v in
382
+ # the ordering. The graph must be a directed acyclic graph (DAG).
383
+ #
384
+ # The iterator can also be applied to undirected graph or to a DG graph
385
+ # which contains a cycle. In this case, the Iterator does not reach all
386
+ # vertices. The implementation of acyclic? and cyclic? uses this fact.
387
+ #
388
+ # Can be called with a block as a standard Ruby iterator, or it can
389
+ # be used directly as it will return the result as an Array
390
+ def topsort(start = nil, &block)
391
+ result = []
392
+ go = true
393
+ back = Proc.new {|e| go = false }
394
+ push = Proc.new {|v| result.unshift(v) if go}
395
+ start ||= vertices[0]
396
+ dfs({:exit_vertex => push, :back_edge => back, :start => start})
397
+ result.each {|v| block.call(v)} if block; result
398
+ end
399
+
400
+ # Returns true if a graph contains no cycles, false otherwise
401
+ def acyclic?() topsort.size == size; end
402
+
403
+ # Returns false if a graph contains no cycles, true otherwise
404
+ def cyclic?() not acyclic?; end
405
+
406
+
407
+ end # Search
408
+ end # Graph
409
+ end # GRATR
@@ -0,0 +1,127 @@
1
+ #--
2
+ # Copyright (c) 2006 Shawn Patrick Garbett
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright notice(s),
8
+ # this list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ # * Neither the name of the Shawn Garbett nor the names of its contributors
13
+ # may be used to endorse or promote products derived from this software
14
+ # without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+ #++
27
+
28
+
29
+ require 'set'
30
+
31
+ module GRATR
32
+ module Graph
33
+ module StrongComponents
34
+ # strong_components computes the strongly connected components
35
+ # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
36
+ # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on
37
+ # Computing, 1(2):146-160, 1972
38
+ #
39
+ # The output of the algorithm is an array of components where is
40
+ # component is an array of vertices
41
+ #
42
+ # A strongly connected component of a directed graph G=(V,E) is a maximal
43
+ # set of vertices U which is in V such that for every pair of
44
+ # vertices u and v in U, we have both a path from u to v
45
+ # and path from v to u. That is to say that u and v are reachable
46
+ # from each other.
47
+ #
48
+ def strong_components
49
+
50
+ dfs_num = 0
51
+ stack = []; result = []; root = {}; comp = {}; number = {}
52
+
53
+ # Enter vertex callback
54
+ enter = Proc.new do |v|
55
+ root[v] = v
56
+ comp[v] = :new
57
+ number[v] = (dfs_num += 1)
58
+ stack.push(v)
59
+ end
60
+
61
+ # Exit vertex callback
62
+ exit = Proc.new do |v|
63
+ adjacent(v).each do |w|
64
+ if comp[w] == :new
65
+ root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w])
66
+ end
67
+ end
68
+ if root[v] == v
69
+ component = []
70
+ begin
71
+ w = stack.pop
72
+ comp[w] = :assigned
73
+ component << w
74
+ end until w == v
75
+ result << component
76
+ end
77
+ end
78
+
79
+ # Execute depth first search
80
+ dfs({:enter_vertex => enter, :exit_vertex => exit}); result
81
+
82
+ end # strong_components
83
+
84
+ # Returns a condensation graph of the strongly connected components
85
+ # Each node is an array of nodes from the original graph
86
+ def condensation
87
+ sc = strong_components
88
+ cg = self.class.new
89
+ map = sc.inject({}) do |a,c|
90
+ c.each {|v| a[v] = c }; a
91
+ end
92
+ sc.each do |c|
93
+ c.each do |v|
94
+ adjacent(v).each {|v| cg.add_edge!(c, map[v]) unless c == map[v]}
95
+ end
96
+ end; cg
97
+ end
98
+
99
+ # Compute transitive closure of a graph. That is any node that is reachable
100
+ # along a path is added as a directed edge.
101
+ def transitive_closure!
102
+ cgtc = condensation.gratr_inner_transitive_closure!
103
+ cgtc.each do |cgv|
104
+ cgtc.adjacent(cgv).each do |adj|
105
+ cgv.each do |u|
106
+ adj.each {|v| add_edge!(u,v)}
107
+ end
108
+ end
109
+ end; self
110
+ end
111
+
112
+ # This returns the transitive closure of a graph. The original graph
113
+ # is not changed.
114
+ def transitive_closure() self.class.new(self).transitive_closure!; end
115
+
116
+ private
117
+ def gratr_inner_transitive_closure! # :nodoc:
118
+ topsort.reverse.each do |u|
119
+ adjacent(u).each do |v|
120
+ adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)}
121
+ end
122
+ end; self
123
+ end
124
+ end # StrongComponens
125
+
126
+ end # Graph
127
+ end # GRATR