Skip to content

Commit ad03785

Browse files
committed
ref: refactor implementation
- Add custom error `AdjMatError` type with 3 varients: `EmptyMat`, `StartOutOfBound` and `ImproperMat` - Add tests for exceptional cases - Change adjaceny matrix repr to `Vec<Vec<bool>>`
1 parent 73788c2 commit ad03785

File tree

1 file changed

+107
-49
lines changed

1 file changed

+107
-49
lines changed

src/backtracking/hamiltonian_cycle.rs

Lines changed: 107 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
//! This module provides functionality to find a Hamiltonian cycle in graph.
1+
//! This module provides functionality to find a Hamiltonian cycle in a graph.
22
//! Source: [Uncyclopedia](https://en.wikipedia.org/wiki/Hamiltonian_path_problem)
33
4+
/// Represents errors that can occur while working with an adjacency matrix.
5+
#[derive(Debug, PartialEq, Eq)]
6+
pub enum AdjMatError {
7+
/// The adjacency matrix is empty.
8+
EmptyMat,
9+
/// The adjacency matrix is not square.
10+
ImproperMat,
11+
/// The start vertex is out of bounds.
12+
StartOutOfBound,
13+
}
14+
415
/// Represents a graph with an adjacency matrix.
516
struct Graph {
617
/// The adjacency matrix representing the graph.
7-
adjacency_matrix: Vec<Vec<u8>>,
8-
/// The number of vertices in the graph.
9-
vertices: usize,
18+
adjacency_matrix: Vec<Vec<bool>>,
1019
}
1120

