Skip to main content

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