gratr19 0.4.4

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