Hamiltonian Path
Algorithm · Stretch
Step through it: Backtracking pattern
holding [ ]
Step 1 of 7
Goal: list every subset of [1, 2]. At each number we choose: take it or skip it. All the choices form a tree of decisions. Start with nothing chosen.
Step 2 of 7
Choose to take 1.
Step 3 of 7
Take 2 as well. No numbers are left, so write this subset down.
Step 4 of 7
Undo that last choice (the "back" in backtracking) and skip 2 instead. Write [1] down.
Step 5 of 7
Undo taking 1 and skip it instead: holding [ ] again.
Step 6 of 7
From [ ], take or skip 2 the same way: [2] and [ ]. The four leaves are every subset. The recipe is choose, explore, undo. If a choice breaks a rule, stop that branch early (pruning).
Step 7 of 7
Your turn.
Hamiltonian path (or traceable path) is a path in an undirected or directed graph that visits each vertex exactly once. A Hamiltonian cycle (or Hamiltonian circuit) is a Hamiltonian path that is a cycle. Determining whether such paths and cycles exist in graphs is the Hamiltonian path problem.
One possible Hamiltonian cycle through every vertex of a dodecahedron is shown in red – like all platonic solids, the dodecahedron is Hamiltonian.
Naive Algorithm
Generate all possible configurations of vertices and print a
configuration that satisfies the given constraints. There
will be n! (n factorial) configurations.
while there are untried configurations
{
generate the next configuration
if ( there are edges between two consecutive vertices of this
configuration and there is an edge from the last vertex to
the first ).
{
print this configuration;
break;
}
}Backtracking Algorithm
Create an empty path array and add vertex 0 to it. Add other
vertices, starting from the vertex 1. Before adding a vertex,
check for whether it is adjacent to the previously added vertex
and not already added. If we find such a vertex, we add the
vertex as part of the solution. If we do not find a vertex
then we return false.
Code
import GraphVertex from '../../../data-structures/graph/GraphVertex';
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @param {GraphVertex} vertexCandidate
* @return {boolean}
*/
function isSafe(adjacencyMatrix, verticesIndices, cycle, vertexCandidate) {
const endVertex = cycle[cycle.length - 1];
// Get end and candidate vertices indices in adjacency matrix.
const candidateVertexAdjacencyIndex = verticesIndices[vertexCandidate.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if last vertex in the path and candidate vertex are adjacent.
if (adjacencyMatrix[endVertexAdjacencyIndex][candidateVertexAdjacencyIndex] === Infinity) {
return false;
}
// Check if vertexCandidate is being added to the path for the first time.
const candidateDuplicate = cycle.find((vertex) => vertex.getKey() === vertexCandidate.getKey());
return !candidateDuplicate;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {object} verticesIndices
* @param {GraphVertex[]} cycle
* @return {boolean}
*/
function isCycle(adjacencyMatrix, verticesIndices, cycle) {
// Check if first and last vertices in hamiltonian path are adjacent.
// Get start and end vertices from the path.
const startVertex = cycle[0];
const endVertex = cycle[cycle.length - 1];
// Get start/end vertices indices in adjacency matrix.
const startVertexAdjacencyIndex = verticesIndices[startVertex.getKey()];
const endVertexAdjacencyIndex = verticesIndices[endVertex.getKey()];
// Check if we can go from end vertex to the start one.
return adjacencyMatrix[endVertexAdjacencyIndex][startVertexAdjacencyIndex] !== Infinity;
}
/**
* @param {number[][]} adjacencyMatrix
* @param {GraphVertex[]} vertices
* @param {object} verticesIndices
* @param {GraphVertex[][]} cycles
* @param {GraphVertex[]} cycle
*/
function hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
}) {
// Clone cycle in order to prevent it from modification by other DFS branches.
const currentCycle = [...cycle].map((vertex) => new GraphVertex(vertex.value));
if (vertices.length === currentCycle.length) {
// Hamiltonian path is found.
// Now we need to check if it is cycle or not.
if (isCycle(adjacencyMatrix, verticesIndices, currentCycle)) {
// Another solution has been found. Save it.
cycles.push(currentCycle);
}
return;
}
for (let vertexIndex = 0; vertexIndex < vertices.length; vertexIndex += 1) {
// Get vertex candidate that we will try to put into next path step and see if it fits.
const vertexCandidate = vertices[vertexIndex];
// Check if it is safe to put vertex candidate to cycle.
if (isSafe(adjacencyMatrix, verticesIndices, currentCycle, vertexCandidate)) {
// Add candidate vertex to cycle path.
currentCycle.push(vertexCandidate);
// Try to find other vertices in cycle.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle: currentCycle,
});
// BACKTRACKING.
// Remove candidate vertex from cycle path in order to try another one.
currentCycle.pop();
}
}
}
/**
* @param {Graph} graph
* @return {GraphVertex[][]}
*/
export default function hamiltonianCycle(graph) {
// Gather some information about the graph that we will need to during
// the problem solving.
const verticesIndices = graph.getVerticesIndices();
const adjacencyMatrix = graph.getAdjacencyMatrix();
const vertices = graph.getAllVertices();
// Define start vertex. We will always pick the first one
// this it doesn't matter which vertex to pick in a cycle.
// Every vertex is in a cycle so we can start from any of them.
const startVertex = vertices[0];
// Init cycles array that will hold all solutions.
const cycles = [];
// Init cycle array that will hold current cycle path.
const cycle = [startVertex];
// Try to find cycles recursively in Depth First Search order.
hamiltonianCycleRecursive({
adjacencyMatrix,
vertices,
verticesIndices,
cycles,
cycle,
});
// Return found cycles.
return cycles;
}References
Practice
Backtracking
- Binary Tree PathsEasy
- SubsetsMedium
- Subsets IIMedium
- PermutationsMedium
- Permutations IIMedium
- CombinationsMedium
- Combination SumMedium
- Combination Sum IIMedium
- Combination Sum IIIMedium
- Letter Combinations of a Phone NumberMedium
- Generate ParenthesesMedium
- Palindrome PartitioningMedium
- Word SearchMedium
- Restore IP AddressesMedium
- Letter Case PermutationMedium
- Partition to K Equal Sum SubsetsMedium
- N-QueensHard
- Sudoku SolverHard
- Word Break IIHard
- Expression Add OperatorsHard
Spotted a mistake or something unclear? Report it on GitHub.