Implementing topological sorting for DAGs

This commit is contained in:
Amos Paribocci 2023-05-23 13:49:52 +02:00
parent af56ad064f
commit 5f0a19ce82
2 changed files with 89 additions and 0 deletions

View file

@ -0,0 +1,37 @@
require 'set'
##
# This class aims to provide topological sorting capabilities for directed acyclic graphs.
#
# Topological sorting runs in O(|V|), where |V| is the number of graph nodes.
class TopologicalSorter
attr_reader :graph
def initialize(graph)
raise ArgumentError, "Topological sort is only applicable to directed graphs!" unless graph.directed
@graph = graph
end
def topological_sort
@sorted_nodes = []
@seen = Set[]
@visited = Set[]
for node in graph.nodes
dfs_visit(node)
end
@sorted_nodes
end
private
def dfs_visit(node)
return if @visited.include?(node)
raise ArgumentError, "Cycle in graph detected on node #{node}!" if @seen.include?(node)
@seen.add(node)
for neighbor in graph.neighbors(node)
dfs_visit(neighbor)
end
@visited.add(node)
@sorted_nodes.unshift(node)
end
end

View file

@ -0,0 +1,52 @@
require 'minitest/autorun'
require_relative 'topological_sort'
require_relative 'unweighted_graph'
class TestTopologicalSort < Minitest::Test
def test_topological_sort_returns_valid_order_for_acyclic_graph
wardrobe_items = [:underwear, :trousers, :belt, :shirt, :tie, :jacket, :socks, :shoes, :watch]
wardrobe_graph = UnweightedGraph.new(nodes: wardrobe_items, directed: true)
wardrobe_graph.add_edge(:underwear, :trousers)
wardrobe_graph.add_edge(:underwear, :shoes)
wardrobe_graph.add_edge(:socks, :shoes)
wardrobe_graph.add_edge(:trousers, :shoes)
wardrobe_graph.add_edge(:trousers, :belt)
wardrobe_graph.add_edge(:shirt, :belt)
wardrobe_graph.add_edge(:belt, :jacket)
wardrobe_graph.add_edge(:shirt, :tie)
wardrobe_graph.add_edge(:tie, :jacket)
sorted_items = TopologicalSorter.new(wardrobe_graph).topological_sort
assert sorted_items.index(:underwear) < sorted_items.index(:trousers)
assert sorted_items.index(:underwear) < sorted_items.index(:shoes)
assert sorted_items.index(:socks) < sorted_items.index(:shoes)
assert sorted_items.index(:trousers) < sorted_items.index(:shoes)
assert sorted_items.index(:trousers) < sorted_items.index(:belt)
assert sorted_items.index(:shirt) < sorted_items.index(:belt)
assert sorted_items.index(:belt) < sorted_items.index(:jacket)
assert sorted_items.index(:shirt) < sorted_items.index(:tie)
assert sorted_items.index(:tie) < sorted_items.index(:jacket)
end
def test_topological_sort_raises_exception_for_undirected_graph
nodes = [:u, :v]
graph = UnweightedGraph.new(nodes: nodes, directed: false)
graph.add_edge(:u, :v)
assert_raises ArgumentError do
TopologicalSorter.new(graph).topological_sort
end
end
def test_topological_sort_raises_exception_for_cyclic_graph
nodes = [:u, :v]
graph = UnweightedGraph.new(nodes: nodes, directed: true)
graph.add_edge(:u, :v)
graph.add_edge(:v, :u)
assert_raises ArgumentError do
TopologicalSorter.new(graph).topological_sort
end
end
end