gratr19 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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