use crate::storage::CsrGraph;
use crate::NodeId;
#[must_use]
pub fn connected_components(graph: &CsrGraph) -> usize {
let n = graph.num_nodes();
if n == 0 {
return 0;
}
let mut visited = vec![false; n];
let mut count = 0;
for start in 0..n {
if !visited[start] {
undirected_dfs(graph, start, &mut visited);
count += 1;
}
}
count
}
fn undirected_dfs(graph: &CsrGraph, node: usize, visited: &mut [bool]) {
if visited[node] {
return;
}
visited[node] = true;
#[allow(clippy::cast_possible_truncation)]
let node_id = NodeId(node as u32);
if let Ok(neighbors) = graph.outgoing_neighbors(node_id) {
for &neighbor in neighbors {
let neighbor_idx = neighbor as usize;
if !visited[neighbor_idx] {
undirected_dfs(graph, neighbor_idx, visited);
}
}
}
if let Ok(neighbors) = graph.incoming_neighbors(node_id) {
for &neighbor in neighbors {
let neighbor_idx = neighbor as usize;
if !visited[neighbor_idx] {
undirected_dfs(graph, neighbor_idx, visited);
}
}
}
}
#[must_use]
pub fn kosaraju_scc(graph: &CsrGraph) -> Vec<Vec<NodeId>> {
let n = graph.num_nodes();
if n == 0 {
return Vec::new();
}
let mut visited = vec![false; n];
let mut finish_order = Vec::with_capacity(n);
for start in 0..n {
if !visited[start] {
dfs_finish_order(graph, start, &mut visited, &mut finish_order);
}
}
let mut visited = vec![false; n];
let mut sccs = Vec::new();
for &node in finish_order.iter().rev() {
if !visited[node] {
let mut component = Vec::new();
dfs_transpose(graph, node, &mut visited, &mut component);
sccs.push(component);
}
}
sccs
}
fn dfs_finish_order(
graph: &CsrGraph,
node: usize,
visited: &mut [bool],
finish_order: &mut Vec<usize>,
) {
visited[node] = true;
#[allow(clippy::cast_possible_truncation)]
if let Ok(neighbors) = graph.outgoing_neighbors(NodeId(node as u32)) {
for &neighbor in neighbors {
let neighbor_idx = neighbor as usize;
if !visited[neighbor_idx] {
dfs_finish_order(graph, neighbor_idx, visited, finish_order);
}
}
}
finish_order.push(node);
}
fn dfs_transpose(graph: &CsrGraph, node: usize, visited: &mut [bool], component: &mut Vec<NodeId>) {
visited[node] = true;
#[allow(clippy::cast_possible_truncation)]
component.push(NodeId(node as u32));
#[allow(clippy::cast_possible_truncation)]
if let Ok(neighbors) = graph.incoming_neighbors(NodeId(node as u32)) {
for &neighbor in neighbors {
let neighbor_idx = neighbor as usize;
if !visited[neighbor_idx] {
dfs_transpose(graph, neighbor_idx, visited, component);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_graph_components() {
let graph = CsrGraph::new();
assert_eq!(connected_components(&graph), 0);
}
#[test]
fn test_empty_graph_scc() {
let graph = CsrGraph::new();
let sccs = kosaraju_scc(&graph);
assert!(sccs.is_empty());
}
#[test]
fn test_single_node_component() {
let edges = vec![(NodeId(0), NodeId(1), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 1);
}
#[test]
fn test_two_disconnected_edges() {
let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(2), NodeId(3), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 2);
}
#[test]
fn test_chain_single_component() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(2), 1.0),
(NodeId(2), NodeId(3), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 1);
}
#[test]
fn test_diamond_single_component() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(0), NodeId(2), 1.0),
(NodeId(1), NodeId(3), 1.0),
(NodeId(2), NodeId(3), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 1);
}
#[test]
fn test_scc_dag_each_node_separate() {
let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(2), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 3);
}
#[test]
fn test_scc_simple_cycle() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(2), 1.0),
(NodeId(2), NodeId(0), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 3);
}
#[test]
fn test_scc_two_node_cycle() {
let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(0), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 2);
}
#[test]
fn test_scc_self_loop() {
let edges = vec![(NodeId(0), NodeId(0), 1.0)];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 1);
assert_eq!(sccs[0].len(), 1);
}
#[test]
fn test_scc_two_separate_cycles() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(0), 1.0),
(NodeId(2), NodeId(3), 1.0),
(NodeId(3), NodeId(2), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 2);
assert!(sccs.iter().all(|scc| scc.len() == 2));
}
#[test]
fn test_scc_complex_graph() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(0), 1.0),
(NodeId(1), NodeId(2), 1.0), (NodeId(2), NodeId(3), 1.0),
(NodeId(3), NodeId(2), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 2);
}
#[test]
fn test_scc_disconnected_with_cycles() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(2), 1.0),
(NodeId(2), NodeId(0), 1.0),
(NodeId(3), NodeId(4), 1.0),
(NodeId(4), NodeId(3), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
let sccs = kosaraju_scc(&graph);
assert_eq!(sccs.len(), 2);
let sizes: Vec<_> = sccs.iter().map(std::vec::Vec::len).collect();
assert!(sizes.contains(&3));
assert!(sizes.contains(&2));
}
#[test]
fn test_connected_components_with_cycle() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(1), NodeId(2), 1.0),
(NodeId(2), NodeId(0), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 1);
}
#[test]
fn test_three_separate_components() {
let edges = vec![
(NodeId(0), NodeId(1), 1.0),
(NodeId(2), NodeId(3), 1.0),
(NodeId(4), NodeId(5), 1.0),
];
let graph = CsrGraph::from_edge_list(&edges).unwrap();
assert_eq!(connected_components(&graph), 3);
}
}