graph_traversal/dfs.rs
1use graph_collections::Stack;
2use graph_core::{Graph, NodeId};
3use std::collections::HashSet;
4
5// ── Recursive DFS ─────────────────────────────────────────────────────────────
6
7/// Runs a **recursive** depth-first search from `start`, calling `visitor` the
8/// first time each node is discovered.
9///
10/// Returns nodes in **finish order** (post-order): a node appears in the
11/// output only after all nodes reachable from it have been visited. This
12/// ordering is the reverse of a topological sort for DAGs.
13///
14/// # Caveats
15///
16/// Recursive DFS uses the call stack. For graphs with millions of nodes this
17/// may overflow. Use [`dfs_iterative`] for deep graphs.
18///
19/// # Examples
20///
21/// ```
22/// use graph_core::{AdjacencyList, Graph};
23/// use graph_traversal::dfs_recursive;
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 mut visited = Vec::new();
33/// let finish = dfs_recursive(&g, a, &mut |id| visited.push(id));
34///
35/// assert_eq!(visited[0], a); // a discovered first
36/// assert_eq!(finish.last(), Some(&a)); // a finishes last (post-order)
37/// ```
38pub fn dfs_recursive<G, F>(graph: &G, start: NodeId, visitor: &mut F) -> Vec<NodeId>
39where
40 G: Graph,
41 F: FnMut(NodeId),
42{
43 let mut visited: HashSet<NodeId> = HashSet::new();
44 let mut finish_order: Vec<NodeId> = Vec::new();
45 dfs_recurse(graph, start, visitor, &mut visited, &mut finish_order);
46 finish_order
47}
48
49fn dfs_recurse<G, F>(
50 graph: &G,
51 node: NodeId,
52 visitor: &mut F,
53 visited: &mut HashSet<NodeId>,
54 finish_order: &mut Vec<NodeId>,
55) where
56 G: Graph,
57 F: FnMut(NodeId),
58{
59 if !visited.insert(node) {
60 return;
61 }
62 visitor(node);
63 for (neighbour, _) in graph.neighbors(node) {
64 dfs_recurse(graph, neighbour, visitor, visited, finish_order);
65 }
66 finish_order.push(node);
67}
68
69// ── Iterative DFS ─────────────────────────────────────────────────────────────
70
71/// Runs an **iterative** depth-first search from `start` using an explicit
72/// [`Stack`], calling `visitor` the first time each node is discovered.
73///
74/// Safe from stack overflow on arbitrarily deep graphs. Note that visit order
75/// may differ slightly from the recursive variant because the stack reverses
76/// the neighbour ordering.
77///
78/// # Examples
79///
80/// ```
81/// use graph_core::{AdjacencyList, Graph};
82/// use graph_traversal::dfs_iterative;
83///
84/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
85/// let a = g.add_node(());
86/// let b = g.add_node(());
87/// let c = g.add_node(());
88/// g.add_edge(a, b, 1.0).unwrap();
89/// g.add_edge(a, c, 1.0).unwrap();
90///
91/// let mut visited = Vec::new();
92/// dfs_iterative(&g, a, &mut |id| visited.push(id));
93///
94/// assert_eq!(visited.len(), 3);
95/// assert!(visited.contains(&a));
96/// assert!(visited.contains(&b));
97/// assert!(visited.contains(&c));
98/// ```
99pub fn dfs_iterative<G, F>(graph: &G, start: NodeId, visitor: &mut F)
100where
101 G: Graph,
102 F: FnMut(NodeId),
103{
104 let mut stack: Stack<NodeId> = Stack::new();
105 let mut visited: HashSet<NodeId> = HashSet::new();
106
107 stack.push(start);
108
109 while let Some(node) = stack.pop() {
110 if visited.insert(node) {
111 visitor(node);
112 for (neighbour, _) in graph.neighbors(node) {
113 if !visited.contains(&neighbour) {
114 stack.push(neighbour);
115 }
116 }
117 }
118 }
119}
120
121// ── DFS over all components ───────────────────────────────────────────────────
122
123/// Runs [`dfs_recursive`] from every unvisited node, covering all connected
124/// components. Returns a single finish-order vec spanning the whole graph.
125///
126/// Used internally by topological sort and SCC algorithms.
127///
128/// # Examples
129///
130/// ```
131/// use graph_core::{AdjacencyList, Graph};
132/// use graph_traversal::dfs::dfs_full;
133///
134/// let mut g: AdjacencyList<()> = AdjacencyList::directed();
135/// let a = g.add_node(());
136/// let b = g.add_node(()); // isolated
137///
138/// let finish = dfs_full(&g, &mut |_| {});
139/// assert_eq!(finish.len(), 2);
140/// ```
141pub fn dfs_full<G, F>(graph: &G, visitor: &mut F) -> Vec<NodeId>
142where
143 G: Graph,
144 F: FnMut(NodeId),
145{
146 let mut visited: HashSet<NodeId> = HashSet::new();
147 let mut finish_order: Vec<NodeId> = Vec::new();
148
149 for node in graph.nodes() {
150 if !visited.contains(&node) {
151 dfs_recurse(graph, node, visitor, &mut visited, &mut finish_order);
152 }
153 }
154
155 finish_order
156}