add euler path/cycle validation

This commit is contained in:
mxhagen 2025-04-10 20:00:57 +02:00
parent 74b438efaa
commit f50cec4923
2 changed files with 138 additions and 11 deletions

View File

@ -139,11 +139,45 @@ where
/// this assumes a path only containing the start/end vertex once,
/// so a valid 2-vertex-loop would be represented as `[v, w]` and not `[v, w, v]`.
///
/// empty paths are never considered a cycle.
/// empty paths are considered a hamilton cycle for graphs with no vertices.
pub fn is_hamilton_cycle(&self, path: &[V]) -> bool {
!path.is_empty()
&& self.is_hamilton_path(path)
&& self.has_edge((path.last().unwrap(), path.first().unwrap()))
(path.is_empty() && self.vertex_count() == 0)
|| (!path.is_empty()
&& self.is_hamilton_path(path)
&& self.has_edge((path.last().unwrap(), path.first().unwrap())))
}
/// check if a provided path is an euler path within the graph,
/// meaning it contains all of the graphs edges exactly once.
///
/// an empty path is considered an euler path for graphs with no edges.
pub fn is_euler_path(&self, path: &[(V, V)]) -> bool {
match self.edge_count() {
0 => path.is_empty(),
n => {
let edges_connected = path.windows(2).all(|w| w[0].1 == w[1].0);
let path_edges = path.iter().collect::<HashSet<_>>();
let has_all_edges_once = path.len() == n
&& self.edges.iter().all(|(v, ws)| {
ws.iter()
.all(|w| path_edges.contains(&(v.clone(), w.clone())))
});
edges_connected && has_all_edges_once
}
}
}
/// check if a provided path is an euler cycle within the graph,
/// meaning it is a cycle and contains all of the graphs edges exactly once.
///
/// an empty path are considered an euler cycle for graphs with no edges.
pub fn is_euler_cycle(&self, path: &[(V, V)]) -> bool {
(path.is_empty() && self.edge_count() == 0)
|| (!path.is_empty()
&& self.is_euler_path(path)
&& path.first().unwrap().0 == path.last().unwrap().1)
}
pub fn iter(&self) -> impl Iterator<Item = &V> {
@ -404,6 +438,39 @@ where
&& self.has_edge((path.last().unwrap(), path.first().unwrap()))
}
/// check if a provided path is an euler path within the graph,
/// meaning it contains all of the graphs edges exactly once.
///
/// an empty path is considered an euler path for graphs with no edges.
pub fn is_euler_path(&self, path: &[(V, V)]) -> bool {
match self.edge_count() {
0 => path.is_empty(),
n => {
let edges_connected = path.windows(2).all(|w| w[0].1 == w[1].0);
let path_edges = path.iter().collect::<HashSet<_>>();
let has_all_edges_once = path.len() == n
&& self.edges.iter().all(|(v, ws)| {
ws.iter()
.all(|(_, w)| path_edges.contains(&(v.clone(), w.clone())))
});
edges_connected && has_all_edges_once
}
}
}
/// check if a provided path is an euler cycle within the graph,
/// meaning it is a cycle and contains all of the graphs edges exactly once.
///
/// an empty path are considered an euler cycle for graphs with no edges.
pub fn is_euler_cycle(&self, path: &[(V, V)]) -> bool {
(path.is_empty() && self.edge_count() == 0)
|| (!path.is_empty()
&& self.is_euler_path(path)
&& path.first().unwrap().0 == path.last().unwrap().1)
}
pub fn iter(&self) -> impl Iterator<Item = &V> {
self.vertices.iter()
}
@ -427,6 +494,6 @@ macro_rules! graph {
$( vertices.insert($e); )*
)*
Graph { vertices, edges }
Graph { vertices, edges }
}};
}

View File

@ -5,9 +5,9 @@ pub use graph::{Graph, WeightedGraph};
fn main() {
// test graph:
// ┏━━━━━1━━━━━┓
// -> 0 3
// ┗━━━━━2━━━━━┛
// ┏━━━1━━━┓
// -> 0 ┃ 3
// ┗━━━2━━━┛
// adjacency list
let g = graph! {
@ -129,9 +129,9 @@ fn main() {
);
// test graph:
// ┏━━━━━1━━━━━┓
// -> 0 3
// ┗━━━━━2━━━━━┛
// ┏━━━1━━━┓
// -> 0 ┃ 3
// ┗━━━2━━━┛
let g = graph! {
0: 1, 2;
@ -164,6 +164,66 @@ fn main() {
"Cycle that does not contain all vertices is not a hamilton cycle"
);
// test graph:
// ┏━━━1━━━┓
// -> 0 3
// ┗━━━2━━━┛
let g = graph! {
0: 1;
1: 3;
2: 0;
3: 2;
};
let path = [(0, 1), (1, 3), (3, 2), (2, 0)];
assert!(
g.is_euler_path(&path),
"Valid euler path in graph should be recognized as such"
);
let path = [(0, 1), (1, 2), (2, 3), (3, 0)];
assert!(
!g.is_euler_path(&path),
"A path with a non-existent edge is not a euler path"
);
// test graph:
// ┏━━━1━━━┓
// -> 0 ┃ 3
// ┗━━━2━━━┛
let g = graph! {
0: 1;
1: 2, 3;
2: 0;
3: 2;
};
let path = [(0, 1), (1, 3), (3, 2), (2, 0)];
assert!(
!g.is_euler_path(&path),
"A path with missing edges is not an euler path"
);
// test graph:
// ┏━>━1━>━┓
// -> 0 3
// ┗━<━2━━━┛
let g = graph! {
0: 1;
1: 3;
2: 0, 3;
3: 2;
};
let path = [(0, 1), (1, 3), (3, 2), (2, 3), (3, 2), (2, 0)];
assert!(
!g.is_euler_path(&path),
"A path with duplicate edges is not an euler path"
);
// yay
println!("All tests passed.");
}