Implementing unweighted graph BFS

This commit is contained in:
Amos Paribocci 2023-05-23 08:37:39 +02:00
parent af56ad064f
commit 360cba9a61
2 changed files with 123 additions and 0 deletions

View file

@ -0,0 +1,56 @@
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).
#
# 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 = Set[]
visited = Set[]
parents = { start_node => nil }
distances = { start_node => 0 }
seen.add(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
q.push(neighbor)
end
end
visited.add(node)
end
GraphBfsResult.new(visited, parents, distances)
end

View file

@ -0,0 +1,67 @@
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_undirected_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_undirected_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
end