scirs2_graph/algorithms/
paths.rs

1//! Path algorithms for graphs
2//!
3//! This module contains algorithms for finding special paths in graphs,
4//! including Eulerian and Hamiltonian paths.
5
6use crate::base::{EdgeWeight, Graph, Node};
7use std::collections::{HashSet, VecDeque};
8
9/// Result of Eulerian path/circuit check
10#[derive(Debug, Clone, PartialEq)]
11pub enum EulerianType {
12    /// Graph has an Eulerian circuit (closed path visiting every edge exactly once)
13    Circuit,
14    /// Graph has an Eulerian path (open path visiting every edge exactly once)
15    Path,
16    /// Graph has neither Eulerian circuit nor path
17    None,
18}
19
20/// Checks if a graph has an Eulerian path or circuit
21///
22/// An Eulerian circuit exists if all vertices have even degree.
23/// An Eulerian path exists if exactly 0 or 2 vertices have odd degree.
24///
25/// # Arguments
26/// * `graph` - The undirected graph to check
27///
28/// # Returns
29/// * The type of Eulerian structure in the graph
30#[allow(dead_code)]
31pub fn eulerian_type<N, E, Ix>(graph: &Graph<N, E, Ix>) -> EulerianType
32where
33    N: Node + std::fmt::Debug,
34    E: EdgeWeight,
35    Ix: petgraph::graph::IndexType,
36{
37    // First check if the graph is connected (ignoring isolated vertices)
38    let non_isolated: Vec<_> = graph
39        .inner()
40        .node_indices()
41        .filter(|&idx| graph.inner().edges(idx).count() > 0)
42        .collect();
43
44    if non_isolated.is_empty() {
45        return EulerianType::Circuit; // Empty graph technically has a circuit
46    }
47
48    // Check connectivity among non-isolated vertices
49    let mut visited = HashSet::new();
50    let mut queue = VecDeque::new();
51    queue.push_back(non_isolated[0]);
52    visited.insert(non_isolated[0]);
53
54    while let Some(node) = queue.pop_front() {
55        for neighbor in graph.inner().neighbors(node) {
56            if !visited.contains(&neighbor) && non_isolated.contains(&neighbor) {
57                visited.insert(neighbor);
58                queue.push_back(neighbor);
59            }
60        }
61    }
62
63    if visited.len() != non_isolated.len() {
64        return EulerianType::None; // Graph is not connected
65    }
66
67    // Count vertices with odd degree
68    let mut odd_degree_count = 0;
69    for node in graph.inner().node_indices() {
70        let degree = graph.inner().edges(node).count();
71        if degree % 2 == 1 {
72            odd_degree_count += 1;
73        }
74    }
75
76    match odd_degree_count {
77        0 => EulerianType::Circuit,
78        2 => EulerianType::Path,
79        _ => EulerianType::None,
80    }
81}
82
83/// Checks if a graph has a Hamiltonian path (visiting every vertex exactly once)
84///
85/// # Arguments
86/// * `graph` - The graph to check
87///
88/// # Returns
89/// * `bool` - True if a Hamiltonian path exists
90#[allow(dead_code)]
91pub fn has_hamiltonian_path<N, E, Ix>(graph: &Graph<N, E, Ix>) -> bool
92where
93    N: Node + std::fmt::Debug,
94    E: EdgeWeight,
95    Ix: petgraph::graph::IndexType,
96{
97    let n = graph.node_count();
98    if n == 0 {
99        return true;
100    }
101
102    let nodes: Vec<_> = graph.inner().node_indices().collect();
103
104    // Try starting from each node
105    for &start in &nodes {
106        let mut visited = vec![false; n];
107        visited[start.index()] = true;
108
109        if hamiltonian_path_dfs(graph, start, &mut visited, 1, n) {
110            return true;
111        }
112    }
113
114    false
115}
116
117/// DFS helper for Hamiltonian path
118#[allow(dead_code)]
119fn hamiltonian_path_dfs<N, E, Ix>(
120    graph: &Graph<N, E, Ix>,
121    current: petgraph::graph::NodeIndex<Ix>,
122    visited: &mut Vec<bool>,
123    count: usize,
124    n: usize,
125) -> bool
126where
127    N: Node + std::fmt::Debug,
128    E: EdgeWeight,
129    Ix: petgraph::graph::IndexType,
130{
131    if count == n {
132        return true;
133    }
134
135    for neighbor in graph.inner().neighbors(current) {
136        if !visited[neighbor.index()] {
137            visited[neighbor.index()] = true;
138
139            if hamiltonian_path_dfs(graph, neighbor, visited, count + 1, n) {
140                return true;
141            }
142
143            visited[neighbor.index()] = false;
144        }
145    }
146
147    false
148}
149
150/// Checks if a graph has a Hamiltonian circuit (cycle visiting every vertex exactly once)
151///
152/// # Arguments
153/// * `graph` - The graph to check
154///
155/// # Returns
156/// * `bool` - True if a Hamiltonian circuit exists
157#[allow(dead_code)]
158pub fn has_hamiltonian_circuit<N, E, Ix>(graph: &Graph<N, E, Ix>) -> bool
159where
160    N: Node + std::fmt::Debug,
161    E: EdgeWeight,
162    Ix: petgraph::graph::IndexType,
163{
164    let n = graph.node_count();
165    if n == 0 {
166        return true;
167    }
168    if n < 3 {
169        return false;
170    }
171
172    let nodes: Vec<_> = graph.inner().node_indices().collect();
173    let start = nodes[0];
174
175    let mut visited = vec![false; n];
176    visited[start.index()] = true;
177
178    hamiltonian_circuit_dfs(graph, start, start, &mut visited, 1, n)
179}
180
181/// DFS helper for Hamiltonian circuit
182#[allow(dead_code)]
183fn hamiltonian_circuit_dfs<N, E, Ix>(
184    graph: &Graph<N, E, Ix>,
185    current: petgraph::graph::NodeIndex<Ix>,
186    start: petgraph::graph::NodeIndex<Ix>,
187    visited: &mut Vec<bool>,
188    count: usize,
189    n: usize,
190) -> bool
191where
192    N: Node + std::fmt::Debug,
193    E: EdgeWeight,
194    Ix: petgraph::graph::IndexType,
195{
196    if count == n {
197        // Check if we can return to start
198        return graph.inner().contains_edge(current, start);
199    }
200
201    for neighbor in graph.inner().neighbors(current) {
202        if !visited[neighbor.index()] {
203            visited[neighbor.index()] = true;
204
205            if hamiltonian_circuit_dfs(graph, neighbor, start, visited, count + 1, n) {
206                return true;
207            }
208
209            visited[neighbor.index()] = false;
210        }
211    }
212
213    false
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate::error::Result as GraphResult;
220    use crate::generators::create_graph;
221
222    #[test]
223    fn test_eulerian_circuit() -> GraphResult<()> {
224        // Create a square (all vertices have even degree)
225        let mut graph = create_graph::<i32, ()>();
226
227        graph.add_edge(0, 1, ())?;
228        graph.add_edge(1, 2, ())?;
229        graph.add_edge(2, 3, ())?;
230        graph.add_edge(3, 0, ())?;
231
232        assert_eq!(eulerian_type(&graph), EulerianType::Circuit);
233
234        Ok(())
235    }
236
237    #[test]
238    fn test_eulerian_path() -> GraphResult<()> {
239        // Create a path graph (2 vertices with odd degree)
240        let mut graph = create_graph::<i32, ()>();
241
242        graph.add_edge(0, 1, ())?;
243        graph.add_edge(1, 2, ())?;
244
245        assert_eq!(eulerian_type(&graph), EulerianType::Path);
246
247        Ok(())
248    }
249
250    #[test]
251    fn test_no_eulerian() -> GraphResult<()> {
252        // Create a graph with 4 vertices of odd degree (no Eulerian path/circuit possible)
253        let mut graph = create_graph::<i32, ()>();
254
255        // Create a graph like: 0-1-2
256        //                         X
257        //                       3-4-5
258        graph.add_edge(0, 1, ())?;
259        graph.add_edge(1, 2, ())?;
260        graph.add_edge(1, 4, ())?; // Bridge
261        graph.add_edge(3, 4, ())?;
262        graph.add_edge(4, 5, ())?;
263
264        assert_eq!(eulerian_type(&graph), EulerianType::None);
265
266        Ok(())
267    }
268
269    #[test]
270    fn test_hamiltonian_path() -> GraphResult<()> {
271        // Create a path graph (has Hamiltonian path)
272        let mut graph = create_graph::<i32, ()>();
273
274        graph.add_edge(0, 1, ())?;
275        graph.add_edge(1, 2, ())?;
276        graph.add_edge(2, 3, ())?;
277
278        assert!(has_hamiltonian_path(&graph));
279
280        Ok(())
281    }
282
283    #[test]
284    fn test_hamiltonian_circuit() -> GraphResult<()> {
285        // Create a square (has Hamiltonian circuit)
286        let mut graph = create_graph::<i32, ()>();
287
288        graph.add_edge(0, 1, ())?;
289        graph.add_edge(1, 2, ())?;
290        graph.add_edge(2, 3, ())?;
291        graph.add_edge(3, 0, ())?;
292
293        assert!(has_hamiltonian_circuit(&graph));
294
295        // Star graph (no Hamiltonian circuit)
296        let mut star = create_graph::<i32, ()>();
297
298        star.add_edge(0, 1, ())?;
299        star.add_edge(0, 2, ())?;
300        star.add_edge(0, 3, ())?;
301
302        assert!(!has_hamiltonian_circuit(&star));
303
304        Ok(())
305    }
306}