use std::collections::VecDeque;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn girth(graph: &Graph) -> IgraphResult<Option<u32>> {
let n = graph.vcount();
if n < 3 {
return Ok(None);
}
let n_us = n as usize;
let mut adj: Vec<Vec<VertexId>> = Vec::with_capacity(n_us);
for v in 0..n {
let raw = graph.neighbors(v)?;
let mut simple: Vec<VertexId> = raw.into_iter().filter(|&u| u != v).collect();
simple.sort_unstable();
simple.dedup();
adj.push(simple);
}
let mut mincirc: u32 = u32::MAX;
let mut stoplevel: u32 = (n + 1).max(2);
let mut triangle = false;
let mut level: Vec<u32> = vec![0; n_us];
let mut q: VecDeque<VertexId> = VecDeque::new();
let mut node = 0u32;
while !triangle && node < n {
if node == 1 && mincirc == u32::MAX {
let cc = crate::algorithms::connectivity::connected_components(graph)?;
if cc.count == 1 {
break;
}
}
q.clear();
level.fill(0);
q.push_back(node);
level[node as usize] = 1;
while let Some(actnode) = q.pop_front() {
let actlevel = level[actnode as usize];
if actlevel >= stoplevel {
break;
}
let neilist = &adj[actnode as usize];
for &nei in neilist {
let neilevel = level[nei as usize];
if neilevel != 0 {
if neilevel == actlevel - 1 {
continue;
}
stoplevel = neilevel;
let candidate = actlevel + neilevel - 1;
if candidate < mincirc {
mincirc = candidate;
if neilevel == 2 {
triangle = true;
}
}
if neilevel == actlevel {
break;
}
} else {
q.push_back(nei);
level[nei as usize] = actlevel + 1;
}
}
}
node += 1;
}
if mincirc == u32::MAX {
Ok(None)
} else {
Ok(Some(mincirc))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_has_no_girth() {
let g = Graph::with_vertices(0);
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn singleton_has_no_girth() {
let g = Graph::with_vertices(1);
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn isolated_vertices_have_no_girth() {
let g = Graph::with_vertices(5);
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn tree_has_no_girth() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn triangle_girth_is_3() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(girth(&g).unwrap(), Some(3));
}
#[test]
fn square_4_cycle_has_girth_4() {
let mut g = Graph::with_vertices(4);
for i in 0..4u32 {
g.add_edge(i, (i + 1) % 4).unwrap();
}
assert_eq!(girth(&g).unwrap(), Some(4));
}
#[test]
fn pentagon_has_girth_5() {
let mut g = Graph::with_vertices(5);
for i in 0..5u32 {
g.add_edge(i, (i + 1) % 5).unwrap();
}
assert_eq!(girth(&g).unwrap(), Some(5));
}
#[test]
fn k4_has_girth_3() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
assert_eq!(girth(&g).unwrap(), Some(3));
}
#[test]
fn self_loop_does_not_count_as_cycle() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn parallel_edges_do_not_count_as_cycle() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(girth(&g).unwrap(), None);
}
#[test]
fn two_components_picks_smaller_girth() {
let mut g = Graph::with_vertices(7);
for i in 0..4u32 {
g.add_edge(i, (i + 1) % 4).unwrap();
}
g.add_edge(4, 5).unwrap();
g.add_edge(5, 6).unwrap();
g.add_edge(6, 4).unwrap();
assert_eq!(girth(&g).unwrap(), Some(3));
}
#[test]
fn pendant_does_not_change_cycle_girth() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(2, 3).unwrap();
assert_eq!(girth(&g).unwrap(), Some(3));
}
}