811-hamiltonian-backtrack — Hamiltonian Path (Backtracking)
Tutorial Video
Text description (accessibility)
This video demonstrates the "811-hamiltonian-backtrack — Hamiltonian Path (Backtracking)" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. A Hamiltonian path visits every vertex exactly once. Key difference from OCaml: 1. **Backtracking style**: Rust's mutable `path` and `visited` with explicit push/pop is imperative; OCaml's recursive approach with immutable lists is more idiomatic but allocates more.
Tutorial
The Problem
A Hamiltonian path visits every vertex exactly once. Unlike Eulerian path (efficient, O(V+E)), Hamiltonian path is NP-complete — no polynomial algorithm is known. The backtracking approach prunes the search tree by abandoning partial paths that cannot possibly complete. It is the basis for TSP (traveling salesman) solvers and appears in puzzle solving (knight's tour, Sudoku) and genome sequencing (alternative to Eulerian for short reads).
🎯 Learning Outcomes
visited: Vec<bool> array to track which vertices are in the current pathCode Example
#![allow(clippy::all)]
//! # Hamiltonian Path (Backtracking)
pub fn hamiltonian_path(n: usize, edges: &[(usize, usize)]) -> Option<Vec<usize>> {
let mut adj = vec![vec![false; n]; n];
for &(u, v) in edges {
adj[u][v] = true;
adj[v][u] = true;
}
let mut path = vec![0];
let mut visited = vec![false; n];
visited[0] = true;
fn backtrack(n: usize, adj: &[Vec<bool>], path: &mut Vec<usize>, vis: &mut [bool]) -> bool {
if path.len() == n {
return true;
}
let last = *path.last().unwrap();
for next in 0..n {
if !vis[next] && adj[last][next] {
vis[next] = true;
path.push(next);
if backtrack(n, adj, path, vis) {
return true;
}
path.pop();
vis[next] = false;
}
}
false
}
if backtrack(n, &adj, &mut path, &mut visited) {
Some(path)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ham() {
let p = hamiltonian_path(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (1, 3), (0, 2)]);
assert!(p.is_some());
}
}Key Differences
path and visited with explicit push/pop is imperative; OCaml's recursive approach with immutable lists is more idiomatic but allocates more.true immediately on finding a path; OCaml uses exceptions or Option for early return through the recursion stack.OCaml Approach
OCaml implements backtracking with let rec backtrack path vis = .... OCaml's recursive style makes backtracking natural: if success then Some path else List.fold_left try_next None neighbors. The exception Found of int list pattern enables early termination when a path is found. Heuristics like Warnsdorff's rule (choose vertex with fewest onward moves first) dramatically speed up knight's tour solutions.
Full Source
#![allow(clippy::all)]
//! # Hamiltonian Path (Backtracking)
pub fn hamiltonian_path(n: usize, edges: &[(usize, usize)]) -> Option<Vec<usize>> {
let mut adj = vec![vec![false; n]; n];
for &(u, v) in edges {
adj[u][v] = true;
adj[v][u] = true;
}
let mut path = vec![0];
let mut visited = vec![false; n];
visited[0] = true;
fn backtrack(n: usize, adj: &[Vec<bool>], path: &mut Vec<usize>, vis: &mut [bool]) -> bool {
if path.len() == n {
return true;
}
let last = *path.last().unwrap();
for next in 0..n {
if !vis[next] && adj[last][next] {
vis[next] = true;
path.push(next);
if backtrack(n, adj, path, vis) {
return true;
}
path.pop();
vis[next] = false;
}
}
false
}
if backtrack(n, &adj, &mut path, &mut visited) {
Some(path)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ham() {
let p = hamiltonian_path(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (1, 3), (0, 2)]);
assert!(p.is_some());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ham() {
let p = hamiltonian_path(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0), (1, 3), (0, 2)]);
assert!(p.is_some());
}
}
Deep Comparison
OCaml vs Rust: Hamiltonian Backtrack
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
path.len() == n that adj[last][start] is true.