use crate::error::GraphalgResult;
use crate::repr::adjacency_list::AdjacencyList;
use crate::shortest_path::transitive_closure::transitive_closure;
use crate::topological::kahn::topo_sort_kahn;
pub fn transitive_reduction(graph: &AdjacencyList) -> GraphalgResult<AdjacencyList> {
let n = graph.n;
topo_sort_kahn(graph)?;
let tc = transitive_closure(graph, false)?;
let reach = tc.matrix();
let mut succ: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut seen: Vec<usize> = vec![usize::MAX; n];
for u in 0..n {
for &v in graph.neighbors(u)? {
if v == u {
continue;
}
if seen[v] != u {
seen[v] = u;
succ[u].push(v);
}
}
}
let mut reduced = AdjacencyList::new(n);
for u in 0..n {
for &v in &succ[u] {
let mut redundant = false;
for &w in &succ[u] {
if w == v || w == u {
continue;
}
if reach[w * n + v] {
redundant = true;
break;
}
}
if !redundant {
reduced.add_edge(u, v)?;
}
}
}
Ok(reduced)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::GraphalgError;
fn edge_set(g: &AdjacencyList) -> Vec<(usize, usize)> {
let mut e: Vec<(usize, usize)> = Vec::new();
for u in 0..g.n {
for &v in &g.edges[u] {
e.push((u, v));
}
}
e.sort_unstable();
e.dedup();
e
}
#[test]
fn drops_single_shortcut() {
let mut g = AdjacencyList::new(3);
g.add_edge(0, 1).expect("edge");
g.add_edge(1, 2).expect("edge");
g.add_edge(0, 2).expect("edge");
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(edge_set(&r), vec![(0, 1), (1, 2)]);
}
#[test]
fn diamond_drops_long_shortcut() {
let mut g = AdjacencyList::new(4);
for (u, v) in [(0, 1), (0, 2), (1, 3), (2, 3), (0, 3)] {
g.add_edge(u, v).expect("edge");
}
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(edge_set(&r), vec![(0, 1), (0, 2), (1, 3), (2, 3)]);
}
#[test]
fn already_reduced_chain_unchanged() {
let mut g = AdjacencyList::new(4);
for i in 0..3 {
g.add_edge(i, i + 1).expect("edge");
}
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(edge_set(&r), edge_set(&g));
}
#[test]
fn already_reduced_tree_unchanged() {
let mut g = AdjacencyList::new(7);
for (u, v) in [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)] {
g.add_edge(u, v).expect("edge");
}
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(edge_set(&r), edge_set(&g));
}
#[test]
fn reduction_preserves_closure() {
let mut g = AdjacencyList::new(6);
for (u, v) in [
(0, 1),
(0, 2),
(0, 3),
(0, 5),
(1, 3),
(1, 4),
(2, 4),
(3, 5),
(4, 5),
(2, 5),
] {
g.add_edge(u, v).expect("edge");
}
let r = transitive_reduction(&g).expect("reduction");
let cg = transitive_closure(&g, false).expect("closure orig");
let cr = transitive_closure(&r, false).expect("closure reduced");
assert_eq!(cg.matrix(), cr.matrix());
assert!(r.num_edges() <= g.num_edges());
}
#[test]
fn cyclic_graph_is_not_a_dag() {
let mut g = AdjacencyList::new(3);
g.add_edge(0, 1).expect("edge");
g.add_edge(1, 2).expect("edge");
g.add_edge(2, 0).expect("edge");
assert!(matches!(
transitive_reduction(&g),
Err(GraphalgError::NotADag)
));
}
#[test]
fn hasse_diagram_of_divisibility_poset() {
let labels = [1usize, 2, 3, 4, 6, 12];
let mut g = AdjacencyList::new(6);
for i in 0..6 {
for j in 0..6 {
if i != j && labels[j] % labels[i] == 0 {
g.add_edge(i, j).expect("edge"); }
}
}
let r = transitive_reduction(&g).expect("reduction");
let expected = vec![
(0, 1), (0, 2), (1, 3), (1, 4), (2, 4), (3, 5), (4, 5), ];
assert_eq!(edge_set(&r), expected);
let cg = transitive_closure(&g, false).expect("c orig");
let cr = transitive_closure(&r, false).expect("c reduced");
assert_eq!(cg.matrix(), cr.matrix());
}
#[test]
fn empty_graph_reduces_to_empty() {
let g = AdjacencyList::new(0);
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(r.n, 0);
assert_eq!(r.num_edges(), 0);
}
#[test]
fn single_vertex_no_edges() {
let g = AdjacencyList::new(1);
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(r.n, 1);
assert_eq!(r.num_edges(), 0);
}
#[test]
fn parallel_edges_collapsed() {
let mut g = AdjacencyList::new(2);
g.add_edge(0, 1).expect("edge");
g.add_edge(0, 1).expect("edge");
let r = transitive_reduction(&g).expect("reduction");
assert_eq!(edge_set(&r), vec![(0, 1)]);
assert_eq!(r.num_edges(), 1);
}
}