Skip to content

Commit 0d31506

Browse files
committed
feat: implement Hamiltonian Cycle
1 parent afdb283 commit 0d31506

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

src/backtracking/hamiltonian_cycle.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//! This module provides functionality to find a Hamiltonian cycle in graph.
2+
//! Source: [Uncyclopedia](https://en.wikipedia.org/wiki/Hamiltonian_path_problem)
3+
4+
/// Represents a graph with an adjacency matrix.
5+
struct Graph {
6+
/// The adjacency matrix representing the graph.
7+
adjacency_matrix: Vec<Vec<u8>>,
8+
/// The number of vertices in the graph.
9+
vertices: usize,
10+
}
11+
12+
impl Graph {
13+
/// Creates a new graph with the given adjacency matrix.
14+
///
15+
/// # Arguments
16+
///
17+
/// * `adjacency_matrix` - A square matrix where each element represents
18+
/// the presence `1` or absence `0` of an edge
19+
/// between two vertices.
20+
fn new(adjacency_matrix: Vec<Vec<u8>>) -> Self {
21+
let vertices = adjacency_matrix.len();
22+
Self {
23+
adjacency_matrix,
24+
vertices,
25+
}
26+
}
27+
28+
/// Checks if it's safe to include vertex `v` in the Hamiltonian cycle path.
29+
///
30+
/// # Arguments
31+
///
32+
/// * `v` - The index of the vertex being considered.
33+
/// * `path` - A reference to the current path being explored.
34+
/// * `pos` - The position of the current vertex being considered.
35+
///
36+
/// # Returns
37+
///
38+
/// * `true` if it's safe to include `v` in the path, `false` otherwise.
39+
fn is_safe(&self, v: usize, path: &[usize], pos: usize) -> bool {
40+
// Check if the current vertex and the last vertex in the path are adjacent
41+
if self.adjacency_matrix[path[pos - 1]][v] == 0 {
42+
return false;
43+
}
44+
45+
// Check if the vertex has already been included in the path
46+
!path[..pos].contains(&v)
47+
}
48+
49+
/// Utility function for finding a Hamiltonian cycle recursively.
50+
///
51+
/// This function is called recursively by `find_hamiltonian_cycle`.
52+
///
53+
/// # Arguments
54+
///
55+
/// * `path` - A mutable vector representing the current path being explored.
56+
/// * `pos` - The position of the current vertex being considered.
57+
///
58+
/// # Returns
59+
///
60+
/// * `true` if a Hamiltonian cycle is found, `false` otherwise.
61+
fn hamiltonian_cycle_util(&self, path: &mut Vec<usize>, pos: usize) -> bool {
62+
if pos == self.vertices {
63+
// Check if there is an edge from the last included vertex to the first vertex
64+
return self.adjacency_matrix[path[pos - 1]][path[0]] == 1;
65+
}
66+
67+
for v in 0..self.vertices {
68+
if self.is_safe(v, path, pos) {
69+
path[pos] = v;
70+
if self.hamiltonian_cycle_util(path, pos + 1) {
71+
return true;
72+
}
73+
path[pos] = std::usize::MAX;
74+
}
75+
}
76+
77+
false
78+
}
79+
80+
/// Finds a Hamiltonian cycle in the graph, if one exists, starting from the specified vertex.
81+
///
82+
/// A Hamiltonian cycle is a cycle that visits every vertex exactly once
83+
/// and returns to the starting vertex.
84+
///
85+
/// Returns `Some(path)` if a Hamiltonian cycle is found, where `path` is a vector
86+
/// containing the indices of vertices in the cycle, starting and ending with the same vertex.
87+
///
88+
/// Returns `None` if no Hamiltonian cycle exists in the graph.
89+
fn find_hamiltonian_cycle(&self, start_vertex: usize) -> Option<Vec<usize>> {
90+
let mut path = vec![std::usize::MAX; self.vertices];
91+
path[0] = start_vertex; // Start at the specified vertex
92+
93+
if self.hamiltonian_cycle_util(&mut path, 1) {
94+
path.push(start_vertex); // To complete the cycle by returning to the starting vertex
95+
Some(path)
96+
} else {
97+
None
98+
}
99+
}
100+
}
101+
102+
/// Finds a Hamiltonian cycle in a given graph represented by an adjacency matrix, if one exists, starting from a specified vertex
103+
pub fn find_hamiltonian_cycle(
104+
adjacency_matrix: Vec<Vec<u8>>,
105+
start_vertex: usize,
106+
) -> Option<Vec<usize>> {
107+
let graph = Graph::new(adjacency_matrix);
108+
graph.find_hamiltonian_cycle(start_vertex)
109+
}
110+
111+
#[cfg(test)]
112+
mod tests {
113+
use super::*;
114+
115+
macro_rules! hamiltonian_cycle_tests {
116+
($($name:ident: $test_case:expr,)*) => {
117+
$(
118+
#[test]
119+
fn $name() {
120+
let (adjacency_matrix, start_vertex, expected) = $test_case;
121+
let result = find_hamiltonian_cycle(adjacency_matrix, start_vertex);
122+
assert_eq!(result, expected);
123+
}
124+
)*
125+
};
126+
}
127+
128+
hamiltonian_cycle_tests! {
129+
test_complete_graph: (
130+
vec![
131+
vec![0, 1, 1, 1],
132+
vec![1, 0, 1, 1],
133+
vec![1, 1, 0, 1],
134+
vec![1, 1, 1, 0],
135+
],
136+
0,
137+
Some(vec![0, 1, 2, 3, 0])
138+
),
139+
test_cycle_graph: (
140+
vec![
141+
vec![0, 1, 0, 0, 1],
142+
vec![1, 0, 1, 0, 0],
143+
vec![0, 1, 0, 1, 0],
144+
vec![0, 0, 1, 0, 1],
145+
vec![1, 0, 0, 1, 0],
146+
],
147+
2,
148+
Some(vec![2, 1, 0, 4, 3, 2])
149+
),
150+
test_no_cycle_graph: (
151+
vec![
152+
vec![0, 1, 0],
153+
vec![1, 0, 0],
154+
vec![0, 0, 0],
155+
],
156+
0,
157+
None::<Vec<usize>>
158+
),
159+
test_triangle_graph: (
160+
vec![
161+
vec![0, 1, 1],
162+
vec![1, 0, 1],
163+
vec![1, 1, 0],
164+
],
165+
1,
166+
Some(vec![1, 0, 2, 1])
167+
),
168+
test_tree_graph: (
169+
vec![
170+
vec![0, 1, 0, 1, 0],
171+
vec![1, 0, 1, 1, 0],
172+
vec![0, 1, 0, 0, 0],
173+
vec![1, 0, 0, 0, 1],
174+
vec![0, 0, 0, 1, 0],
175+
],
176+
0,
177+
None::<Vec<usize>>
178+
),
179+
}
180+
}

src/backtracking/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod all_combination_of_size_k;
2+
mod hamiltonian_cycle;
23
mod knight_tour;
34
mod n_queens;
45
mod parentheses_generator;
@@ -7,6 +8,7 @@ mod rat_in_maze;
78
mod sudoku;
89

910
pub use all_combination_of_size_k::generate_all_combinations;
11+
pub use hamiltonian_cycle::find_hamiltonian_cycle;
1012
pub use knight_tour::find_knight_tour;
1113
pub use n_queens::n_queens_solver;
1214
pub use parentheses_generator::generate_parentheses;

0 commit comments

Comments
 (0)