rust_igraph/algorithms/connectivity/
biconnected.rs1use crate::core::graph::EdgeId;
28use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct BiconnectedComponents {
33 pub count: u32,
35 pub components: Vec<Vec<VertexId>>,
37 pub tree_edges: Vec<Vec<EdgeId>>,
40 pub component_edges: Vec<Vec<EdgeId>>,
44 pub articulation_points: Vec<VertexId>,
46}
47
48#[allow(clippy::too_many_lines)] pub fn biconnected_components(graph: &Graph) -> IgraphResult<BiconnectedComponents> {
77 let n = graph.vcount();
78 let mut out = BiconnectedComponents {
79 count: 0,
80 components: Vec::new(),
81 tree_edges: Vec::new(),
82 component_edges: Vec::new(),
83 articulation_points: Vec::new(),
84 };
85 if n == 0 {
86 return Ok(out);
87 }
88 let n_us = n as usize;
89
90 let mut num: Vec<u32> = vec![0; n_us];
92 let mut low: Vec<u32> = vec![0; n_us];
93 let mut nextptr: Vec<usize> = vec![0; n_us];
94 let mut found: Vec<bool> = vec![false; n_us];
95 let mut path: Vec<VertexId> = Vec::new();
96 let mut edgestack: Vec<EdgeId> = Vec::new();
99 let mut counter: u32 = 1;
100
101 let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
103 for v in 0..n {
104 inc.push(graph.incident(v)?);
105 }
106
107 let mut comps_so_far: u32 = 0;
111 let mut vertex_added: Vec<u32> = vec![0; n_us]; for i in 0..n {
114 if low[i as usize] != 0 {
115 continue;
116 }
117 path.push(i);
118 let mut rootdfs: u32 = 0;
119 num[i as usize] = counter;
120 low[i as usize] = counter;
121 counter = counter
122 .checked_add(1)
123 .ok_or(IgraphError::Internal("DFS counter overflow"))?;
124
125 while let Some(&act) = path.last() {
126 let act_us = act as usize;
127 let nbrs = &inc[act_us];
128 let actnext = nextptr[act_us];
129
130 if actnext < nbrs.len() {
131 let edge = nbrs[actnext];
132 let nei = graph.edge_other(edge, act)?;
133 if low[nei as usize] == 0 {
134 if act == i {
135 rootdfs = rootdfs.saturating_add(1);
136 }
137 edgestack.push(edge);
138 path.push(nei);
139 num[nei as usize] = counter;
140 low[nei as usize] = counter;
141 counter = counter
142 .checked_add(1)
143 .ok_or(IgraphError::Internal("DFS counter overflow"))?;
144 } else if num[nei as usize] < low[act_us] {
145 low[act_us] = num[nei as usize];
146 }
147 nextptr[act_us] += 1;
148 } else {
149 path.pop();
151 if let Some(&prev) = path.last() {
152 let prev_us = prev as usize;
153 if low[act_us] < low[prev_us] {
154 low[prev_us] = low[act_us];
155 }
156 if low[act_us] >= num[prev_us] {
157 if !found[prev_us] && prev != i {
159 out.articulation_points.push(prev);
160 found[prev_us] = true;
161 }
162 out.count = out
163 .count
164 .checked_add(1)
165 .ok_or(IgraphError::Internal("component count overflow"))?;
166 comps_so_far = comps_so_far
167 .checked_add(1)
168 .ok_or(IgraphError::Internal("component count overflow"))?;
169
170 let mut comp_tree_edges: Vec<EdgeId> = Vec::new();
174 let mut comp_vertices: Vec<VertexId> = Vec::new();
175 while let Some(e) = edgestack.pop() {
176 let (from, to) = graph.edge(e)?;
177 comp_tree_edges.push(e);
178 if vertex_added[from as usize] != comps_so_far {
179 vertex_added[from as usize] = comps_so_far;
180 comp_vertices.push(from);
181 }
182 if vertex_added[to as usize] != comps_so_far {
183 vertex_added[to as usize] = comps_so_far;
184 comp_vertices.push(to);
185 }
186 if from == prev || to == prev {
187 break;
188 }
189 }
190 let mut comp_edges: Vec<EdgeId> = Vec::new();
197 for &vert in &comp_vertices {
198 for &e in &inc[vert as usize] {
199 let nei = graph.edge_other(e, vert)?;
200 if vertex_added[nei as usize] == comps_so_far && nei < vert {
201 comp_edges.push(e);
202 }
203 }
204 }
205 out.components.push(comp_vertices);
206 out.tree_edges.push(comp_tree_edges);
207 out.component_edges.push(comp_edges);
208 }
209 }
210 }
211 }
212
213 if rootdfs >= 2 {
214 out.articulation_points.push(i);
215 }
216 }
217
218 Ok(out)
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn sorted<T: Ord + Clone>(slice: &[T]) -> Vec<T> {
226 let mut v = slice.to_vec();
227 v.sort_unstable();
228 v
229 }
230
231 #[test]
232 fn empty_graph_yields_empty() {
233 let g = Graph::with_vertices(0);
234 let bc = biconnected_components(&g).unwrap();
235 assert_eq!(bc.count, 0);
236 assert!(bc.components.is_empty());
237 assert!(bc.tree_edges.is_empty());
238 assert!(bc.component_edges.is_empty());
239 assert!(bc.articulation_points.is_empty());
240 }
241
242 #[test]
243 fn isolated_vertices_yield_zero_components() {
244 let g = Graph::with_vertices(5);
245 let bc = biconnected_components(&g).unwrap();
246 assert_eq!(bc.count, 0);
247 assert!(bc.components.is_empty());
248 assert!(bc.component_edges.is_empty());
249 }
250
251 #[test]
252 fn two_vertices_one_edge_is_one_component() {
253 let mut g = Graph::with_vertices(2);
254 g.add_edge(0, 1).unwrap();
255 let bc = biconnected_components(&g).unwrap();
256 assert_eq!(bc.count, 1);
257 assert_eq!(sorted(&bc.components[0]), vec![0, 1]);
258 assert_eq!(bc.tree_edges[0], vec![0]);
259 assert_eq!(bc.component_edges[0], vec![0]);
260 assert!(bc.articulation_points.is_empty());
261 }
262
263 #[test]
264 fn triangle_is_single_biconnected_component() {
265 let mut g = Graph::with_vertices(3);
266 g.add_edge(0, 1).unwrap();
267 g.add_edge(1, 2).unwrap();
268 g.add_edge(2, 0).unwrap();
269 let bc = biconnected_components(&g).unwrap();
270 assert_eq!(bc.count, 1);
271 assert_eq!(sorted(&bc.components[0]), vec![0, 1, 2]);
272 assert_eq!(sorted(&bc.component_edges[0]), vec![0, 1, 2]);
274 assert_eq!(bc.tree_edges[0].len(), 2);
276 assert!(bc.articulation_points.is_empty());
277 }
278
279 #[test]
280 fn k4_complete_component_edges_has_all_six_edges() {
281 let mut g = Graph::with_vertices(4);
282 for u in 0..4u32 {
283 for v in (u + 1)..4 {
284 g.add_edge(u, v).unwrap();
285 }
286 }
287 let bc = biconnected_components(&g).unwrap();
288 assert_eq!(bc.count, 1);
289 assert_eq!(bc.component_edges[0].len(), 6);
291 assert_eq!(bc.tree_edges[0].len(), 3);
292 let mut all = bc.component_edges[0].clone();
293 all.sort_unstable();
294 assert_eq!(all, vec![0, 1, 2, 3, 4, 5]);
295 }
296
297 #[test]
298 fn pendant_component_edges_match_tree() {
299 let mut g = Graph::with_vertices(4);
302 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 0).unwrap(); g.add_edge(2, 3).unwrap(); let bc = biconnected_components(&g).unwrap();
307 assert_eq!(bc.count, 2);
308 let bridge_idx = bc
310 .components
311 .iter()
312 .position(|c| c.len() == 2)
313 .expect("bridge component");
314 assert_eq!(bc.tree_edges[bridge_idx], bc.component_edges[bridge_idx]);
315 let cycle_idx = 1 - bridge_idx;
317 assert_eq!(bc.component_edges[cycle_idx].len(), 3);
318 assert_eq!(bc.tree_edges[cycle_idx].len(), 2);
319 }
320
321 #[test]
322 fn component_edges_partition_non_bridge_edges() {
323 let mut g = Graph::with_vertices(7);
327 for &(u, v) in &[
328 (0u32, 1),
329 (1, 2),
330 (2, 0),
331 (2, 3),
332 (3, 4),
333 (4, 5),
334 (5, 3),
335 (0, 6),
336 ] {
337 g.add_edge(u, v).unwrap();
338 }
339 let bc = biconnected_components(&g).unwrap();
340 let total: usize = bc.component_edges.iter().map(Vec::len).sum();
341 assert_eq!(total, g.ecount());
343 let mut all: Vec<_> = bc.component_edges.iter().flatten().copied().collect();
345 all.sort_unstable();
346 let mut deduped = all.clone();
347 deduped.dedup();
348 assert_eq!(all, deduped);
349 }
350
351 #[test]
352 fn cycle_with_pendant_three_components() {
353 let mut g = Graph::with_vertices(5);
355 g.add_edge(0, 1).unwrap();
356 g.add_edge(1, 2).unwrap();
357 g.add_edge(2, 0).unwrap();
358 g.add_edge(2, 3).unwrap();
359 g.add_edge(3, 4).unwrap();
360 let bc = biconnected_components(&g).unwrap();
361 assert_eq!(bc.count, 3);
362 let mut aps = bc.articulation_points.clone();
363 aps.sort_unstable();
364 assert_eq!(aps, vec![2, 3]);
365 let total_vertices: usize = bc.components.iter().map(Vec::len).sum();
369 assert_eq!(total_vertices, 7);
370 }
371
372 #[test]
373 fn star_4_three_components() {
374 let mut g = Graph::with_vertices(4);
376 for v in 1..4 {
377 g.add_edge(0, v).unwrap();
378 }
379 let bc = biconnected_components(&g).unwrap();
380 assert_eq!(bc.count, 3);
381 assert_eq!(bc.articulation_points, vec![0]);
382 for comp in &bc.components {
383 assert_eq!(comp.len(), 2);
384 }
385 }
386
387 #[test]
388 fn upstream_test_fixture_produces_4_components() {
389 let mut g = Graph::with_vertices(10);
394 for &(u, v) in &[
395 (0u32, 1),
396 (1, 2),
397 (2, 3),
398 (3, 0),
399 (2, 4),
400 (4, 5),
401 (5, 2),
402 (5, 6),
403 (7, 8),
404 ] {
405 g.add_edge(u, v).unwrap();
406 }
407 let bc = biconnected_components(&g).unwrap();
408 assert_eq!(bc.count, 4);
409 let mut aps = bc.articulation_points.clone();
410 aps.sort_unstable();
411 assert_eq!(aps, vec![2, 5]);
412 }
413
414 #[test]
415 fn k4_complete_one_component() {
416 let mut g = Graph::with_vertices(4);
417 for u in 0..4u32 {
418 for v in (u + 1)..4 {
419 g.add_edge(u, v).unwrap();
420 }
421 }
422 let bc = biconnected_components(&g).unwrap();
423 assert_eq!(bc.count, 1);
424 assert_eq!(sorted(&bc.components[0]), vec![0, 1, 2, 3]);
425 assert!(bc.articulation_points.is_empty());
426 }
427
428 #[test]
429 fn aps_match_articulation_points_function() {
430 let mut g = Graph::with_vertices(7);
432 for &(u, v) in &[
433 (0u32, 1),
434 (1, 2),
435 (2, 0),
436 (2, 3),
437 (3, 4),
438 (4, 5),
439 (5, 3),
440 (0, 6),
441 ] {
442 g.add_edge(u, v).unwrap();
443 }
444 let bc = biconnected_components(&g).unwrap();
445 let mut bc_aps = bc.articulation_points.clone();
446 bc_aps.sort_unstable();
447 let mut ap = super::super::articulation::articulation_points(&g).unwrap();
448 ap.sort_unstable();
449 assert_eq!(bc_aps, ap);
450 }
451}