biconnected_components/
lib.rs

1//! Compute the [biconnected
2//! components](https://en.wikipedia.org/wiki/Biconnected_component)
3//! of a graph.
4//!
5//! # Example
6//!
7//! ```
8//! use petgraph::graph::UnGraph;
9//! use biconnected_components::{Bcc, SplitIntoBcc};
10//!
11//! // construct a simple graph
12//! let g = UnGraph::<(), ()>::from_edges([
13//!    (0, 1),
14//!    (1, 2)
15//!  ]);
16//!
17//! // Get a vector of the biconnected components defined by their node indices
18//! let bcc = g.bcc();
19//! assert_eq!(bcc.len(), 2);
20//! for bcc_nodes in bcc {
21//!    println!("Found biconnected component with nodes {bcc_nodes:?}");
22//! }
23//!
24//! // Split up the graph into its biconnected components, that is
25//! // two identical graphs with two nodes connected by single edge
26//! let bcc = g.split_into_bcc();
27//! assert_eq!(bcc.len(), 2);
28//! assert!(bcc.iter().all(|g| g.node_count() == 2));
29//! assert!(bcc.iter().all(|g| g.edge_count() == 1));
30//! ```
31use std::cmp::min;
32
33pub use petgraph;
34
35use petgraph::{
36    graph::Edge, prelude::UnGraph, stable_graph::IndexType,
37    stable_graph::NodeIndex, visit::NodeIndexable,
38};
39
40pub trait Bcc {
41    type Output;
42
43    /// Return all biconnected components
44    fn bcc(&self) -> Self::Output;
45}
46
47impl<N, E, Ix: IndexType> Bcc for UnGraph<N, E, Ix> {
48    type Output = Vec<Vec<NodeIndex<Ix>>>;
49
50    fn bcc(&self) -> Self::Output {
51        let mut dfs = HopcroftTarjan::new(self.node_count());
52        dfs.find_bcc(self)
53    }
54}
55
56pub trait SplitIntoBcc {
57    type Output;
58
59    /// Split up a graph into its biconnected components
60    fn split_into_bcc(self) -> Self::Output;
61}
62
63impl<N: Clone, E, Ix: IndexType> SplitIntoBcc for UnGraph<N, E, Ix> {
64    type Output = Vec<UnGraph<N, E, Ix>>;
65
66    fn split_into_bcc(self) -> Self::Output {
67        let bccs = self.bcc();
68        let res = bccs.iter().map(|bcc| {
69            let mut g = UnGraph::with_capacity(bcc.len(), 0);
70            for n in bcc {
71                g.add_node(self.node_weight(*n).unwrap().clone());
72            }
73            g
74        });
75        let mut res = Vec::from_iter(res);
76        let (nodes, edges) = self.into_nodes_edges();
77        for edge in edges {
78            if edge.source() == edge.target() {
79                // self-loops are their own bcc
80
81                // first, ensure that we are not double-counting
82                if res.len() == 1
83                    && res[0].node_count() == 1
84                    && res[0].edge_count() == 0
85                {
86                    res.clear();
87                }
88                //
89                let mut g = UnGraph::with_capacity(1, 1);
90                let weight = nodes[edge.source().index()].weight.clone();
91                let idx = g.add_node(weight);
92                g.add_edge(idx, idx, edge.weight);
93                res.push(g);
94            } else {
95                add_edge(&mut res, &bccs, edge);
96            }
97        }
98        res
99    }
100}
101
102fn add_edge<N: Clone, E, Ix: IndexType>(
103    res: &mut [UnGraph<N, E, Ix>],
104    bccs: &[Vec<NodeIndex<Ix>>],
105    edge: Edge<E, Ix>,
106) {
107    for (res, bcc) in res.iter_mut().zip(bccs.iter()) {
108        let source_pos = bcc.iter().position(|&n| n == edge.source());
109        if let Some(from) = source_pos {
110            let target_pos = bcc.iter().position(|&n| n == edge.target());
111            if let Some(to) = target_pos {
112                let from = res.from_index(from);
113                let to = res.from_index(to);
114                res.add_edge(from, to, edge.weight);
115                return;
116            }
117        }
118    }
119    unreachable!("Found edge that is in none of the biconnected components");
120}
121
122// Find biconnected components using the algorithm from
123// Hopcroft, J.; Tarjan, R.
124// "Algorithm 447: efficient algorithms for graph manipulation".
125// Communications of the ACM. 16 (6): 372–378.
126// [doi:10.1145/362248.362272](https://doi.org/10.1145%2F362248.362272).
127struct HopcroftTarjan {
128    visited: Vec<bool>,
129    depth: Vec<usize>,
130    lowpoint: Vec<usize>,
131    parent: Vec<usize>,
132}
133
134impl HopcroftTarjan {
135    fn new(nnodes: usize) -> Self {
136        Self {
137            visited: vec![false; nnodes],
138            depth: vec![0; nnodes],
139            lowpoint: vec![0; nnodes],
140            parent: vec![0; nnodes],
141        }
142    }
143
144    fn find_bcc<N, E, Ix: IndexType>(
145        &mut self,
146        g: &UnGraph<N, E, Ix>,
147    ) -> Vec<Vec<NodeIndex<Ix>>> {
148        if g.node_count() == 0 {
149            return vec![];
150        }
151        let mut res = vec![];
152        let mut start_node = 0;
153        while let Some(start) = self.next_start_pos(start_node) {
154            self.find_bcc_from(g, start, 0, &mut res);
155            start_node = start + 1;
156        }
157        if res.is_empty() {
158            vec![g.node_indices().collect()]
159        } else {
160            res
161        }
162    }
163
164    fn find_bcc_from<N, E, Ix: IndexType>(
165        &mut self,
166        g: &UnGraph<N, E, Ix>,
167        node: usize,
168        depth: usize,
169        res: &mut Vec<Vec<NodeIndex<Ix>>>,
170    ) {
171        self.visited[node] = true;
172        self.depth[node] = depth;
173        self.lowpoint[node] = depth;
174
175        let mut is_cut_vx = false;
176
177        let idx = g.from_index(node);
178        // filter: ignore self-loops
179        for n_idx in g.neighbors(idx).filter(|&n_idx| n_idx != idx) {
180            let n = g.to_index(n_idx);
181            if !self.visited[n] {
182                let parent = node;
183                self.parent[n] = parent;
184                self.find_bcc_from(g, n, depth + 1, res);
185                if self.lowpoint[n] >= self.depth[parent] {
186                    is_cut_vx = true;
187                }
188                self.lowpoint[parent] =
189                    min(self.lowpoint[parent], self.lowpoint[n]);
190            } else if n != self.parent[node] {
191                self.lowpoint[node] = min(self.lowpoint[node], self.depth[n]);
192            }
193        }
194        if node > 0 && is_cut_vx {
195            for n_idx in g.neighbors(idx) {
196                let n = g.to_index(n_idx);
197                if self.parent[n] == node
198                    && self.lowpoint[n] >= self.depth[node]
199                {
200                    let mut bcc = vec![idx, n_idx];
201                    self.add_subtree(g, n, &mut bcc);
202                    res.push(bcc);
203                }
204            }
205        } else if node == 0 {
206            // The starting node is only a cut vertex if it has more
207            // than one child. But even if there is only a single
208            // child the corresponding subtree is a biconnected
209            // component
210            for n_idx in g.neighbors(idx).filter(|&n_idx| n_idx != idx) {
211                let n = g.to_index(n_idx);
212                if self.parent[n] == node {
213                    let mut bcc = vec![idx, n_idx];
214                    self.add_subtree(g, n, &mut bcc);
215                    res.push(bcc);
216                }
217            }
218        }
219    }
220
221    fn add_subtree<N, E, Ix: IndexType>(
222        &mut self,
223        g: &UnGraph<N, E, Ix>,
224        root: usize,
225        bcc: &mut Vec<NodeIndex<Ix>>,
226    ) {
227        // detach from depth-first search tree
228        self.parent[root] = usize::MAX;
229        let root_idx = g.from_index(root);
230        for n_idx in g.neighbors(root_idx) {
231            let n = g.to_index(n_idx);
232            if self.parent[n] == root {
233                bcc.push(n_idx);
234                self.add_subtree(g, n, bcc)
235            }
236        }
237    }
238
239    fn next_start_pos(&self, nskip: usize) -> Option<usize> {
240        self.visited
241            .iter()
242            .skip(nskip)
243            .position(|v| !v)
244            .map(|p| p + nskip)
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use petgraph::Graph;
251
252    use super::*;
253
254    fn bcc_indices(g: &UnGraph<(), (), u32>) -> Vec<Vec<usize>> {
255        let mut bcc: Vec<Vec<usize>> = g
256            .bcc()
257            .into_iter()
258            .map(|bcc| bcc.into_iter().map(|i| g.to_index(i)).collect())
259            .collect();
260        for bcc in &mut bcc {
261            bcc.sort();
262        }
263        bcc.sort();
264        bcc
265    }
266
267    #[test]
268    fn trivial() {
269        let g: UnGraph<(), (), u32> = UnGraph::default();
270        assert!(g.bcc().is_empty());
271    }
272
273    #[test]
274    fn single_edge() {
275        let g: UnGraph<(), (), u32> = Graph::from_edges([(0, 1)]);
276        let bcc = bcc_indices(&g);
277        assert_eq!(bcc, [[0, 1]]);
278    }
279
280    #[test]
281    fn star_2() {
282        let g: UnGraph<(), (), u32> = Graph::from_edges([(0, 1), (0, 2)]);
283        let bcc = bcc_indices(&g);
284        let bcc_ref = [[0, 1], [0, 2]];
285        assert_eq!(bcc, bcc_ref);
286    }
287
288    #[test]
289    fn star_2_alt() {
290        let g: UnGraph<(), (), u32> = Graph::from_edges([(0, 1), (1, 2)]);
291        let bcc = bcc_indices(&g);
292        let bcc_ref = [[0, 1], [1, 2]];
293        assert_eq!(bcc, bcc_ref);
294    }
295
296    #[test]
297    fn lacrosse_stick() {
298        let g: UnGraph<(), (), u32> =
299            Graph::from_edges([(0, 1), (1, 2), (2, 0), (2, 3)]);
300        let bcc = bcc_indices(&g);
301        let bcc_ref = [vec![0, 1, 2], vec![2, 3]];
302        assert_eq!(bcc, bcc_ref);
303    }
304
305    #[test]
306    fn forest() {
307        let g: UnGraph<(), (), u32> =
308            Graph::from_edges([(0, 1), (1, 2), (3, 4), (3, 5)]);
309        let bcc = bcc_indices(&g);
310        let bcc_ref = [[0, 1], [1, 2], [3, 4], [3, 5]];
311        assert_eq!(bcc, bcc_ref);
312    }
313
314    #[test]
315    fn wp_example() {
316        // example taken from wikipedia
317        let g: UnGraph<(), (), u32> = Graph::from_edges([
318            (0, 1, ()),
319            (0, 9, ()),
320            (1, 2, ()),
321            (1, 6, ()),
322            (1, 8, ()),
323            (2, 3, ()),
324            (2, 4, ()),
325            (3, 4, ()),
326            (4, 5, ()),
327            (5, 6, ()),
328            (6, 7, ()),
329            (9, 10, ()),
330            (10, 11, ()),
331            (10, 13, ()),
332            (11, 12, ()),
333            (12, 13, ()),
334        ]);
335        let bcc = bcc_indices(&g);
336        let bcc_ref = [
337            vec![0, 1],
338            vec![0, 9],
339            vec![1, 2, 3, 4, 5, 6],
340            vec![1, 8],
341            vec![6, 7],
342            vec![9, 10],
343            vec![10, 11, 12, 13],
344        ];
345        assert_eq!(bcc, bcc_ref);
346    }
347
348    #[test]
349    fn self_loops() {
350        let g: UnGraph<(), (), u32> = Graph::from_edges([(0, 0, ())]);
351        let bcc = bcc_indices(&g);
352        let bcc_ref = [vec![0]];
353        assert_eq!(bcc, bcc_ref);
354
355        let g: UnGraph<(), (), u32> =
356            Graph::from_edges([(0, 0, ()), (1, 0, ()), (1, 1, ())]);
357        let bcc = bcc_indices(&g);
358        let bcc_ref = [vec![0, 1]];
359        assert_eq!(bcc, bcc_ref);
360    }
361
362    #[test]
363    fn split() {
364        let g: UnGraph<(), (), u32> = Graph::new_undirected();
365        let bccs = g.split_into_bcc();
366        assert!(bccs.is_empty());
367
368        let mut g: UnGraph<(), (), u32> = Graph::new_undirected();
369        g.add_node(());
370        let bccs = g.clone().split_into_bcc();
371        assert_eq!(bccs.len(), 1);
372        assert_eq!(bccs[0].node_count(), 1);
373        assert_eq!(bccs[0].edge_count(), 0);
374
375        let g: UnGraph<(), (), u32> = Graph::from_edges([(0, 0, ())]);
376        let bccs = g.split_into_bcc();
377        assert_eq!(bccs.len(), 1);
378        assert_eq!(bccs[0].node_count(), 1);
379        assert_eq!(bccs[0].edge_count(), 1);
380    }
381}