rustworkx_core/connectivity/
biconnected.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::{HashMap, HashSet};
14use std::hash::Hash;
15
16use petgraph::{
17    visit::{
18        EdgeCount, GraphBase, GraphProp, IntoEdges, IntoNodeIdentifiers, NodeIndexable, Time,
19        Visitable,
20    },
21    Undirected,
22};
23
24use crate::traversal::{depth_first_search, DfsEvent};
25
26const NULL: usize = usize::MAX;
27
28type Edge<G> = (<G as GraphBase>::NodeId, <G as GraphBase>::NodeId);
29
30#[inline]
31fn is_root(parent: &[usize], u: usize) -> bool {
32    parent[u] == NULL
33}
34
35fn _articulation_points<G>(
36    graph: G,
37    components: Option<&mut HashMap<Edge<G>, usize>>,
38    bridges: Option<&mut HashSet<Edge<G>>>,
39) -> HashSet<G::NodeId>
40where
41    G: GraphProp<EdgeType = Undirected>
42        + EdgeCount
43        + IntoEdges
44        + Visitable
45        + NodeIndexable
46        + IntoNodeIdentifiers,
47    G::NodeId: Eq + Hash,
48{
49    let num_nodes = graph.node_bound();
50
51    let mut low = vec![NULL; num_nodes];
52    let mut disc = vec![NULL; num_nodes];
53    let mut parent = vec![NULL; num_nodes];
54
55    let mut root_children: usize = 0;
56    let mut points = HashSet::new();
57
58    let mut edge_stack = Vec::new();
59    let need_components = components.is_some();
60    let mut tmp_components = if need_components {
61        HashMap::with_capacity(graph.edge_count())
62    } else {
63        HashMap::new()
64    };
65    let need_bridges = bridges.is_some();
66    let mut tmp_bridges = if need_bridges {
67        HashSet::with_capacity(graph.edge_count())
68    } else {
69        HashSet::new()
70    };
71    let mut num_components: usize = 0;
72
73    depth_first_search(graph, graph.node_identifiers(), |event| match event {
74        DfsEvent::Discover(u_id, Time(t)) => {
75            let u = graph.to_index(u_id);
76            low[u] = t;
77            disc[u] = t;
78        }
79        DfsEvent::TreeEdge(u_id, v_id, _) => {
80            let u = graph.to_index(u_id);
81            let v = graph.to_index(v_id);
82            parent[v] = u;
83            if is_root(&parent, u) {
84                root_children += 1;
85            }
86            if need_components {
87                edge_stack.push((u_id, v_id));
88            }
89        }
90        DfsEvent::BackEdge(u_id, v_id, _) => {
91            let u = graph.to_index(u_id);
92            let v = graph.to_index(v_id);
93
94            // do *not* consider ``(u, v)`` as a back edge if ``(v, u)`` is a tree edge.
95            if v != parent[u] {
96                low[u] = low[u].min(disc[v]);
97                if need_components {
98                    edge_stack.push((u_id, v_id));
99                }
100            }
101        }
102        DfsEvent::Finish(u_id, _) => {
103            let u = graph.to_index(u_id);
104            if is_root(&parent, u) {
105                if root_children > 1 {
106                    points.insert(u_id);
107                }
108                // restart ``root_children`` for the remaining connected components
109                root_children = 0;
110            } else {
111                let pu = parent[u];
112                let pu_id = graph.from_index(pu);
113                low[pu] = low[pu].min(low[u]);
114
115                if !is_root(&parent, pu) && low[u] >= disc[pu] {
116                    points.insert(pu_id);
117                    // now find a biconnected component that the
118                    // current articulation point belongs.
119                    if need_components {
120                        if let Some(at) = edge_stack.iter().rposition(|&x| x == (pu_id, u_id)) {
121                            tmp_components.extend(
122                                edge_stack[at..].iter().map(|edge| (*edge, num_components)),
123                            );
124                            edge_stack.truncate(at);
125                            num_components += 1;
126                        }
127                    }
128                    if need_bridges && low[u] != disc[pu] {
129                        tmp_bridges.insert((pu_id, u_id));
130                    }
131                }
132
133                if is_root(&parent, pu) && need_components {
134                    if let Some(at) = edge_stack.iter().position(|&x| x == (pu_id, u_id)) {
135                        tmp_components
136                            .extend(edge_stack[at..].iter().map(|edge| (*edge, num_components)));
137                        edge_stack.truncate(at);
138                        num_components += 1;
139                    }
140                }
141            }
142        }
143        _ => (),
144    });
145
146    if let Some(x) = components {
147        *x = tmp_components;
148    }
149    if let Some(x) = bridges {
150        *x = tmp_bridges;
151    }
152
153    points
154}
155
156/// Return the articulation points of an undirected graph.
157///
158/// An articulation point or cut vertex is any node whose removal (along with
159/// all its incident edges) increases the number of connected components of
160/// a graph. An undirected connected graph without articulation points is
161/// biconnected.
162///
163/// At the same time, you can record the biconnected components in `components`.
164///
165/// Biconnected components are maximal subgraphs such that the removal
166/// of a node (and all edges incident on that node) will not disconnect
167/// the subgraph. Note that nodes may be part of more than one biconnected
168/// component. Those nodes are articulation points, or cut vertices. The
169/// algorithm computes how many biconnected components are in the graph,
170/// and assigning each component an integer label.
171///
172/// # Note
173/// The function implicitly assumes that there are no parallel edges
174/// or self loops. It may produce incorrect/unexpected results if the
175/// input graph has self loops or parallel edges.
176///
177///
178/// # Example:
179/// ```rust
180/// use std::iter::FromIterator;
181/// use hashbrown::{HashMap, HashSet};
182///
183/// use rustworkx_core::connectivity::articulation_points;
184/// use rustworkx_core::petgraph::graph::UnGraph;
185/// use rustworkx_core::petgraph::graph::node_index as nx;
186///
187/// let graph = UnGraph::<(), ()>::from_edges(&[
188///    (0, 1), (0, 2), (1, 2), (1, 3),
189/// ]);
190///
191/// let mut bicomp = HashMap::new();
192/// let a_points = articulation_points(&graph, Some(&mut bicomp));
193///
194/// assert_eq!(a_points, HashSet::from_iter([nx(1)]));
195/// assert_eq!(bicomp, HashMap::from_iter([
196///     ((nx(0), nx(2)), 1), ((nx(2), nx(1)), 1), ((nx(1), nx(0)), 1),
197///     ((nx(1), nx(3)), 0)
198/// ]));
199/// ```
200pub fn articulation_points<G>(
201    graph: G,
202    components: Option<&mut HashMap<Edge<G>, usize>>,
203) -> HashSet<G::NodeId>
204where
205    G: GraphProp<EdgeType = Undirected>
206        + EdgeCount
207        + IntoEdges
208        + Visitable
209        + NodeIndexable
210        + IntoNodeIdentifiers,
211    G::NodeId: Eq + Hash,
212{
213    _articulation_points(graph, components, None)
214}
215
216/// Return the bridges of an undirected graph.
217///
218/// Bridges are edges that, if removed, would increase the number of
219/// connected components of a graph.
220///
221/// # Note
222/// The function implicitly assumes that there are no parallel edges
223/// or self loops. It may produce incorrect/unexpected results if the
224/// input graph has self loops or parallel edges.
225///
226///
227/// # Example:
228/// ```rust
229/// use std::iter::FromIterator;
230/// use hashbrown::{HashMap, HashSet};
231///
232/// use rustworkx_core::connectivity::bridges;
233/// use rustworkx_core::petgraph::graph::UnGraph;
234/// use rustworkx_core::petgraph::graph::node_index as nx;
235///
236/// let graph = UnGraph::<(), ()>::from_edges(&[
237///    (0, 1), (0, 2), (1, 2), (1, 3),
238/// ]);
239///
240/// let bridges = bridges(&graph);
241///
242/// assert_eq!(bridges, HashSet::from_iter([(nx(1), nx(3))]));
243/// ```
244pub fn bridges<G>(graph: G) -> HashSet<Edge<G>>
245where
246    G: GraphProp<EdgeType = Undirected>
247        + EdgeCount
248        + IntoEdges
249        + Visitable
250        + NodeIndexable
251        + IntoNodeIdentifiers,
252    G::NodeId: Eq + Hash,
253{
254    let mut bridges = HashSet::new();
255    _articulation_points(graph, None, Some(&mut bridges));
256    bridges
257}
258
259#[cfg(test)]
260mod tests {
261    use crate::connectivity::{articulation_points, bridges};
262    use hashbrown::{HashMap, HashSet};
263    use petgraph::graph::node_index as nx;
264    use petgraph::prelude::*;
265    use std::iter::FromIterator;
266
267    #[test]
268    fn test_articulation_points_repetitions() {
269        let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (1, 3)]);
270
271        let a_points = articulation_points(&graph, None);
272
273        assert_eq!(a_points, HashSet::from_iter([nx(1)]));
274    }
275
276    #[test]
277    fn test_articulation_points_cycle() {
278        // create a cycle graph
279        let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 1)]);
280
281        let a_points = articulation_points(&graph, None);
282
283        assert_eq!(a_points, HashSet::from_iter([nx(1)]));
284    }
285
286    #[test]
287    fn test_single_bridge() {
288        let graph = UnGraph::<(), ()>::from_edges([
289            (1, 2),
290            (2, 3),
291            (3, 4),
292            (3, 5),
293            (5, 6),
294            (6, 7),
295            (7, 8),
296            (5, 9),
297            (9, 10),
298            // Nontree edges.
299            (1, 3),
300            (1, 4),
301            (2, 5),
302            (5, 10),
303            (6, 8),
304        ]);
305
306        assert_eq!(bridges(&graph), HashSet::from_iter([(nx(5), nx(6))]));
307    }
308
309    #[test]
310    // generate test cases for bridges
311    fn test_bridges_cycle() {
312        let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 1)]);
313        assert_eq!(bridges(&graph), HashSet::from_iter([]));
314    }
315
316    #[test]
317    fn test_biconnected_components_cycle() {
318        // create a cycle graph
319        let graph = UnGraph::<(), ()>::from_edges([(0, 1), (1, 2), (2, 0), (1, 3), (3, 4), (4, 1)]);
320
321        let mut components = HashMap::new();
322        let _ = articulation_points(&graph, Some(&mut components));
323
324        assert_eq!(
325            components,
326            HashMap::from_iter([
327                ((nx(1), nx(3)), 0),
328                ((nx(3), nx(4)), 0),
329                ((nx(4), nx(1)), 0),
330                ((nx(1), nx(2)), 1),
331                ((nx(2), nx(0)), 1),
332                ((nx(0), nx(1)), 1)
333            ])
334        );
335    }
336
337    #[test]
338    fn test_biconnected_components1() {
339        // example from https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html
340        let graph = UnGraph::<(), ()>::from_edges([
341            (0, 1),
342            (0, 5),
343            (0, 6),
344            (0, 14),
345            (1, 5),
346            (1, 6),
347            (1, 14),
348            (2, 4),
349            (2, 10),
350            (3, 4),
351            (3, 15),
352            (4, 6),
353            (4, 7),
354            (4, 10),
355            (5, 14),
356            (6, 14),
357            (7, 9),
358            (8, 9),
359            (8, 12),
360            (8, 13),
361            (10, 15),
362            (11, 12),
363            (11, 13),
364            (12, 13),
365        ]);
366
367        let mut components = HashMap::new();
368        let a_points = articulation_points(&graph, Some(&mut components));
369
370        assert_eq!(
371            a_points,
372            HashSet::from_iter([nx(4), nx(6), nx(7), nx(8), nx(9)])
373        );
374        assert_eq!(
375            components,
376            HashMap::from_iter([
377                ((nx(3), nx(4)), 0),
378                ((nx(15), nx(3)), 0),
379                ((nx(10), nx(15)), 0),
380                ((nx(4), nx(10)), 0),
381                ((nx(10), nx(2)), 0),
382                ((nx(2), nx(4)), 0),
383                ((nx(13), nx(12)), 1),
384                ((nx(8), nx(13)), 1),
385                ((nx(11), nx(13)), 1),
386                ((nx(12), nx(11)), 1),
387                ((nx(12), nx(8)), 1),
388                ((nx(9), nx(8)), 2),
389                ((nx(7), nx(9)), 3),
390                ((nx(4), nx(7)), 4),
391                ((nx(6), nx(4)), 5),
392                ((nx(0), nx(14)), 6),
393                ((nx(1), nx(5)), 6),
394                ((nx(5), nx(0)), 6),
395                ((nx(5), nx(14)), 6),
396                ((nx(1), nx(14)), 6),
397                ((nx(14), nx(6)), 6),
398                ((nx(6), nx(0)), 6),
399                ((nx(6), nx(1)), 6),
400                ((nx(1), nx(0)), 6),
401            ])
402        )
403    }
404
405    #[test]
406    fn test_biconnected_components2() {
407        let mut graph: Graph<&str, (), Undirected> = Graph::new_undirected();
408        let a = graph.add_node("A");
409        let b = graph.add_node("B");
410        let c = graph.add_node("C");
411        let d = graph.add_node("D");
412        let e = graph.add_node("E");
413        let f = graph.add_node("F");
414        let g = graph.add_node("G");
415        let h = graph.add_node("H");
416        let i = graph.add_node("I");
417        let j = graph.add_node("J");
418
419        graph.extend_with_edges([
420            (a, b),
421            (b, c),
422            (c, a),
423            (c, d),
424            (d, e),
425            (e, c),
426            (f, i),
427            (i, j),
428            (j, h),
429            (h, g),
430            (g, f),
431            (g, i),
432            (g, j),
433            (e, g),
434        ]);
435
436        let mut components = HashMap::new();
437        let _ = articulation_points(&graph, Some(&mut components));
438
439        assert_eq!(
440            components,
441            HashMap::from_iter([
442                ((f, g), 0),
443                ((i, f), 0),
444                ((i, g), 0),
445                ((j, i), 0),
446                ((g, j), 0),
447                ((j, h), 0),
448                ((h, g), 0),
449                ((e, g), 1),
450                ((c, d), 2),
451                ((d, e), 2),
452                ((e, c), 2),
453                ((c, a), 3),
454                ((a, b), 3),
455                ((b, c), 3),
456            ])
457        )
458    }
459
460    #[test]
461    fn test_null_graph() {
462        let graph: Graph<(), (), Undirected> = Graph::new_undirected();
463
464        let mut components = HashMap::new();
465        let a_points = articulation_points(&graph, Some(&mut components));
466        let bridges = bridges(&graph);
467
468        assert_eq!(a_points, HashSet::new());
469        assert_eq!(bridges, HashSet::new());
470        assert_eq!(components, HashMap::new());
471    }
472}