rustworkx_core/traversal/dfs_edges.rs
1// Licensed under the Apache License, Version 2.0 (the "License"); you may
2// not use this file except in compliance with the License. You may obtain
3// a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10// License for the specific language governing permissions and limitations
11// under the License.
12
13use std::hash::Hash;
14
15use hashbrown::{HashMap, HashSet};
16
17use petgraph::visit::{
18 EdgeCount, IntoNeighbors, IntoNodeIdentifiers, NodeCount, NodeIndexable, Visitable,
19};
20
21/// Return an edge list of the tree edges from a depth-first traversal.
22///
23/// The pseudo-code for the DFS algorithm is listed below. The output
24/// contains the tree edges found by the procedure.
25///
26/// ```norust
27/// DFS(G, v)
28/// let S be a stack
29/// label v as discovered
30/// PUSH(S, (v, iterator of G.neighbors(v)))
31/// while (S != Ø)
32/// let (v, iterator) := LAST(S)
33/// if hasNext(iterator) then
34/// w := next(iterator)
35/// if w is not labeled as discovered then
36/// label w as discovered # (v, w) is a tree edge
37/// PUSH(S, (w, iterator of G.neighbors(w)))
38/// else
39/// POP(S)
40/// end while
41/// ```
42///
43/// Arguments:
44///
45/// * `graph` - the graph to run on
46/// * `source` - the optional node index to use as the starting node for the
47/// depth-first search. If specified the edge list will only return edges
48/// in the components reachable from this index. If this is not specified
49/// then a source will be chosen arbitrarily and repeated until all
50/// components of the graph are searched
51///
52/// # Example
53/// ```rust
54/// use rustworkx_core::petgraph;
55/// use rustworkx_core::traversal::dfs_edges;
56///
57/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
58/// (0, 1), (1, 2), (1, 3), (2, 4), (3, 4)
59/// ]);
60/// let dfs_edges = dfs_edges(&g, Some(petgraph::graph::NodeIndex::new(0)));
61/// assert_eq!(vec![(0, 1), (1, 2), (2, 4), (4, 3)], dfs_edges);
62/// ```
63pub fn dfs_edges<G>(graph: G, source: Option<G::NodeId>) -> Vec<(usize, usize)>
64where
65 G: IntoNodeIdentifiers + NodeIndexable + IntoNeighbors + NodeCount + EdgeCount + Visitable,
66 G::NodeId: Eq + Hash,
67{
68 let nodes: Vec<G::NodeId> = match source {
69 Some(start) => vec![start],
70 None => graph.node_identifiers().collect(),
71 };
72 let node_count = graph.node_count();
73 let mut visited: HashSet<G::NodeId> = HashSet::with_capacity(node_count);
74 // Avoid potential overallocation if source node is provided
75 let mut out_vec: Vec<(usize, usize)> = if source.is_some() {
76 Vec::new()
77 } else {
78 Vec::with_capacity(core::cmp::min(graph.node_count() - 1, graph.edge_count()))
79 };
80 for start in nodes {
81 if visited.contains(&start) {
82 continue;
83 }
84 visited.insert(start);
85 let mut children: Vec<G::NodeId> = graph.neighbors(start).collect();
86 children.reverse();
87 let mut stack: Vec<(G::NodeId, Vec<G::NodeId>)> = vec![(start, children)];
88 // Used to track the last position in children vec across iterations
89 let mut index_map: HashMap<G::NodeId, usize> = HashMap::with_capacity(node_count);
90 index_map.insert(start, 0);
91 while !stack.is_empty() {
92 let temp_parent = stack.last().unwrap();
93 let parent = temp_parent.0;
94 let children = temp_parent.1.clone();
95 let count = *index_map.get(&parent).unwrap();
96 let mut found = false;
97 let mut index = count;
98 for child in &children[index..] {
99 index += 1;
100 if !visited.contains(child) {
101 out_vec.push((graph.to_index(parent), graph.to_index(*child)));
102 visited.insert(*child);
103 let mut grandchildren: Vec<G::NodeId> = graph.neighbors(*child).collect();
104 grandchildren.reverse();
105 stack.push((*child, grandchildren));
106 index_map.insert(*child, 0);
107 *index_map.get_mut(&parent).unwrap() = index;
108 found = true;
109 break;
110 }
111 }
112 if !found || children.is_empty() {
113 stack.pop();
114 }
115 }
116 }
117 out_vec
118}