1use crate::storage::CsrGraph;
28use crate::NodeId;
29
30#[must_use]
59pub fn connected_components(graph: &CsrGraph) -> usize {
60 let n = graph.num_nodes();
61 if n == 0 {
62 return 0;
63 }
64
65 let mut visited = vec![false; n];
66 let mut count = 0;
67
68 for start in 0..n {
69 if !visited[start] {
70 undirected_dfs(graph, start, &mut visited);
72 count += 1;
73 }
74 }
75
76 count
77}
78
79fn undirected_dfs(graph: &CsrGraph, node: usize, visited: &mut [bool]) {
81 if visited[node] {
82 return;
83 }
84 visited[node] = true;
85
86 #[allow(clippy::cast_possible_truncation)]
87 let node_id = NodeId(node as u32);
88
89 if let Ok(neighbors) = graph.outgoing_neighbors(node_id) {
91 for &neighbor in neighbors {
92 let neighbor_idx = neighbor as usize;
93 if !visited[neighbor_idx] {
94 undirected_dfs(graph, neighbor_idx, visited);
95 }
96 }
97 }
98
99 if let Ok(neighbors) = graph.incoming_neighbors(node_id) {
101 for &neighbor in neighbors {
102 let neighbor_idx = neighbor as usize;
103 if !visited[neighbor_idx] {
104 undirected_dfs(graph, neighbor_idx, visited);
105 }
106 }
107 }
108}
109
110#[must_use]
150pub fn kosaraju_scc(graph: &CsrGraph) -> Vec<Vec<NodeId>> {
151 let n = graph.num_nodes();
152 if n == 0 {
153 return Vec::new();
154 }
155
156 let mut visited = vec![false; n];
158 let mut finish_order = Vec::with_capacity(n);
159
160 for start in 0..n {
161 if !visited[start] {
162 dfs_finish_order(graph, start, &mut visited, &mut finish_order);
163 }
164 }
165
166 let mut visited = vec![false; n];
168 let mut sccs = Vec::new();
169
170 for &node in finish_order.iter().rev() {
171 if !visited[node] {
172 let mut component = Vec::new();
173 dfs_transpose(graph, node, &mut visited, &mut component);
174 sccs.push(component);
175 }
176 }
177
178 sccs
179}
180
181fn dfs_finish_order(
183 graph: &CsrGraph,
184 node: usize,
185 visited: &mut [bool],
186 finish_order: &mut Vec<usize>,
187) {
188 visited[node] = true;
189
190 #[allow(clippy::cast_possible_truncation)]
191 if let Ok(neighbors) = graph.outgoing_neighbors(NodeId(node as u32)) {
192 for &neighbor in neighbors {
193 let neighbor_idx = neighbor as usize;
194 if !visited[neighbor_idx] {
195 dfs_finish_order(graph, neighbor_idx, visited, finish_order);
196 }
197 }
198 }
199
200 finish_order.push(node);
201}
202
203fn dfs_transpose(graph: &CsrGraph, node: usize, visited: &mut [bool], component: &mut Vec<NodeId>) {
205 visited[node] = true;
206
207 #[allow(clippy::cast_possible_truncation)]
208 component.push(NodeId(node as u32));
209
210 #[allow(clippy::cast_possible_truncation)]
212 if let Ok(neighbors) = graph.incoming_neighbors(NodeId(node as u32)) {
213 for &neighbor in neighbors {
214 let neighbor_idx = neighbor as usize;
215 if !visited[neighbor_idx] {
216 dfs_transpose(graph, neighbor_idx, visited, component);
217 }
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_empty_graph_components() {
228 let graph = CsrGraph::new();
229 assert_eq!(connected_components(&graph), 0);
230 }
231
232 #[test]
233 fn test_empty_graph_scc() {
234 let graph = CsrGraph::new();
235 let sccs = kosaraju_scc(&graph);
236 assert!(sccs.is_empty());
237 }
238
239 #[test]
240 fn test_single_node_component() {
241 let edges = vec![(NodeId(0), NodeId(1), 1.0)];
242 let graph = CsrGraph::from_edge_list(&edges).unwrap();
243 assert_eq!(connected_components(&graph), 1);
245 }
246
247 #[test]
248 fn test_two_disconnected_edges() {
249 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(2), NodeId(3), 1.0)];
250 let graph = CsrGraph::from_edge_list(&edges).unwrap();
251 assert_eq!(connected_components(&graph), 2);
252 }
253
254 #[test]
255 fn test_chain_single_component() {
256 let edges = vec![
258 (NodeId(0), NodeId(1), 1.0),
259 (NodeId(1), NodeId(2), 1.0),
260 (NodeId(2), NodeId(3), 1.0),
261 ];
262 let graph = CsrGraph::from_edge_list(&edges).unwrap();
263 assert_eq!(connected_components(&graph), 1);
264 }
265
266 #[test]
267 fn test_diamond_single_component() {
268 let edges = vec![
270 (NodeId(0), NodeId(1), 1.0),
271 (NodeId(0), NodeId(2), 1.0),
272 (NodeId(1), NodeId(3), 1.0),
273 (NodeId(2), NodeId(3), 1.0),
274 ];
275 let graph = CsrGraph::from_edge_list(&edges).unwrap();
276 assert_eq!(connected_components(&graph), 1);
277 }
278
279 #[test]
280 fn test_scc_dag_each_node_separate() {
281 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(2), 1.0)];
283 let graph = CsrGraph::from_edge_list(&edges).unwrap();
284 let sccs = kosaraju_scc(&graph);
285 assert_eq!(sccs.len(), 3);
286 }
287
288 #[test]
289 fn test_scc_simple_cycle() {
290 let edges = vec![
292 (NodeId(0), NodeId(1), 1.0),
293 (NodeId(1), NodeId(2), 1.0),
294 (NodeId(2), NodeId(0), 1.0),
295 ];
296 let graph = CsrGraph::from_edge_list(&edges).unwrap();
297 let sccs = kosaraju_scc(&graph);
298 assert_eq!(sccs.len(), 1);
300 assert_eq!(sccs[0].len(), 3);
301 }
302
303 #[test]
304 fn test_scc_two_node_cycle() {
305 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(0), 1.0)];
307 let graph = CsrGraph::from_edge_list(&edges).unwrap();
308 let sccs = kosaraju_scc(&graph);
309 assert_eq!(sccs.len(), 1);
310 assert_eq!(sccs[0].len(), 2);
311 }
312
313 #[test]
314 fn test_scc_self_loop() {
315 let edges = vec![(NodeId(0), NodeId(0), 1.0)];
317 let graph = CsrGraph::from_edge_list(&edges).unwrap();
318 let sccs = kosaraju_scc(&graph);
319 assert_eq!(sccs.len(), 1);
320 assert_eq!(sccs[0].len(), 1);
321 }
322
323 #[test]
324 fn test_scc_two_separate_cycles() {
325 let edges = vec![
327 (NodeId(0), NodeId(1), 1.0),
328 (NodeId(1), NodeId(0), 1.0),
329 (NodeId(2), NodeId(3), 1.0),
330 (NodeId(3), NodeId(2), 1.0),
331 ];
332 let graph = CsrGraph::from_edge_list(&edges).unwrap();
333 let sccs = kosaraju_scc(&graph);
334 assert_eq!(sccs.len(), 2);
335 assert!(sccs.iter().all(|scc| scc.len() == 2));
336 }
337
338 #[test]
339 fn test_scc_complex_graph() {
340 let edges = vec![
343 (NodeId(0), NodeId(1), 1.0),
344 (NodeId(1), NodeId(0), 1.0),
345 (NodeId(1), NodeId(2), 1.0), (NodeId(2), NodeId(3), 1.0),
347 (NodeId(3), NodeId(2), 1.0),
348 ];
349 let graph = CsrGraph::from_edge_list(&edges).unwrap();
350 let sccs = kosaraju_scc(&graph);
351 assert_eq!(sccs.len(), 2);
352 }
353
354 #[test]
355 fn test_scc_disconnected_with_cycles() {
356 let edges = vec![
358 (NodeId(0), NodeId(1), 1.0),
359 (NodeId(1), NodeId(2), 1.0),
360 (NodeId(2), NodeId(0), 1.0),
361 (NodeId(3), NodeId(4), 1.0),
362 (NodeId(4), NodeId(3), 1.0),
363 ];
364 let graph = CsrGraph::from_edge_list(&edges).unwrap();
365 let sccs = kosaraju_scc(&graph);
366 assert_eq!(sccs.len(), 2);
367 let sizes: Vec<_> = sccs.iter().map(std::vec::Vec::len).collect();
369 assert!(sizes.contains(&3));
370 assert!(sizes.contains(&2));
371 }
372
373 #[test]
374 fn test_connected_components_with_cycle() {
375 let edges = vec![
377 (NodeId(0), NodeId(1), 1.0),
378 (NodeId(1), NodeId(2), 1.0),
379 (NodeId(2), NodeId(0), 1.0),
380 ];
381 let graph = CsrGraph::from_edge_list(&edges).unwrap();
382 assert_eq!(connected_components(&graph), 1);
383 }
384
385 #[test]
386 fn test_three_separate_components() {
387 let edges = vec![
388 (NodeId(0), NodeId(1), 1.0),
389 (NodeId(2), NodeId(3), 1.0),
390 (NodeId(4), NodeId(5), 1.0),
391 ];
392 let graph = CsrGraph::from_edge_list(&edges).unwrap();
393 assert_eq!(connected_components(&graph), 3);
394 }
395}