mirror of
https://github.com/TheAlgorithms/Ruby
synced 2024-11-16 19:50:00 +01:00
Merge pull request #207 from aparibocci/feature/graph_bfs
Implementing BFS for unweighted graphs
This commit is contained in:
commit
774a964e43
4 changed files with 156 additions and 2 deletions
65
data_structures/graphs/bfs.rb
Normal file
65
data_structures/graphs/bfs.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
require 'set'
|
||||
|
||||
##
|
||||
# This class represents the result of a breadth-first search performed on an unweighted graph.
|
||||
#
|
||||
# It exposes:
|
||||
# - the set of visited nodes
|
||||
# - a hash of distances by node from the search root node
|
||||
# (only for visited nodes, 0 for the search root node);
|
||||
# - a hash of parent nodes by node
|
||||
# (only for visited nodes, nil for the search root node).
|
||||
|
||||
class GraphBfsResult
|
||||
attr_reader :visited
|
||||
attr_reader :parents
|
||||
attr_reader :distances
|
||||
|
||||
def initialize(visited, parents, distances)
|
||||
@visited = visited
|
||||
@parents = parents
|
||||
@distances = distances
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Performs a breadth-first search for the provided graph, starting at the given node.
|
||||
# Returns the search result (see GraphBfsResult).
|
||||
# Nodes are consumed using the provided consumers upon being first seen, or being completely visited
|
||||
# (nothing, by default).
|
||||
#
|
||||
# The algorithm has a time complexity of O(|V| + |E|), where:
|
||||
# - |V| is the number of nodes in the graph;
|
||||
# - |E| is the number of edges in the graph.
|
||||
|
||||
def bfs(graph, start_node, seen_node_consumer: method(:do_nothing_on_node), visited_node_consumer: method(:do_nothing_on_node))
|
||||
seen = Set[]
|
||||
visited = Set[]
|
||||
parents = { start_node => nil }
|
||||
distances = { start_node => 0 }
|
||||
|
||||
seen.add(start_node)
|
||||
seen_node_consumer.call(start_node)
|
||||
q = Queue.new
|
||||
q.push(start_node)
|
||||
until q.empty?
|
||||
node = q.pop
|
||||
for neighbor in graph.neighbors(node)
|
||||
unless seen.include?(neighbor)
|
||||
seen.add(neighbor)
|
||||
distances[neighbor] = distances[node] + 1
|
||||
parents[neighbor] = node
|
||||
seen_node_consumer.call(neighbor)
|
||||
q.push(neighbor)
|
||||
end
|
||||
end
|
||||
visited.add(node)
|
||||
visited_node_consumer.call(node)
|
||||
end
|
||||
|
||||
GraphBfsResult.new(visited, parents, distances)
|
||||
end
|
||||
|
||||
private
|
||||
def do_nothing_on_node(node)
|
||||
end
|
89
data_structures/graphs/bfs_test.rb
Normal file
89
data_structures/graphs/bfs_test.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
require 'minitest/autorun'
|
||||
require_relative 'bfs'
|
||||
require_relative 'unweighted_graph'
|
||||
|
||||
class TestBfs < Minitest::Test
|
||||
def test_bfs_visits_single_graph_node
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
|
||||
bfs_result = bfs(graph, :w)
|
||||
|
||||
assert bfs_result.visited.to_set == [:w].to_set
|
||||
assert bfs_result.parents == {
|
||||
:w => nil
|
||||
}
|
||||
assert bfs_result.distances == {
|
||||
:w => 0
|
||||
}
|
||||
end
|
||||
|
||||
def test_bfs_visits_graph_fully
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v, :w, :x], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
graph.add_edge(:u, :w)
|
||||
graph.add_edge(:w, :x)
|
||||
|
||||
bfs_result = bfs(graph, :u)
|
||||
|
||||
assert bfs_result.visited.to_set == [:u, :v, :w, :x].to_set
|
||||
assert bfs_result.parents == {
|
||||
:u => nil,
|
||||
:v => :u,
|
||||
:w => :u,
|
||||
:x => :w
|
||||
}
|
||||
assert bfs_result.distances == {
|
||||
:u => 0,
|
||||
:v => 1,
|
||||
:w => 1,
|
||||
:x => 2
|
||||
}
|
||||
end
|
||||
|
||||
def test_bfs_visits_graph_partially
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v, :w, :x, :y, :z], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
graph.add_edge(:w, :x)
|
||||
graph.add_edge(:x, :y)
|
||||
graph.add_edge(:y, :z)
|
||||
|
||||
bfs_result = bfs(graph, :x)
|
||||
|
||||
assert bfs_result.visited.to_set == [:w, :x, :y, :z].to_set
|
||||
assert bfs_result.parents == {
|
||||
:w => :x,
|
||||
:x => nil,
|
||||
:y => :x,
|
||||
:z => :y
|
||||
}
|
||||
assert bfs_result.distances == {
|
||||
:w => 1,
|
||||
:x => 0,
|
||||
:y => 1,
|
||||
:z => 2
|
||||
}
|
||||
end
|
||||
|
||||
def test_bfs_visits_with_seen_node_consumer
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
graph.add_edge(:u, :w)
|
||||
|
||||
seen_order = []
|
||||
bfs(graph, :w, seen_node_consumer: ->(node) { seen_order.append(node) })
|
||||
|
||||
assert seen_order == [:w, :u, :v]
|
||||
end
|
||||
|
||||
def test_bfs_visits_with_visited_node_consumer
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v, :w], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
graph.add_edge(:u, :w)
|
||||
|
||||
visited_order = []
|
||||
bfs(graph, :w, visited_node_consumer: ->(node) { visited_order.append(node) })
|
||||
|
||||
assert visited_order == [:w, :u, :v]
|
||||
end
|
||||
end
|
|
@ -72,7 +72,7 @@ class TestUnweightedGraph < Minitest::Test
|
|||
assert graph.neighbors(:v).empty?
|
||||
end
|
||||
|
||||
def test_add_edge_adds_edge_to_directed_unweighted_graph
|
||||
def test_add_edge_adds_edge_to_undirected_unweighted_graph
|
||||
graph = UnweightedGraph.new(nodes: [:u, :v], directed: false)
|
||||
graph.add_edge(:u, :v)
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ class TestWeightedGraph < Minitest::Test
|
|||
assert graph.edges(:v).empty?
|
||||
end
|
||||
|
||||
def test_add_edge_adds_edge_to_directed_weighted_graph
|
||||
def test_add_edge_adds_edge_to_undirected_weighted_graph
|
||||
graph = WeightedGraph.new(nodes: [:u, :v], directed: false)
|
||||
graph.add_edge(:u, :v, 2)
|
||||
|
||||
|
|
Loading…
Reference in a new issue