use crate::core::{Graph, IgraphResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectednessMode {
Weak,
Strong,
}
pub fn is_connected(graph: &Graph, mode: ConnectednessMode) -> IgraphResult<bool> {
let n = graph.vcount();
if n <= 1 {
return Ok(true);
}
if mode == ConnectednessMode::Strong && graph.is_directed() {
return is_strongly_connected(graph);
}
is_weakly_connected(graph)
}
fn is_weakly_connected(graph: &Graph) -> IgraphResult<bool> {
let n = graph.vcount();
let nn = n as usize;
let mut adj: Vec<Vec<u32>> = vec![Vec::new(); nn];
let ecount = graph.ecount();
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
if src != tgt {
adj[src as usize].push(tgt);
adj[tgt as usize].push(src);
}
}
let mut visited = vec![false; nn];
let mut queue = std::collections::VecDeque::new();
visited[0] = true;
queue.push_back(0u32);
let mut count = 1u32;
while let Some(v) = queue.pop_front() {
for &nb in &adj[v as usize] {
if !visited[nb as usize] {
visited[nb as usize] = true;
count += 1;
queue.push_back(nb);
}
}
}
Ok(count == n)
}
fn is_strongly_connected(graph: &Graph) -> IgraphResult<bool> {
let n = graph.vcount();
let nn = n as usize;
let ecount = graph.ecount();
let mut fwd: Vec<Vec<u32>> = vec![Vec::new(); nn];
let mut rev: Vec<Vec<u32>> = vec![Vec::new(); nn];
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let (src, tgt) = graph.edge(eid as u32)?;
if src != tgt {
fwd[src as usize].push(tgt);
rev[tgt as usize].push(src);
}
}
let forward_count = bfs_count_adj(&fwd, n, 0);
if forward_count != n {
return Ok(false);
}
let backward_count = bfs_count_adj(&rev, n, 0);
Ok(backward_count == n)
}
fn bfs_count_adj(adj: &[Vec<u32>], n: u32, start: u32) -> u32 {
let mut visited = vec![false; n as usize];
let mut queue = std::collections::VecDeque::new();
visited[start as usize] = true;
queue.push_back(start);
let mut count = 1u32;
while let Some(v) = queue.pop_front() {
for &nb in &adj[v as usize] {
if !visited[nb as usize] {
visited[nb as usize] = true;
count += 1;
queue.push_back(nb);
}
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_graph() {
let g = Graph::with_vertices(0);
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_single_vertex() {
let g = Graph::with_vertices(1);
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
assert!(is_connected(&g, ConnectednessMode::Strong).unwrap());
}
#[test]
fn test_two_connected() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_two_disconnected() {
let g = Graph::with_vertices(2);
assert!(!is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_path_connected() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_two_components() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
assert!(!is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_directed_weakly_connected() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_directed_not_strongly_connected() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert!(!is_connected(&g, ConnectednessMode::Strong).unwrap());
}
#[test]
fn test_directed_strongly_connected() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert!(is_connected(&g, ConnectednessMode::Strong).unwrap());
}
#[test]
fn test_complete_graph() {
let mut g = Graph::with_vertices(5);
for i in 0..5u32 {
for j in (i + 1)..5 {
g.add_edge(i, j).unwrap();
}
}
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_star_connected() {
let mut g = Graph::with_vertices(5);
for i in 1..5u32 {
g.add_edge(0, i).unwrap();
}
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
}
#[test]
fn test_directed_star_weak_not_strong() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
assert!(is_connected(&g, ConnectednessMode::Weak).unwrap());
assert!(!is_connected(&g, ConnectednessMode::Strong).unwrap());
}
}