lib/rgl/traversal.rb
# traversal.rb
#
require 'rgl/base'
require 'rgl/graph_visitor'
require 'rgl/graph_iterator'
module RGL
# File +traversal.rb+ defines the basic graph traversal algorithm for DFS and
# BFS search. They are implemented as a {GraphIterator}, which is a +Stream+
# of vertices of a given graph. The streams are not reversable.
#
# Beside being an iterator in the sense of the Stream mixin, {BFSIterator} and
# {DFSIterator} follow the
# {https://www.boost.org/libs/graph/doc/visitor_concepts.html BGL Visitor
# Concepts} in a slightly modified fashion (especially for the
# {DFSIterator}).
#
# A {BFSIterator} can be used to traverse a graph from a given start vertex in
# breath first search order. Since the iterator also mixins the {GraphVisitor},
# it provides all event points defined there.
#
# The vertices which are not yet visited are held in the queue +@waiting+.
# During the traversal, vertices are *colored* using the colors :GRAY
# (when waiting) and :BLACK when finished. All other vertices are :WHITE.
#
# For more see the BGL {BFS Visitor
# Concept}[https://www.boost.org/libs/graph/doc/BFSVisitor.html].
#
# See the implementation of {Graph#bfs_search_tree_from} for an example usage.
#
# @see Graph#bfs_iterator
# @see Graph#bfs_search_tree_from
class BFSIterator
include GraphIterator
include GraphVisitor
# @return the vertex where the search starts
attr_accessor :start_vertex
# Create a new {BFSIterator} on _graph_, starting at vertex _start_.
#
def initialize(graph, start = graph.detect { |x| true })
super(graph)
@start_vertex = start
set_to_begin
end
# Returns true if +@color_map+ has only one entry (for the start vertex).
#
def at_beginning?
@color_map.size == 1
end
# Returns true if @waiting is empty.
#
def at_end?
@waiting.empty?
end
# Reset the iterator to the initial state (i.e. at_beginning? == true).
#
def set_to_begin
# Reset color_map
@color_map = Hash.new(:WHITE)
color_map[@start_vertex] = :GRAY
@waiting = [@start_vertex] # a queue
handle_tree_edge(nil, @start_vertex) # discovers start vertex
self
end
# @private
def basic_forward
u = next_vertex
handle_examine_vertex(u)
graph.each_adjacent(u) do |v|
handle_examine_edge(u, v)
if follow_edge?(u, v) # (u,v) is a tree edge
handle_tree_edge(u, v) # also discovers v
color_map[v] = :GRAY # color of v was :WHITE
@waiting.push(v)
else # (u,v) is a non tree edge
if color_map[v] == :GRAY
handle_back_edge(u, v) # (u,v) has gray target
else
handle_forward_edge(u, v) # (u,v) has black target
end
end
end
color_map[u] = :BLACK
handle_finish_vertex(u) # finish vertex
u
end
protected
def next_vertex
# waiting is a queue
@waiting.shift
end
end # class BFSIterator
module Graph
# @return [BFSIterator] starting at vertex _v_.
def bfs_iterator(v = self.detect { |x| true })
BFSIterator.new(self, v)
end
# This method uses the +tree_edge_event+ of BFSIterator
# to record all tree edges of the search tree in the result.
#
# @return [DirectedAdjacencyGraph] which represents a BFS search tree starting at _v_.
def bfs_search_tree_from(v)
unless defined?(DirectedAdjacencyGraph)
require 'rgl/adjacency'
end
bfs = bfs_iterator(v)
tree = DirectedAdjacencyGraph.new
bfs.set_tree_edge_event_handler do |from, to|
tree.add_edge(from, to)
end
bfs.set_to_end # does the search
tree
end
end # module Graph
# Iterator for a depth first search, starting at a given vertex. The only
# difference from {BFSIterator} is that +@waiting+ is a stack, instead of a queue.
#
# Note that this is different from {DFSVisitor}, which is used in the recursive
# version for depth first search (see {Graph#depth_first_search}).
#
# @see Graph#dfs_iterator
class DFSIterator < BFSIterator
def next_vertex
# waiting is a stack
@waiting.pop
end
end # class DFSIterator
# A DFSVisitor is needed by the {Graph#depth_first_search} and
# {Graph#depth_first_visit} methods of a graph. Besides the eventpoints of
# {GraphVisitor}, it provides an additional eventpoint +start_vertex+, which is
# called when a +depth_first_search+ starts a new subtree of the depth first
# forest that is defined by the search.
#
# @see DFSIterator
class DFSVisitor
include GraphVisitor
def_event_handler 'start_vertex'
end # class DFSVisitor
module Graph
# @return [DFSIterator] staring at vertex _v_.
def dfs_iterator(v = self.detect { |x| true })
DFSIterator.new(self, v)
end
# Do a recursive DFS search on the whole graph. If a block is passed, it is
# called on each +finish_vertex+ event. See {Graph#strongly_connected_components}
# for an example usage.
#
# Note that this traversal does not garantee, that roots are at the top of
# each spanning subtree induced by the DFS search on a directed graph (see
# also the discussion in issue #20[https://github.com/monora/rgl/issues/20]).
# @see DFSVisitor
def depth_first_search(vis = DFSVisitor.new(self), &b)
each_vertex do |u|
unless vis.finished_vertex?(u)
vis.handle_start_vertex(u)
depth_first_visit(u, vis, &b)
end
end
end
# Start a depth first search at vertex _u_. The block _b_ is called on
# each +finish_vertex+ event.
# @see DFSVisitor
def depth_first_visit(u, vis = DFSVisitor.new(self), &b)
vis.color_map[u] = :GRAY
vis.handle_examine_vertex(u)
each_adjacent(u) do |v|
vis.handle_examine_edge(u, v)
if vis.follow_edge?(u, v) # (u,v) is a tree edge
vis.handle_tree_edge(u, v) # also discovers v
# color of v was :WHITE. Will be marked :GRAY in recursion
depth_first_visit(v, vis, &b)
else # (u,v) is a non tree edge
if vis.color_map[v] == :GRAY
vis.handle_back_edge(u, v) # (u,v) has gray target
else
vis.handle_forward_edge(u, v) # (u,v) is a cross or forward edge
end
end
end
vis.color_map[u] = :BLACK
vis.handle_finish_vertex(u) # finish vertex
b.call(u)
end
end # module Graph
end # module RGL