graph_traversal/topo.rs
1use crate::cycle::has_cycle_directed;
2use crate::dfs::dfs_full;
3use graph_collections::Queue;
4use graph_core::{Graph, GraphError, NodeId};
5use std::collections::HashMap;
6
7/// Returns a topological ordering of the nodes using **DFS finish order**.
8///
9/// Works only on directed acyclic graphs (DAGs). If the graph contains a
10/// cycle, returns [`GraphError::InvalidOperation`].
11///
12/// The reverse of the DFS finish order is a valid topological sort: a node
13/// appears before all nodes that depend on it.
14///
15/// # Errors
16///
17/// Returns `Err(GraphError::InvalidOperation)` if the graph has a cycle.
18///
19/// # Examples
20///
21/// ```
22/// use graph_core::{AdjacencyList, Graph};
23/// use graph_traversal::topological_sort_dfs;
24///
25/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
26/// let a = g.add_node(());
27/// let b = g.add_node(());
28/// let c = g.add_node(());
29/// g.add_edge(a, b, 1.0).unwrap();
30/// g.add_edge(b, c, 1.0).unwrap();
31///
32/// let order = topological_sort_dfs(&g).unwrap();
33/// // a must come before b, b before c
34/// let pos: std::collections::HashMap<_, _> =
35/// order.iter().enumerate().map(|(i, &n)| (n, i)).collect();
36/// assert!(pos[&a] < pos[&b]);
37/// assert!(pos[&b] < pos[&c]);
38/// ```
39pub fn topological_sort_dfs<G: Graph>(graph: &G) -> Result<Vec<NodeId>, GraphError> {
40 if has_cycle_directed(graph) {
41 return Err(GraphError::InvalidOperation(
42 "topological sort requires a DAG — cycle detected",
43 ));
44 }
45
46 let mut finish_order = dfs_full(graph, &mut |_| {});
47 finish_order.reverse();
48 Ok(finish_order)
49}
50
51/// Returns a topological ordering using **Kahn's algorithm** (in-degree BFS).
52///
53/// Processes nodes with in-degree 0 first; decrementing neighbours' in-degrees
54/// as each node is emitted. If not all nodes are emitted the graph has a cycle.
55///
56/// # Errors
57///
58/// Returns `Err(GraphError::InvalidOperation)` if a cycle is detected.
59///
60/// # Examples
61///
62/// ```
63/// use graph_core::{AdjacencyList, Graph};
64/// use graph_traversal::topological_sort_kahn;
65///
66/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
67/// let a = g.add_node(());
68/// let b = g.add_node(());
69/// let c = g.add_node(());
70/// g.add_edge(a, b, 1.0).unwrap();
71/// g.add_edge(a, c, 1.0).unwrap();
72/// g.add_edge(b, c, 1.0).unwrap();
73///
74/// let order = topological_sort_kahn(&g).unwrap();
75/// let pos: std::collections::HashMap<_, _> =
76/// order.iter().enumerate().map(|(i, &n)| (n, i)).collect();
77/// assert!(pos[&a] < pos[&b]);
78/// assert!(pos[&b] < pos[&c]);
79/// ```
80pub fn topological_sort_kahn<G: Graph>(graph: &G) -> Result<Vec<NodeId>, GraphError> {
81 // Compute in-degrees.
82 let mut in_degree: HashMap<NodeId, usize> = graph.nodes().map(|n| (n, 0usize)).collect();
83
84 for node in graph.nodes() {
85 for (neighbour, _) in graph.neighbors(node) {
86 *in_degree.get_mut(&neighbour).unwrap() += 1;
87 }
88 }
89
90 // Seed queue with all zero-in-degree nodes.
91 let mut queue: Queue<NodeId> = Queue::new();
92 for (&node, °) in &in_degree {
93 if deg == 0 {
94 queue.enqueue(node);
95 }
96 }
97
98 let mut order: Vec<NodeId> = Vec::new();
99
100 while let Some(node) = queue.dequeue() {
101 order.push(node);
102 for (neighbour, _) in graph.neighbors(node) {
103 let deg = in_degree.get_mut(&neighbour).unwrap();
104 *deg -= 1;
105 if *deg == 0 {
106 queue.enqueue(neighbour);
107 }
108 }
109 }
110
111 if order.len() != graph.node_count() {
112 return Err(GraphError::InvalidOperation(
113 "topological sort requires a DAG — cycle detected",
114 ));
115 }
116
117 Ok(order)
118}