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.
- data/README +335 -0
- data/examples/graph_self.rb +54 -0
- data/examples/module_graph.jpg +0 -0
- data/examples/module_graph.rb +12 -0
- data/examples/self_graph.jpg +0 -0
- data/examples/visualize.jpg +0 -0
- data/examples/visualize.rb +8 -0
- data/install.rb +49 -0
- data/lib/gratr.rb +42 -0
- data/lib/gratr/adjacency_graph.rb +230 -0
- data/lib/gratr/base.rb +34 -0
- data/lib/gratr/biconnected.rb +116 -0
- data/lib/gratr/chinese_postman.rb +123 -0
- data/lib/gratr/common.rb +74 -0
- data/lib/gratr/comparability.rb +92 -0
- data/lib/gratr/digraph.rb +115 -0
- data/lib/gratr/digraph_distance.rb +185 -0
- data/lib/gratr/dot.rb +90 -0
- data/lib/gratr/edge.rb +145 -0
- data/lib/gratr/graph.rb +314 -0
- data/lib/gratr/graph_api.rb +82 -0
- data/lib/gratr/import.rb +44 -0
- data/lib/gratr/labels.rb +103 -0
- data/lib/gratr/maximum_flow.rb +107 -0
- data/lib/gratr/rdot.rb +332 -0
- data/lib/gratr/search.rb +422 -0
- data/lib/gratr/strong_components.rb +127 -0
- data/lib/gratr/undirected_graph.rb +153 -0
- data/lib/gratr/version.rb +6 -0
- data/lib/priority-queue/benchmark/dijkstra.rb +171 -0
- data/lib/priority-queue/compare_comments.rb +49 -0
- data/lib/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
- data/lib/priority-queue/lib/priority_queue.rb +14 -0
- data/lib/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
- data/lib/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
- data/lib/priority-queue/lib/priority_queue/ruby_priority_queue.rb +525 -0
- data/lib/priority-queue/setup.rb +1551 -0
- data/lib/priority-queue/test/priority_queue_test.rb +371 -0
- data/tests/TestBiconnected.rb +53 -0
- data/tests/TestChinesePostman.rb +53 -0
- data/tests/TestComplement.rb +54 -0
- data/tests/TestDigraph.rb +333 -0
- data/tests/TestDigraphDistance.rb +138 -0
- data/tests/TestDot.rb +75 -0
- data/tests/TestEdge.rb +171 -0
- data/tests/TestInspection.rb +57 -0
- data/tests/TestMultiEdge.rb +57 -0
- data/tests/TestNeighborhood.rb +64 -0
- data/tests/TestProperties.rb +160 -0
- data/tests/TestSearch.rb +277 -0
- data/tests/TestStrongComponents.rb +85 -0
- data/tests/TestTriagulated.rb +137 -0
- data/tests/TestUndirectedGraph.rb +219 -0
- metadata +152 -0
data/lib/gratr/search.rb
ADDED
@@ -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
|