use std::collections::HashSet;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FastMinimumDegreeError {
EmptyAdjacencyList,
NeighborIndexOutOfBounds {
node: usize,
neighbor: usize,
len: usize,
},
InvalidGraphStructure,
}
impl fmt::Display for FastMinimumDegreeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FastMinimumDegreeError::EmptyAdjacencyList => write!(f, "Adjacency list is empty"),
FastMinimumDegreeError::NeighborIndexOutOfBounds {
node,
neighbor,
len,
} => write!(
f,
"Neighbor index {neighbor} out of bounds for node {node} (adjacency list length {len})"
),
FastMinimumDegreeError::InvalidGraphStructure => {
write!(f, "Invalid graph structure")
}
}
}
}
impl std::error::Error for FastMinimumDegreeError {}
pub fn fast_minimum_degree(adj_list: &[Vec<usize>]) -> Result<Vec<usize>, FastMinimumDegreeError> {
let n = adj_list.len();
if n == 0 {
return Err(FastMinimumDegreeError::EmptyAdjacencyList);
}
if let Some((i, nbr)) = adj_list
.iter()
.enumerate()
.flat_map(|(i, nbrs)| nbrs.iter().map(move |&nbr| (i, nbr)))
.find(|&(_, nbr)| nbr >= n)
{
return Err(FastMinimumDegreeError::NeighborIndexOutOfBounds {
node: i,
neighbor: nbr,
len: n,
});
}
let mut fill_graph = vec![HashSet::new(); n];
for (i, neighbors) in adj_list.iter().enumerate() {
for &neighbor in neighbors {
fill_graph[i].insert(neighbor);
fill_graph[neighbor].insert(i);
}
}
let mut hyperedges = Vec::new();
for (i, neighbors) in adj_list.iter().enumerate() {
for &neighbor in neighbors {
if i < neighbor {
hyperedges.push(HashSet::from([i, neighbor]));
}
}
}
let mut active = vec![true; n];
let mut elimination_ordering = Vec::with_capacity(n);
for _ in 0..n {
let a = (0..n)
.into_iter()
.filter(|&i| active[i])
.min_by_key(|&i| fill_graph[i].len())
.ok_or(FastMinimumDegreeError::InvalidGraphStructure)?;
active[a] = false;
elimination_ordering.push(a);
let mut w = HashSet::new();
let mut new_hyperedges = Vec::new();
for hyperedge in &hyperedges {
if hyperedge.contains(&a) {
let mut u = hyperedge.clone();
u.remove(&a);
if !u.is_empty() {
let x: HashSet<_> = w.difference(&u).cloned().collect();
let y: HashSet<_> = u.difference(&w).cloned().collect();
for &x_vertex in &x {
for &y_vertex in &y {
fill_graph[x_vertex].insert(y_vertex);
fill_graph[y_vertex].insert(x_vertex);
}
}
for &b in &y {
fill_graph[a].remove(&b);
fill_graph[b].remove(&a);
}
w.extend(y);
}
} else {
new_hyperedges.push(hyperedge.clone());
}
}
if !w.is_empty() {
new_hyperedges.push(w);
}
hyperedges = new_hyperedges;
}
Ok(elimination_ordering)
}
pub fn fast_minimum_degree_with_stats(
adj_list: &[Vec<usize>],
) -> Result<(Vec<usize>, usize, Vec<HashSet<usize>>), FastMinimumDegreeError> {
let n = adj_list.len();
if n == 0 {
return Err(FastMinimumDegreeError::EmptyAdjacencyList);
}
if let Some((i, nbr)) = adj_list
.iter()
.enumerate()
.flat_map(|(i, nbrs)| nbrs.iter().map(move |&nbr| (i, nbr)))
.find(|&(_, nbr)| nbr >= n)
{
return Err(FastMinimumDegreeError::NeighborIndexOutOfBounds {
node: i,
neighbor: nbr,
len: n,
});
}
let mut fill_graph = vec![HashSet::new(); n];
let mut original_edges = HashSet::new();
for (i, neighbors) in adj_list.iter().enumerate() {
for &neighbor in neighbors {
fill_graph[i].insert(neighbor);
fill_graph[neighbor].insert(i);
if i < neighbor {
original_edges.insert((i, neighbor));
} else {
original_edges.insert((neighbor, i));
}
}
}
let mut hyperedges = Vec::new();
for (i, neighbors) in adj_list.iter().enumerate() {
for &neighbor in neighbors {
if i < neighbor {
hyperedges.push(HashSet::from([i, neighbor]));
}
}
}
let mut active = vec![true; n];
let mut elimination_ordering = Vec::with_capacity(n);
let mut fill_count = 0;
for _ in 0..n {
let a = (0..n)
.into_iter()
.filter(|&i| active[i])
.min_by_key(|&i| fill_graph[i].len())
.ok_or(FastMinimumDegreeError::InvalidGraphStructure)?;
active[a] = false;
elimination_ordering.push(a);
let mut w = HashSet::new();
let mut new_hyperedges = Vec::new();
for hyperedge in &hyperedges {
if hyperedge.contains(&a) {
let mut u = hyperedge.clone();
u.remove(&a);
if !u.is_empty() {
let x: HashSet<_> = w.difference(&u).cloned().collect();
let y: HashSet<_> = u.difference(&w).cloned().collect();
for &x_vertex in &x {
for &y_vertex in &y {
if !original_edges.contains(&(x_vertex.min(y_vertex), x_vertex.max(y_vertex))) {
fill_count += 1;
}
fill_graph[x_vertex].insert(y_vertex);
fill_graph[y_vertex].insert(x_vertex);
}
}
for &b in &y {
fill_graph[a].remove(&b);
fill_graph[b].remove(&a);
}
w.extend(y);
}
} else {
new_hyperedges.push(hyperedge.clone());
}
}
if !w.is_empty() {
new_hyperedges.push(w);
}
hyperedges = new_hyperedges;
}
Ok((elimination_ordering, fill_count, fill_graph))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fast_minimum_degree_simple() {
let adj_list = vec![
vec![1], vec![0, 2], vec![1, 3], vec![2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(order.iter().all(|&i| i < 4));
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_star() {
let adj_list = vec![
vec![1, 2, 3], vec![0], vec![0], vec![0], ];
let order = fast_minimum_degree(&adj_list).unwrap();
println!("Star graph ordering: {:?}", order);
assert_eq!(order.len(), 4);
assert_ne!(order[0], 0, "Center should not be eliminated first");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_cycle() {
let adj_list = vec![
vec![1, 3], vec![0, 2], vec![1, 3], vec![0, 2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(order.iter().all(|&i| i < 4));
}
#[test]
fn test_fast_minimum_degree_empty() {
let adj_list: Vec<Vec<usize>> = vec![];
let result = fast_minimum_degree(&adj_list);
assert!(matches!(result, Err(FastMinimumDegreeError::EmptyAdjacencyList)));
}
#[test]
fn test_fast_minimum_degree_invalid_neighbor() {
let adj_list = vec![
vec![1], vec![0, 5], ];
let result = fast_minimum_degree(&adj_list);
assert!(matches!(result, Err(FastMinimumDegreeError::NeighborIndexOutOfBounds { .. })));
}
#[test]
fn test_fast_minimum_degree_single_vertex() {
let adj_list = vec![vec![]];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order, vec![0]);
}
#[test]
fn test_fast_minimum_degree_with_stats() {
let adj_list = vec![
vec![1, 2], vec![0, 2], vec![0, 1, 3], vec![2], ];
let (order, fill_count, final_graph) = fast_minimum_degree_with_stats(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(fill_count >= 0 as usize);
assert_eq!(final_graph.len(), 4);
}
#[test]
fn test_fast_minimum_degree_complete_graph() {
let adj_list = vec![
vec![1, 2, 3], vec![0, 2, 3], vec![0, 1, 3], vec![0, 1, 2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(order.iter().all(|&i| i < 4));
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_disconnected() {
let adj_list = vec![
vec![1], vec![0], vec![3], vec![2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(order.iter().all(|&i| i < 4));
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_path_graph() {
let adj_list = vec![
vec![1], vec![0, 2], vec![1, 3], vec![2, 4], vec![3, 5], vec![4], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 6);
assert!(order[0] == 0 || order[0] == 5, "First eliminated should be an endpoint");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_fast_minimum_degree_wheel_graph() {
let adj_list = vec![
vec![1, 2, 3, 4, 5], vec![0, 2, 5], vec![0, 1, 3], vec![0, 2, 4], vec![0, 3, 5], vec![0, 1, 4], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 6);
assert_ne!(order[0], 0, "Center should not be eliminated first");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_fast_minimum_degree_grid_graph() {
let adj_list = vec![
vec![1, 3], vec![0, 2, 4], vec![1, 5], vec![0, 4], vec![1, 3, 5], vec![2, 4], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 6);
let corners = [0, 2, 3, 5];
assert!(corners.contains(&order[0]), "First eliminated should be a corner vertex");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_fast_minimum_degree_bipartite_graph() {
let adj_list = vec![
vec![2, 3, 4], vec![2, 3, 4], vec![0, 1], vec![0, 1], vec![0, 1], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 5);
let right_partition = [2, 3, 4];
assert!(right_partition.contains(&order[0]), "First eliminated should be from right partition");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_fast_minimum_degree_tree_with_high_degree_vertex() {
let adj_list = vec![
vec![1, 2, 3], vec![0, 4, 5, 6], vec![0], vec![0], vec![1], vec![1], vec![1], ];
let order = fast_minimum_degree(&adj_list).unwrap();
println!("Tree with high degree vertex result: {:?}", order);
assert_eq!(order.len(), 7);
let leaves = [2, 3, 4, 5, 6];
let _high_degree = [0, 1];
assert!(leaves.contains(&order[0]), "First eliminated should be a leaf");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_fast_minimum_degree_multiple_components() {
let adj_list = vec![
vec![1, 2], vec![0, 2], vec![0, 1], vec![4], vec![3], vec![], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 6);
assert_eq!(order[0], 5, "Isolated vertex should be eliminated first");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn test_fast_minimum_degree_self_loops_ignored() {
let adj_list = vec![
vec![0, 1], vec![0, 2], vec![1, 2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 3);
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2]);
}
#[test]
fn test_fast_minimum_degree_duplicate_edges() {
let adj_list = vec![
vec![1, 1], vec![0, 0, 2], vec![1], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 3);
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2]);
}
#[test]
fn test_fast_minimum_degree_large_sparse_graph() {
let mut adj_list = vec![Vec::new(); 20];
for i in 0..20 {
if i > 0 {
adj_list[i].push(i - 1); }
if i < 19 {
adj_list[i].push(i + 1); }
if i % 3 == 0 && i + 3 < 20 {
adj_list[i].push(i + 3);
adj_list[i + 3].push(i);
}
}
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 20);
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, (0..20).collect::<Vec<_>>());
}
#[test]
fn test_fast_minimum_degree_fill_in_validation() {
let adj_list = vec![
vec![1, 3], vec![0, 2], vec![1, 3], vec![0, 2], ];
let (order, fill_count, _) = fast_minimum_degree_with_stats(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(fill_count >= 0 as usize);
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_optimal_ordering() {
let adj_list = vec![
vec![1], vec![0, 2, 4], vec![1, 3], vec![2], vec![1], ];
let order = fast_minimum_degree(&adj_list).unwrap();
println!("Optimal ordering test result: {:?}", order);
assert_eq!(order.len(), 5);
assert!(order[0] == 0 || order[0] == 4, "First eliminated should be an endpoint (degree 1)");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_fast_minimum_degree_consistency() {
let adj_list = vec![
vec![1, 2], vec![0, 2], vec![0, 1, 3], vec![2], ];
let order1 = fast_minimum_degree(&adj_list).unwrap();
let order2 = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order1, order2, "Algorithm should be deterministic");
}
#[test]
fn test_fast_minimum_degree_degree_progression() {
let adj_list = vec![
vec![1], vec![0, 2, 3], vec![1, 3], vec![1, 2], ];
let order = fast_minimum_degree(&adj_list).unwrap();
assert_eq!(order.len(), 4);
assert!(order[0] == 0, "First eliminated should be degree 1 vertex");
let mut sorted_order = order.clone();
sorted_order.sort();
assert_eq!(sorted_order, vec![0, 1, 2, 3]);
}
#[test]
fn test_fast_minimum_degree_error_handling() {
let empty: Vec<Vec<usize>> = vec![];
assert!(matches!(
fast_minimum_degree(&empty),
Err(FastMinimumDegreeError::EmptyAdjacencyList)
));
let invalid = vec![vec![1], vec![0, 10]];
assert!(matches!(
fast_minimum_degree(&invalid),
Err(FastMinimumDegreeError::NeighborIndexOutOfBounds { .. })
));
let self_ref = vec![vec![0, 1], vec![0]];
let result = fast_minimum_degree(&self_ref);
assert!(result.is_ok(), "Self-reference should be handled gracefully");
}
}