use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct UndirectedGraph {
pub n: usize,
pub adj: Vec<Vec<(usize, bool)>>,
}
impl UndirectedGraph {
#[must_use]
pub fn new(n: usize) -> Self {
Self {
n,
adj: vec![Vec::new(); n],
}
}
pub fn add_edge(&mut self, u: usize, v: usize) {
assert!(u < self.n && v < self.n, "vertex out of range");
self.adj[u].push((v, false));
self.adj[v].push((u, false));
}
#[must_use]
fn total_degree(&self, v: usize) -> usize {
self.adj[v].len()
}
#[must_use]
fn unused_edges(&self, v: usize) -> usize {
self.adj[v].iter().filter(|&&(_, used)| !used).count()
}
}
#[must_use = "This function returns a Result containing the Eulerian path/cycle that should be handled"]
pub fn hierholzer_eulerian_path(
g: &mut UndirectedGraph,
require_cycle: bool,
) -> Result<Vec<usize>, String> {
let comp_check = check_connectivity(g);
if !comp_check {
return Err(
"Graph is not connected (in the subgraph that has edges). No Eulerian path/cycle."
.into(),
);
}
let mut odd_vertices = Vec::new();
for v in 0..g.n {
let deg = g.total_degree(v);
if deg % 2 != 0 {
odd_vertices.push(v);
}
}
if require_cycle && !odd_vertices.is_empty() {
return Err(format!(
"Require Eulerian cycle, but found {} vertices with odd degree",
odd_vertices.len()
));
}
if !(require_cycle || odd_vertices.is_empty() || odd_vertices.len() == 2) {
return Err(format!(
"Eulerian path requires 0 or 2 odd-degree vertices, found {}",
odd_vertices.len()
));
}
let start = odd_vertices.first().copied().unwrap_or_else(|| {
(0..g.n).find(|&v| !g.adj[v].is_empty()).unwrap_or(0)
});
let mut circuit = Vec::new();
let mut stack = Vec::new();
stack.push(start);
while let Some(u) = stack.last().copied() {
if g.unused_edges(u) > 0 {
if let Some(e_idx) = g.adj[u].iter().position(|&(_, used)| !used) {
let v = g.adj[u][e_idx].0;
g.adj[u][e_idx].1 = true;
if let Some(rev_edge) = g.adj[v].iter_mut().find(|(w, used)| *w == u && !*used) {
rev_edge.1 = true;
}
stack.push(v);
}
} else {
stack.pop();
circuit.push(u);
}
}
circuit.reverse();
Ok(circuit)
}
fn check_connectivity(g: &UndirectedGraph) -> bool {
let start_opt = (0..g.n).find(|&v| !g.adj[v].is_empty());
if start_opt.is_none() {
return true;
}
let start = start_opt.unwrap();
let mut visited = vec![false; g.n];
let mut queue = VecDeque::new();
visited[start] = true;
queue.push_back(start);
let mut count = 1usize;
while let Some(u) = queue.pop_front() {
for &(nbr, _) in &g.adj[u] {
if !visited[nbr] {
visited[nbr] = true;
queue.push_back(nbr);
count += 1;
}
}
}
let mut total_with_edges = 0;
for v in 0..g.n {
if !g.adj[v].is_empty() {
total_with_edges += 1;
}
}
count == total_with_edges
}
#[test]
fn test_odd_degree_vertices() {
let mut g = UndirectedGraph::new(4);
g.add_edge(0, 1);
g.add_edge(1, 2);
g.add_edge(2, 3);
g.add_edge(3, 0);
g.add_edge(0, 2);
g.add_edge(1, 3);
let r = hierholzer_eulerian_path(&mut g, false);
assert!(
r.is_err(),
"Should fail when 4 vertices have odd degree for Eulerian path"
);
let r = hierholzer_eulerian_path(&mut g, true);
assert!(
r.is_err(),
"Should fail when 4 vertices have odd degree for Eulerian cycle"
);
}
#[test]
fn test_multiple_edges() {
let mut g = UndirectedGraph::new(3);
g.add_edge(0, 1);
g.add_edge(0, 1); g.add_edge(1, 2);
g.add_edge(2, 0);
g.add_edge(0, 1);
let cycle = hierholzer_eulerian_path(&mut g, true).expect("should find cycle");
assert_eq!(cycle.len(), 6, "5 edges + return-to-start => length 6 path");
assert_eq!(cycle.first(), cycle.last());
}
#[test]
fn test_vertex_out_of_range() {
let _g = UndirectedGraph::new(2);
let result = std::panic::catch_unwind(|| {
UndirectedGraph::new(2).add_edge(0, 2) });
assert!(result.is_err());
}