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}