Yog.Traversal.Sort (YogEx v0.97.0)

Copy Markdown View Source

Topological sorting algorithms for directed acyclic graphs (DAGs).

This module provides algorithms for producing a topological ordering of nodes in a DAG. A topological ordering is a linear ordering of nodes such that for every directed edge (u, v), node u comes before v in the ordering.

Algorithms

  • Kahn's Algorithm: topological_sort/1 - BFS-based approach using in-degrees. Time complexity O(V + E). Preferred for most use cases.
  • Lexicographical Topological Sort: lexicographical_topological_sort/2 - Kahn's algorithm with a priority queue for deterministic ordering. Time complexity O((V + E) log V).

Algorithm Characteristics

  • Time Complexity: O(V + E) for standard, O((V + E) log V) for lexicographical
  • Space Complexity: O(V) for the in-degree map and queue
  • Cycle Detection: Both algorithms detect cycles and return {:error, :contains_cycle}

When to Use

  • Standard: Use when you just need any valid topological ordering
  • Lexicographical: Use when you need a deterministic ordering (e.g., alphabetical)

Use Cases

  • Task scheduling with dependencies
  • Resolving symbol dependencies in compilers
  • Ordering of formula calculations in spreadsheets
  • Determining instruction sequences in dataflow programming

Topological Order Visualization

A topological sort flattens a DAG into a linear sequence where all directed edges point from left to right.

digraph G { rankdir=LR; bgcolor="transparent"; node [shape=circle, fontname="inherit"]; edge [fontname="inherit", fontsize=10]; subgraph cluster_dag { label="Directed Acyclic Graph (DAG)"; color="#6366f1"; style=rounded; A -> B; A -> C; B -> D; C -> D; D -> E; } }
iex> alias Yog.Traversal.Sort
iex> graph = Yog.from_edges(:directed, [
...>   {"A", "B", 1}, {"A", "C", 1},
...>   {"B", "D", 1}, {"C", "D", 1},
...>   {"D", "E", 1}
...> ])
iex> {:ok, order} = Sort.topological_sort(graph)
iex> # Check if order is valid (A before B,C; B,C before D; D before E)
...> Enum.find_index(order, &(&1 == "A")) < Enum.find_index(order, &(&1 == "B"))
true

Examples

# Standard topological sort
iex> graph = Yog.directed()
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_node(3, "C")
...> |> Yog.add_edges!([{1, 2, 1}, {2, 3, 1}])
iex> {:ok, order} = Yog.Traversal.Sort.topological_sort(graph)
iex> order
[1, 2, 3]

# Lexicographical topological sort
iex> graph = Yog.directed()
...> |> Yog.add_node(1, "c")
...> |> Yog.add_node(2, "a")
...> |> Yog.add_node(3, "b")
...> |> Yog.add_edges!([{1, 3, 1}, {2, 3, 1}])
iex> {:ok, order} = Yog.Traversal.Sort.lexicographical_topological_sort(graph, &<=/2)
iex> order
[2, 1, 3]

Summary

Functions

Performs a lexicographically smallest topological sort. Compares by node_data. Breaks tie by ID.

Performs a topological sort on a directed graph using Kahn's algorithm.

Functions

lexicographical_topological_sort(graph, compare_nodes)

@spec lexicographical_topological_sort(Yog.graph(), (term(), term() ->
                                                 :lt | :eq | :gt)) ::
  {:ok, [Yog.node_id()]} | {:error, :contains_cycle}

Performs a lexicographically smallest topological sort. Compares by node_data. Breaks tie by ID.

Time Complexity: O((V + E) log V) due to priority queue operations

topological_sort(graph)

@spec topological_sort(Yog.graph()) ::
  {:ok, [Yog.node_id()]} | {:error, :contains_cycle}

Performs a topological sort on a directed graph using Kahn's algorithm.

Time Complexity: O(V + E)