1221
impl Graph {
@@ -15,14 +24,14 @@ impl Graph {
1524
/// # Arguments
1625
///
1726
/// * `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-
}
27+
/// the presence `true` or absence `false` of an edge
28+
/// between two vertices.
29+
///
30+
/// # Returns
31+
///
32+
/// A graph that represents adjacency matrix
33+
fn new(adjacency_matrix: Vec<Vec<bool>>) -> Self {
34+
Self { adjacency_matrix }
2635
}
2736

2837
/// Checks if it's safe to include vertex `v` in the Hamiltonian cycle path.
@@ -38,7 +47,7 @@ impl Graph {
3847
/// * `true` if it's safe to include `v` in the path, `false` otherwise.
3948
fn is_safe(&self, v: usize, path: &[usize], pos: usize) -> bool {
4049
// Check if the current vertex and the last vertex in the path are adjacent
41-
if self.adjacency_matrix[path[pos - 1]][v] == 0 {
50+
if !self.adjacency_matrix[path[pos - 1]][v] {
4251
return false;
4352
}
4453

@@ -48,7 +57,7 @@ impl Graph {
4857

4958
/// Utility function for finding a Hamiltonian cycle recursively.
5059
///
51-
/// This function is called recursively by `find_hamiltonian_cycle`.
60+
/// This function is called by `find_hamiltonian_cycle`.
5261
///
5362
/// # Arguments
5463
///
@@ -59,18 +68,19 @@ impl Graph {
5968
///
6069
/// * `true` if a Hamiltonian cycle is found, `false` otherwise.
6170
fn hamiltonian_cycle_util(&self, path: &mut Vec<usize>, pos: usize) -> bool {
62-
if pos == self.vertices {
71+
let vertices = self.adjacency_matrix.len();
72+
if pos == vertices {
6373
// 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;
74+
return self.adjacency_matrix[path[pos - 1]][path[0]];
6575
}
6676

67-
for v in 0..self.vertices {
77+
for v in 0..vertices {
6878
if self.is_safe(v, path, pos) {
6979
path[pos] = v;
7080
if self.hamiltonian_cycle_util(path, pos + 1) {
7181
return true;
7282
}
73-
path[pos] = std::usize::MAX;
83+
path[pos] = usize::MAX;
7484
}
7585
}
7686

@@ -82,16 +92,23 @@ impl Graph {
8292
/// A Hamiltonian cycle is a cycle that visits every vertex exactly once
8393
/// and returns to the starting vertex.
8494
///
95+
/// # Note
96+
/// This implementation may not find all possible Hamiltonian cycles.
97+
/// It will stop as soon as it finds one valid cycle. If multiple Hamiltonian cycles exist,
98+
/// only one of them will be returned.
99+
///
85100
/// Returns `Some(path)` if a Hamiltonian cycle is found, where `path` is a vector
86101
/// containing the indices of vertices in the cycle, starting and ending with the same vertex.
87102
///
88103
/// Returns `None` if no Hamiltonian cycle exists in the graph.
89104
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
105+
let mut path = vec![usize::MAX; self.adjacency_matrix.len()];
106+
// Start at the specified vertex
107+
path[0] = start_vertex;
92108

93109
if self.hamiltonian_cycle_util(&mut path, 1) {
94-
path.push(start_vertex); // To complete the cycle by returning to the starting vertex
110+
// To complete the cycle by returning to the starting vertex
111+
path.push(start_vertex);
95112
Some(path)
96113
} else {
97114
None
@@ -101,11 +118,28 @@ impl Graph {
101118

102119
/// Finds a Hamiltonian cycle in a given graph represented by an adjacency matrix, if one exists, starting from a specified vertex
103120
pub fn find_hamiltonian_cycle(
104-
adjacency_matrix: Vec<Vec<u8>>,
121+
adjacency_matrix: Vec<Vec<bool>>,
105122
start_vertex: usize,
106-
) -> Option<Vec<usize>> {
123+
) -> Result<Option<Vec<usize>>, AdjMatError> {
124+
let vertices = adjacency_matrix.len();
125+
// Check if the adjacency matrix is empty
126+
if vertices == 0 {
127+
return Err(AdjMatError::EmptyMat);
128+
}
129+
130+
// Validate maze representation (if necessary)
131+
if adjacency_matrix.iter().any(|row| row.len() != vertices) {
132+
return Err(AdjMatError::ImproperMat);
133+
}
134+
135+
// Validate start position
136+
if start_vertex >= vertices {
137+
return Err(AdjMatError::StartOutOfBound);
138+
}
139+
140+
// If validations pass, proceed with finding the cycle
107141
let graph = Graph::new(adjacency_matrix);
108-
graph.find_hamiltonian_cycle(start_vertex)
142+
Ok(graph.find_hamiltonian_cycle(start_vertex))
109143
}
110144

111145
#[cfg(test)]
@@ -128,53 +162,77 @@ mod tests {
128162
hamiltonian_cycle_tests! {
129163
test_complete_graph: (
130164
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],
165+
vec![false, true, true, true],
166+
vec![true, false, true, true],
167+
vec![true, true, false, true],
168+
vec![true, true, true, false],
135169
],
136170
0,
137-
Some(vec![0, 1, 2, 3, 0])
171+
Ok(Some(vec![0, 1, 2, 3, 0]))
138172
),
139173
test_cycle_graph: (
140174
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],
175+
vec![false, true, false, false, true],
176+
vec![true, false, true, false, false],
177+
vec![false, true, false, true, false],
178+
vec![false, false, true, false, true],
179+
vec![true, false, false, true, false],
146180
],
147181
2,
148-
Some(vec![2, 1, 0, 4, 3, 2])
182+
Ok(Some(vec![2, 1, 0, 4, 3, 2]))
149183
),
150184
test_no_cycle_graph: (
151185
vec![
152-
vec![0, 1, 0],
153-
vec![1, 0, 0],
154-
vec![0, 0, 0],
186+
vec![false, true, false],
187+
vec![true, false, false],
188+
vec![false, false, false],
155189
],
156190
0,
157-
None::<Vec<usize>>
191+
Ok(None::<Vec<usize>>)
158192
),
159193
test_triangle_graph: (
160194
vec![
161-
vec![0, 1, 1],
162-
vec![1, 0, 1],
163-
vec![1, 1, 0],
195+
vec![false, true, true],
196+
vec![true, false, true],
197+
vec![true, true, false],
164198
],
165199
1,
166-
Some(vec![1, 0, 2, 1])
200+
Ok(Some(vec![1, 0, 2, 1]))
167201
),
168202
test_tree_graph: (
169203
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],
204+
vec![false, true, false, true, false],
205+
vec![true, false, true, true, false],
206+
vec![false, true, false, false, false],
207+
vec![true, true, false, false, true],
208+
vec![false, false, false, true, false],
175209
],
176210
0,
177-
None::<Vec<usize>>
211+
Ok(None::<Vec<usize>>)
212+
),
213+
test_empty_matrix: (
214+
vec![],
215+
0,
216+
Err(AdjMatError::EmptyMat)
217+
),
218+
test_improper_matrix: (
219+
vec![
220+
vec![false, true],
221+
vec![true],
222+
vec![false, true, true],
223+
vec![true, true, true, false]
224+
],
225+
0,
226+
Err(AdjMatError::ImproperMat)
227+
),
228+
test_start_out_of_bound: (
229+
vec![
230+
vec![false, true, true],
231+
vec![true, false, true],
232+
vec![true, true, false],
233+
],
234+
3,
235+
Err(AdjMatError::StartOutOfBound)
178236
),
179237
}
180238
}

0 commit comments

Comments
 (0)