lib/rgl/bipartite.rb
require 'rgl/base'
require 'rgl/traversal'
module RGL
module Graph
# Separates graph's vertices into two disjoint sets so that every edge of the graph connects vertices from different
# sets. If it's possible, the graph is bipartite.
#
# Returns an array of two disjoint vertices sets (represented as arrays) if the graph is bipartite. Otherwise,
# returns nil.
# @return [Array]
def bipartite_sets
raise NotUndirectedError.new('bipartite sets can only be found for an undirected graph') if directed?
bfs = BipartiteBFSIterator.new(self)
# if necessary, we start BFS from each vertex to make sure
# that all connected components of the graph are processed
each_vertex do |u|
next if bfs.finished_vertex?(u)
bfs.reset_start(u)
bfs.move_forward_until { bfs.found_odd_cycle }
return if bfs.found_odd_cycle
end
bfs.bipartite_sets_map.inject([[], []]) do |sets, (vertex, set)|
sets[set] << vertex
sets
end
end
# Returns true if the graph is bipartite. Otherwise returns false.
#
def bipartite?
!bipartite_sets.nil?
end
end # module Graph
class BipartiteBFSIterator < BFSIterator
attr_reader :bipartite_sets_map, :found_odd_cycle
def reset
super
@bipartite_sets_map = {}
@found_odd_cycle = false
end
def set_to_begin
super
@bipartite_sets_map[@start_vertex] = 0
end
def reset_start(new_start)
@start_vertex = new_start
set_to_begin
end
def handle_tree_edge(u, v)
@bipartite_sets_map[v] = (@bipartite_sets_map[u] + 1) % 2 unless u.nil? # put v into the other set
end
def handle_back_edge(u, v)
verify_odd_cycle(u, v)
end
def handle_forward_edge(u, v)
verify_odd_cycle(u, v)
end
private
def verify_odd_cycle(u, v)
u_set = @bipartite_sets_map[u]
@found_odd_cycle = true if u_set && u_set == @bipartite_sets_map[v]
end
end # class BipartiteBFSIterator
end # module RGL