use crate::core::{Graph, IgraphError, IgraphResult};
type EdgeId = u32;
#[allow(clippy::cast_possible_truncation)]
pub fn is_edge_cover(graph: &Graph, cover: &[EdgeId]) -> bool {
let n = graph.vcount() as usize;
if n == 0 {
return true;
}
let mut covered = vec![false; n];
for &eid in cover {
if let Ok((u, v)) = graph.edge(eid) {
covered[u as usize] = true;
covered[v as usize] = true;
}
}
covered.iter().all(|&c| c)
}
#[allow(clippy::cast_possible_truncation)]
pub fn minimum_edge_cover(graph: &Graph) -> IgraphResult<Vec<EdgeId>> {
let n = graph.vcount();
if n == 0 {
return Ok(Vec::new());
}
let directed = graph.is_directed();
for v in 0..n {
let out_edges = graph.incident(v)?;
let has_edges = if directed {
let in_edges = graph.incident_in(v)?;
!out_edges.is_empty() || !in_edges.is_empty()
} else {
!out_edges.is_empty()
};
if !has_edges {
return Err(IgraphError::InvalidArgument(format!(
"vertex {v} has degree 0; no edge cover exists"
)));
}
}
let ecount = graph.ecount();
if ecount == 0 {
return Err(IgraphError::InvalidArgument(
"graph has vertices but no edges; no edge cover exists".into(),
));
}
let mut matched = vec![false; n as usize];
let mut cover = Vec::new();
for eid in 0..ecount as u32 {
let (u, v) = graph.edge(eid)?;
if !matched[u as usize] && !matched[v as usize] && u != v {
matched[u as usize] = true;
matched[v as usize] = true;
cover.push(eid);
}
}
for v in 0..n {
if !matched[v as usize] {
let mut edges = graph.incident(v)?;
if directed && edges.is_empty() {
edges = graph.incident_in(v)?;
}
if let Some(&eid) = edges.first() {
if !cover.contains(&eid) {
cover.push(eid);
}
matched[v as usize] = true;
}
}
}
cover.sort_unstable();
cover.dedup();
Ok(cover)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph() {
let g = Graph::with_vertices(0);
let cover = minimum_edge_cover(&g).unwrap();
assert!(cover.is_empty());
}
#[test]
fn isolated_vertex_error() {
let g = Graph::with_vertices(3);
assert!(minimum_edge_cover(&g).is_err());
}
#[test]
fn single_edge() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert_eq!(cover.len(), 1);
assert!(is_edge_cover(&g, &cover));
}
#[test]
fn path_3() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 2);
}
#[test]
fn triangle() {
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();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 2);
}
#[test]
fn star_graph() {
let mut g = Graph::with_vertices(5);
for i in 1..5u32 {
g.add_edge(0, i).unwrap();
}
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 4);
}
#[test]
fn complete_k4() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 2);
}
#[test]
fn bipartite_k22() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(1, 3).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 2);
}
#[test]
fn path_5() {
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();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 3);
}
#[test]
fn directed_graph() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
}
#[test]
fn self_loop_only() {
let mut g = Graph::with_vertices(1);
g.add_edge(0, 0).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 1);
}
#[test]
fn mixed_isolated_error() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
assert!(minimum_edge_cover(&g).is_err());
}
#[test]
fn parallel_edges() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
assert!(is_edge_cover(&g, &cover));
assert_eq!(cover.len(), 1);
}
#[test]
fn sorted_output() {
let mut g = Graph::with_vertices(4);
g.add_edge(2, 3).unwrap();
g.add_edge(0, 1).unwrap();
let cover = minimum_edge_cover(&g).unwrap();
for i in 1..cover.len() {
assert!(cover[i] > cover[i - 1]);
}
}
#[test]
fn validator_positive() {
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(); assert!(is_edge_cover(&g, &[0, 2]));
assert!(is_edge_cover(&g, &[0, 1, 2]));
}
#[test]
fn validator_negative() {
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();
assert!(!is_edge_cover(&g, &[]));
assert!(!is_edge_cover(&g, &[1])); }
}