retworkx_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,
19    Visitable,
20};
21
22/// Return an edge list of the tree edges from a depth-first traversal.
23///
24/// The pseudo-code for the DFS algorithm is listed below. The output
25/// contains the tree edges found by the procedure.
26///
27/// ```norust
28/// DFS(G, v)
29///   let S be a stack
30///   label v as discovered
31///   PUSH(S, (v, iterator of G.neighbors(v)))
32///   while (S != Ø)
33///       let (v, iterator) := LAST(S)
34///       if hasNext(iterator) then
35///           w := next(iterator)
36///           if w is not labeled as discovered then
37///               label w as discovered                   # (v, w) is a tree edge
38///               PUSH(S, (w, iterator of G.neighbors(w)))
39///       else
40///           POP(S)
41///   end while
42/// ```
43///
44/// Arguments:
45///
46/// * `graph` - the graph to run on
47/// * `source` - the optional node index to use as the starting node for the
48///     depth-first search. If specified the edge list will only return edges
49///     in the components reachable from this index. If this is not specified
50///     then a source will be chosen arbitrarily and repeated until all
51///     components of the graph are searched
52///
53/// # Example
54/// ```rust
55/// use retworkx_core::petgraph;
56/// use retworkx_core::traversal::dfs_edges;
57///
58/// let g = petgraph::graph::UnGraph::<i32, ()>::from_edges(&[
59///     (0, 1), (1, 2), (1, 3), (2, 4), (3, 4)
60/// ]);
61/// let dfs_edges = dfs_edges(&g, Some(petgraph::graph::NodeIndex::new(0)));
62/// assert_eq!(vec![(0, 1), (1, 2), (2, 4), (4, 3)], dfs_edges);
63/// ```
64pub fn dfs_edges<G>(graph: G, source: Option<G::NodeId>) -> Vec<(usize, usize)>
65where
66    G: IntoNodeIdentifiers
67        + NodeIndexable
68        + IntoNeighbors
69        + NodeCount
70        + EdgeCount
71        + Visitable,
72    G::NodeId: Eq + Hash,
73{
74    let nodes: Vec<G::NodeId> = match source {
75        Some(start) => vec![start],
76        None => graph.node_identifiers().collect(),
77    };
78    let node_count = graph.node_count();
79    let mut visited: HashSet<G::NodeId> = HashSet::with_capacity(node_count);
80    // Avoid potential overallocation if source node is provided
81    let mut out_vec: Vec<(usize, usize)> = if source.is_some() {
82        Vec::new()
83    } else {
84        Vec::with_capacity(core::cmp::min(
85            graph.node_count() - 1,
86            graph.edge_count(),
87        ))
88    };
89    for start in nodes {
90        if visited.contains(&start) {
91            continue;
92        }
93        visited.insert(start);
94        let mut children: Vec<G::NodeId> = graph.neighbors(start).collect();
95        children.reverse();
96        let mut stack: Vec<(G::NodeId, Vec<G::NodeId>)> =
97            vec![(start, children)];
98        // Used to track the last position in children vec across iterations
99        let mut index_map: HashMap<G::NodeId, usize> =
100            HashMap::with_capacity(node_count);
101        index_map.insert(start, 0);
102        while !stack.is_empty() {
103            let temp_parent = stack.last().unwrap();
104            let parent = temp_parent.0;
105            let children = temp_parent.1.clone();
106            let count = *index_map.get(&parent).unwrap();
107            let mut found = false;
108            let mut index = count;
109            for child in &children[index..] {
110                index += 1;
111                if !visited.contains(child) {
112                    out_vec
113                        .push((graph.to_index(parent), graph.to_index(*child)));
114                    visited.insert(*child);
115                    let mut grandchildren: Vec<G::NodeId> =
116                        graph.neighbors(*child).collect();
117                    grandchildren.reverse();
118                    stack.push((*child, grandchildren));
119                    index_map.insert(*child, 0);
120                    *index_map.get_mut(&parent).unwrap() = index;
121                    found = true;
122                    break;
123                }
124            }
125            if !found || children.is_empty() {
126                stack.pop();
127            }
128        }
129    }
130    out_vec
131}