use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn bridges(graph: &Graph) -> IgraphResult<Vec<EdgeId>> {
let n = graph.vcount();
if n == 0 {
return Ok(Vec::new());
}
let n_us = n as usize;
let mut visited: Vec<bool> = vec![false; n_us];
let mut vis: Vec<u32> = vec![0; n_us];
let mut low: Vec<u32> = vec![0; n_us];
let mut incoming_edge: Vec<Option<EdgeId>> = vec![None; n_us];
let mut su: Vec<VertexId> = Vec::new();
let mut si: Vec<usize> = Vec::new();
let mut time: u32 = 0;
let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
for v in 0..n {
inc.push(graph.incident(v)?);
}
let mut bridges_out: Vec<EdgeId> = Vec::new();
for start in 0..n {
if visited[start as usize] {
continue;
}
su.push(start);
si.push(0);
while let Some(u) = su.pop() {
let i = si.pop().expect("si parallel to su");
let u_us = u as usize;
if i == 0 {
visited[u_us] = true;
time = time
.checked_add(1)
.ok_or(IgraphError::Internal("DFS time counter overflow"))?;
vis[u_us] = time;
low[u_us] = time;
}
let edges = &inc[u_us];
if i < edges.len() {
su.push(u);
si.push(i + 1);
let edge = edges[i];
let v = graph.edge_other(edge, u)?;
if !visited[v as usize] {
incoming_edge[v as usize] = Some(edge);
su.push(v);
si.push(0);
} else if Some(edge) != incoming_edge[u_us] {
if vis[v as usize] < low[u_us] {
low[u_us] = vis[v as usize];
}
}
} else {
if let Some(edge) = incoming_edge[u_us] {
let w = graph.edge_other(edge, u)?; let w_us = w as usize;
if low[u_us] < low[w_us] {
low[w_us] = low[u_us];
}
if low[u_us] > vis[w_us] {
bridges_out.push(edge);
}
}
}
}
}
Ok(bridges_out)
}
#[cfg(test)]
mod tests {
use super::*;
fn brsorted(g: &Graph) -> Vec<EdgeId> {
let mut b = bridges(g).unwrap();
b.sort_unstable();
b
}
#[test]
fn empty_graph_has_no_bridges() {
let g = Graph::with_vertices(0);
assert!(bridges(&g).unwrap().is_empty());
}
#[test]
fn isolated_vertices_have_no_bridges() {
let g = Graph::with_vertices(5);
assert!(bridges(&g).unwrap().is_empty());
}
#[test]
fn cycle_has_no_bridges() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
assert!(bridges(&g).unwrap().is_empty());
}
#[test]
fn path_every_edge_is_a_bridge() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(brsorted(&g), vec![0, 1, 2, 3]);
}
#[test]
fn cycle_with_pendant_bridges_are_pendant_edges() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 0).unwrap(); g.add_edge(2, 3).unwrap(); g.add_edge(3, 4).unwrap(); assert_eq!(brsorted(&g), vec![3, 4]);
}
#[test]
fn parallel_edges_make_neither_a_bridge() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert!(bridges(&g).unwrap().is_empty());
}
#[test]
fn self_loop_is_not_a_bridge() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap(); assert_eq!(brsorted(&g), vec![1]);
}
#[test]
fn two_components_each_contributes_independently() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
assert_eq!(brsorted(&g), vec![0, 1, 2, 3]);
}
#[test]
fn two_triangles_joined_by_bridge() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
g.add_edge(3, 4).unwrap();
g.add_edge(4, 5).unwrap();
g.add_edge(5, 3).unwrap();
g.add_edge(2, 3).unwrap(); assert_eq!(brsorted(&g), vec![6]);
}
#[test]
fn star_every_edge_is_a_bridge() {
let mut g = Graph::with_vertices(5);
for v in 1..5 {
g.add_edge(0, v).unwrap();
}
assert_eq!(brsorted(&g), vec![0, 1, 2, 3]);
}
}