rust_igraph/algorithms/connectivity/
components.rs1use std::collections::VecDeque;
12
13use crate::core::{Graph, IgraphResult, VertexId};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct ConnectedComponents {
18 pub membership: Vec<u32>,
20 pub count: u32,
22}
23
24pub fn connected_components(graph: &Graph) -> IgraphResult<ConnectedComponents> {
50 let n = graph.vcount();
51 if n == 0 {
52 return Ok(ConnectedComponents {
53 membership: Vec::new(),
54 count: 0,
55 });
56 }
57
58 let mut membership = vec![u32::MAX; n as usize];
59 let mut count: u32 = 0;
60 let mut queue: VecDeque<VertexId> = VecDeque::new();
61
62 for start in 0..n {
63 if membership[start as usize] != u32::MAX {
64 continue;
65 }
66 let component_id = count;
67 count = count
68 .checked_add(1)
69 .ok_or(crate::core::IgraphError::Internal("too many components"))?;
70
71 membership[start as usize] = component_id;
72 queue.clear();
73 queue.push_back(start);
74 while let Some(v) = queue.pop_front() {
75 for w in graph.neighbors_iter(v)? {
76 if membership[w as usize] == u32::MAX {
77 membership[w as usize] = component_id;
78 queue.push_back(w);
79 }
80 }
81 }
82 }
83
84 Ok(ConnectedComponents { membership, count })
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn empty_graph() {
93 let g = Graph::with_vertices(0);
94 let cc = connected_components(&g).unwrap();
95 assert_eq!(cc.count, 0);
96 assert!(cc.membership.is_empty());
97 }
98
99 #[test]
100 fn n_isolated_vertices_yields_n_components() {
101 let g = Graph::with_vertices(5);
102 let cc = connected_components(&g).unwrap();
103 assert_eq!(cc.count, 5);
104 assert_eq!(cc.membership, vec![0, 1, 2, 3, 4]);
106 }
107
108 #[test]
109 fn single_path_is_one_component() {
110 let mut g = Graph::with_vertices(4);
111 g.add_edge(0, 1).unwrap();
112 g.add_edge(1, 2).unwrap();
113 g.add_edge(2, 3).unwrap();
114 let cc = connected_components(&g).unwrap();
115 assert_eq!(cc.count, 1);
116 assert_eq!(cc.membership, vec![0, 0, 0, 0]);
117 }
118
119 #[test]
120 fn two_disjoint_components_are_distinct() {
121 let mut g = Graph::with_vertices(5);
122 g.add_edge(0, 1).unwrap();
123 g.add_edge(1, 2).unwrap();
124 g.add_edge(3, 4).unwrap();
125 let cc = connected_components(&g).unwrap();
126 assert_eq!(cc.count, 2);
127 assert_eq!(cc.membership, vec![0, 0, 0, 1, 1]);
129 }
130
131 #[test]
132 fn self_loop_does_not_split_component() {
133 let mut g = Graph::with_vertices(3);
134 g.add_edge(0, 0).unwrap();
135 g.add_edge(0, 1).unwrap();
136 g.add_edge(1, 2).unwrap();
137 let cc = connected_components(&g).unwrap();
138 assert_eq!(cc.count, 1);
139 assert_eq!(cc.membership, vec![0, 0, 0]);
140 }
141
142 #[test]
143 fn directed_graph_uses_weak_connectivity() {
144 let mut g = Graph::new(3, true).unwrap();
146 g.add_edge(0, 1).unwrap();
147 g.add_edge(1, 2).unwrap();
148 let cc = connected_components(&g).unwrap();
149 assert_eq!(cc.count, 1);
150 assert_eq!(cc.membership, vec![0, 0, 0]);
151 }
152
153 #[test]
154 fn membership_ids_are_dense() {
155 let mut g = Graph::with_vertices(6);
157 g.add_edge(0, 1).unwrap();
158 g.add_edge(2, 3).unwrap();
159 g.add_edge(4, 5).unwrap();
160 let cc = connected_components(&g).unwrap();
161 assert_eq!(cc.count, 3);
162 let mut seen: Vec<u32> = cc.membership.clone();
163 seen.sort_unstable();
164 seen.dedup();
165 assert_eq!(seen, vec![0, 1, 2]);
166 }
167}