rustworkx_core/connectivity/
conn_components.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 hashbrown::HashSet;
14use std::collections::VecDeque;
15use std::hash::Hash;
16
17use petgraph::visit::{GraphProp, IntoNeighborsDirected, IntoNodeIdentifiers, VisitMap, Visitable};
18use petgraph::{Incoming, Outgoing};
19
20/// Given an graph, a node in the graph, and a visit_map,
21/// return the set of nodes connected to the given node
22/// using breadth first search and treating all edges
23/// as undirected.
24///
25/// Arguments:
26///
27/// * `graph` - The graph object to run the algorithm on
28/// * `node` - The node index to find the connected nodes for
29/// * `discovered()` - The visit map for the graph
30///
31/// # Example
32/// ```rust
33/// use std::iter::FromIterator;
34/// use hashbrown::HashSet;
35/// use petgraph::graph::Graph;
36/// use petgraph::graph::node_index as ndx;
37/// use petgraph::visit::Visitable;
38/// use petgraph::Directed;
39/// use rustworkx_core::connectivity::bfs_undirected;
40///
41/// let graph = Graph::<(), (), Directed>::from_edges(&[
42///     (0, 1),
43///     (1, 2),
44///     (2, 3),
45///     (3, 0),
46///     (4, 5),
47///     (5, 6),
48///     (6, 7),
49///     (7, 4),
50/// ]);
51/// let node_idx = ndx(3);
52/// let component = bfs_undirected(&graph, node_idx, &mut graph.visit_map());
53/// let expected = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]);
54/// assert_eq!(expected, component);
55/// ```
56pub fn bfs_undirected<G>(graph: G, start: G::NodeId, discovered: &mut G::Map) -> HashSet<G::NodeId>
57where
58    G: GraphProp + IntoNeighborsDirected + Visitable,
59    G::NodeId: Eq + Hash,
60{
61    let mut component = HashSet::new();
62    component.insert(start);
63    let mut stack = VecDeque::new();
64    stack.push_front(start);
65
66    while let Some(node) = stack.pop_front() {
67        for succ in graph
68            .neighbors_directed(node, Outgoing)
69            .chain(graph.neighbors_directed(node, Incoming))
70        {
71            if discovered.visit(succ) {
72                stack.push_back(succ);
73                component.insert(succ);
74            }
75        }
76    }
77
78    component
79}
80
81/// Given a graph, return a list of sets of all the
82/// connected components.
83///
84/// Arguments:
85///
86/// * `graph` - The graph object to run the algorithm on
87///
88/// # Example
89/// ```rust
90/// use std::iter::FromIterator;
91/// use hashbrown::HashSet;
92/// use petgraph::graph::Graph;
93/// use petgraph::graph::NodeIndex;
94/// use petgraph::{Undirected, Directed};
95/// use petgraph::graph::node_index as ndx;
96/// use rustworkx_core::connectivity::connected_components;
97///
98/// let graph = Graph::<(), (), Undirected>::from_edges(&[
99///     (0, 1),
100///     (1, 2),
101///     (2, 3),
102///     (3, 0),
103///     (4, 5),
104///     (5, 6),
105///     (6, 7),
106///     (7, 4),
107/// ]);
108/// let components = connected_components(&graph);
109/// let exp1 = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]);
110/// let exp2 = HashSet::from_iter([ndx(7), ndx(5), ndx(4), ndx(6)]);
111/// let expected = vec![exp1, exp2];
112/// assert_eq!(expected, components);
113/// ```
114pub fn connected_components<G>(graph: G) -> Vec<HashSet<G::NodeId>>
115where
116    G: GraphProp + IntoNeighborsDirected + Visitable + IntoNodeIdentifiers,
117    G::NodeId: Eq + Hash,
118{
119    let mut conn_components = Vec::new();
120    let mut discovered = graph.visit_map();
121
122    for start in graph.node_identifiers() {
123        if !discovered.visit(start) {
124            continue;
125        }
126
127        let component = bfs_undirected(graph, start, &mut discovered);
128        conn_components.push(component)
129    }
130
131    conn_components
132}
133
134/// Given a graph, return the number of connected components of the graph.
135///
136/// Arguments:
137///
138/// * `graph` - The graph object to run the algorithm on
139///
140/// # Example
141/// ```rust
142/// use rustworkx_core::petgraph::{Graph, Undirected};
143/// use rustworkx_core::connectivity::number_connected_components;
144///
145/// let graph = Graph::<(), (), Undirected>::from_edges([(0, 1), (1, 2), (3, 4)]);
146/// assert_eq!(number_connected_components(&graph), 2);
147/// ```
148pub fn number_connected_components<G>(graph: G) -> usize
149where
150    G: GraphProp + IntoNeighborsDirected + Visitable + IntoNodeIdentifiers,
151    G::NodeId: Eq + Hash,
152{
153    let mut num_components = 0;
154
155    let mut discovered = graph.visit_map();
156    for start in graph.node_identifiers() {
157        if !discovered.visit(start) {
158            continue;
159        }
160
161        num_components += 1;
162        bfs_undirected(graph, start, &mut discovered);
163    }
164
165    num_components
166}
167
168#[cfg(test)]
169mod test_conn_components {
170    use hashbrown::HashSet;
171    use petgraph::graph::node_index as ndx;
172    use petgraph::graph::{Graph, NodeIndex};
173    use petgraph::visit::Visitable;
174    use petgraph::{Directed, Undirected};
175    use std::iter::FromIterator;
176
177    use crate::connectivity::{bfs_undirected, connected_components, number_connected_components};
178
179    #[test]
180    fn test_number_connected() {
181        let graph = Graph::<(), (), Undirected>::from_edges([(0, 1), (1, 2), (3, 4)]);
182        assert_eq!(number_connected_components(&graph), 2);
183    }
184
185    #[test]
186    fn test_number_node_holes() {
187        let mut graph = Graph::<(), (), Directed>::from_edges([(0, 1), (1, 2)]);
188        graph.remove_node(NodeIndex::new(1));
189        assert_eq!(number_connected_components(&graph), 2);
190    }
191
192    #[test]
193    fn test_number_connected_directed() {
194        let graph = Graph::<(), (), Directed>::from_edges([(3, 2), (2, 1), (1, 0)]);
195        assert_eq!(number_connected_components(&graph), 1);
196    }
197
198    #[test]
199    fn test_connected_components() {
200        let graph = Graph::<(), (), Undirected>::from_edges([
201            (0, 1),
202            (1, 2),
203            (2, 3),
204            (3, 0),
205            (4, 5),
206            (5, 6),
207            (6, 7),
208            (7, 4),
209        ]);
210        let components = connected_components(&graph);
211        let exp1 = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]);
212        let exp2 = HashSet::from_iter([ndx(7), ndx(5), ndx(4), ndx(6)]);
213        let expected = vec![exp1, exp2];
214        assert_eq!(expected, components);
215    }
216
217    #[test]
218    fn test_bfs_undirected() {
219        let graph = Graph::<(), (), Directed>::from_edges([
220            (0, 1),
221            (1, 2),
222            (2, 3),
223            (3, 0),
224            (4, 5),
225            (5, 6),
226            (6, 7),
227            (7, 4),
228        ]);
229        let node_idx = NodeIndex::new(3);
230        let component = bfs_undirected(&graph, node_idx, &mut graph.visit_map());
231        let expected = HashSet::from_iter([ndx(0), ndx(1), ndx(3), ndx(2)]);
232        assert_eq!(expected, component);
233    }
234}