9.19 Hamiltonian Paths
You need to visit every vertex exactly once.
9.19 Hamiltonian Paths
Problem
You need to visit every vertex exactly once.
Many graph problems focus on covering all vertices rather than all edges.
Examples include:
- Visiting every city in a tour
- Scheduling visits to every location
- Traversing every checkpoint
- Planning routes through attractions
- Sequencing tasks with transition constraints
Unlike Euler paths, which use every edge exactly once, these problems care about vertices.
You need to determine whether a traversal exists that visits every vertex exactly once.
Solution
Use a Hamiltonian path.
A Hamiltonian path is a path that visits every vertex exactly once.
A Hamiltonian cycle is a Hamiltonian path that returns to its starting vertex.
Example:
A -- B -- C -- D
Hamiltonian path:
A → B → C → D
Every vertex appears exactly once.
Now consider:
A -- B
| |
D -- C
Hamiltonian cycle:
A → B → C → D → A
Every vertex appears once, and the path returns to the start.
Discussion
Hamiltonian paths and Euler paths are frequently confused.
The distinction is fundamental.
| Problem | Requirement |
|---|---|
| Euler path | Visit every edge exactly once |
| Hamiltonian path | Visit every vertex exactly once |
Consider:
A -- B -- C
The traversal:
A → B → C
is both Eulerian and Hamiltonian.
However, the two concepts quickly diverge.
Example:
A
/|\\n B C D
A Hamiltonian path exists:
B → A → C
but it cannot visit every edge.
Conversely, many Eulerian graphs contain no Hamiltonian cycle.
The problems are fundamentally different.
Why Hamiltonian Problems Are Difficult
Euler path existence can be checked using degree conditions.
Hamiltonian paths have no comparable simple characterization.
For example:
A -- B -- C -- D
has a Hamiltonian path.
Now add one edge:
A -- B -- C
| |
+---------+
A Hamiltonian cycle exists.
The addition of a single edge changes the problem dramatically.
Unlike Euler paths, local information such as vertex degree is often insufficient.
The difficulty of Hamiltonian problems is one reason they play a central role in complexity theory.
Brute Force Approach
The simplest strategy tries every possible ordering.
Suppose:
n vertices
exist.
There are:
n!
possible vertex permutations.
For each permutation:
- Check whether consecutive vertices are connected.
- Verify that every vertex appears exactly once.
Example:
from itertools import permutations
def hamiltonian_path(graph):
vertices = list(graph)
for path in permutations(vertices):
valid = True
for i in range(len(path) - 1):
if path[i + 1] not in graph[path[i]]:
valid = False
break
if valid:
return list(path)
return None
This works for very small graphs.
It becomes impractical quickly.
Backtracking Search
A more efficient approach builds the path incrementally.
Instead of generating all permutations, extend only valid partial paths.
def hamiltonian_path(graph):
n = len(graph)
def search(path, visited):
if len(path) == n:
return path
current = path[-1]
for neighbor in graph[current]:
if neighbor not in visited:
result = search(
path + [neighbor],
visited | {neighbor}
)
if result:
return result
return None
for start in graph:
result = search(
[start],
{start}
)
if result:
return result
return None
This approach avoids exploring impossible branches.
For many practical instances, it performs much better than brute force.
Example
Consider:
graph = {
"A": ["B", "D"],
"B": ["A", "C"],
"C": ["B", "D"],
"D": ["A", "C"],
}
Result:
[
"A",
"B",
"C",
"D"
]
Every vertex appears exactly once.
The path is Hamiltonian.
Hamiltonian Cycles
A Hamiltonian cycle returns to the starting vertex.
After constructing a Hamiltonian path:
A → B → C → D
verify:
D → A
exists.
If so:
A → B → C → D → A
is a Hamiltonian cycle.
The verification is simple.
def is_hamiltonian_cycle(graph, path):
if len(path) != len(graph):
return False
return path[0] in graph[path[-1]]
Complete Graphs
A complete graph contains every possible edge.
Example:
A -- B
|\ /|
| \/ |
| /\ |
|/ \|
C -- D
Every pair of vertices is connected.
Every complete graph with at least three vertices contains a Hamiltonian cycle.
Example:
A → B → C → D → A
Many such cycles exist.
Complete graphs are therefore the easiest Hamiltonian graphs.
Trees
Trees provide an interesting contrast.
Consider:
A
/ \\n B C
A Hamiltonian path exists:
B → A → C
Now consider:
A
/ | \\n B C D
No Hamiltonian path exists.
Once you leave:
B
you cannot return.
The branching structure prevents visiting every leaf exactly once.
Trees rarely contain Hamiltonian cycles.
In fact:
No tree with more than two vertices contains a Hamiltonian cycle.
The cycle would require an extra edge.
Degree Observations
Degree alone cannot determine Hamiltonicity.
However, some useful observations exist.
If a graph contains:
More than two vertices of degree one
then a Hamiltonian path is impossible.
Why?
Every degree-one vertex must appear at an endpoint of the path.
A path has only two endpoints.
Therefore:
Degree-one vertices > 2
immediately rules out a Hamiltonian path.
This is a useful pruning rule.
Directed Hamiltonian Paths
The concept extends naturally to directed graphs.
Example:
A → B → C → D
Hamiltonian path:
A → B → C → D
The direction must be respected.
The backtracking algorithm changes only slightly.
Neighbor expansion follows outgoing edges.
Everything else remains the same.
Traveling Salesman Connection
Hamiltonian cycles form the basis of the Traveling Salesman Problem (TSP).
Suppose:
Cities = vertices
Roads = edges
A Hamiltonian cycle visits every city exactly once.
TSP adds weights:
Distance
Cost
Time
and asks:
Which Hamiltonian cycle has minimum total cost?
This transforms a feasibility problem into an optimization problem.
Many route-planning systems build upon this idea.
Recipe: Hamiltonian Cycle Search
def hamiltonian_cycle(graph):
n = len(graph)
def search(path, visited):
if len(path) == n:
if path[0] in graph[path[-1]]:
return path + [path[0]]
return None
current = path[-1]
for neighbor in graph[current]:
if neighbor not in visited:
result = search(
path + [neighbor],
visited | {neighbor}
)
if result:
return result
return None
for start in graph:
result = search(
[start],
{start}
)
if result:
return result
return None
Example:
[
"A",
"B",
"C",
"D",
"A"
]
This is a Hamiltonian cycle.
Complexity Analysis
Unlike BFS, DFS, or shortest-path algorithms, Hamiltonian path search is generally exponential.
For a graph with:
n vertices
the search space may approach:
O(n!)
or:
O(2ⁿ · n)
depending on implementation.
| Method | Complexity |
|---|---|
| Brute force permutations | O(n!) |
| Backtracking | Exponential worst case |
| Dynamic programming (Held-Karp) | O(2ⁿ · n²) |
| BFS | O(V + E) |
| DFS | O(V + E) |
This dramatic difference is why Hamiltonian problems are considered computationally difficult.
Common Pitfalls
Do not confuse Hamiltonian paths with Euler paths.
Do not assume high connectivity guarantees a Hamiltonian cycle.
Do not use degree parity rules from Euler graphs.
Those rules do not apply.
Do not forget to verify the closing edge when searching for a Hamiltonian cycle.
Do not expect polynomial-time algorithms for arbitrary graphs.
Do not revisit vertices during the search.
The entire definition depends on visiting each vertex exactly once.
Recipe: Validate a Hamiltonian Path
Given a candidate path:
def validate_hamiltonian_path(
graph,
path
):
if len(path) != len(graph):
return False
if len(set(path)) != len(graph):
return False
for i in range(len(path) - 1):
if path[i + 1] not in graph[path[i]]:
return False
return True
This is useful for testing generated solutions.
Takeaway
Hamiltonian paths visit every vertex exactly once, while Hamiltonian cycles additionally return to the starting vertex. Unlike Euler problems, which have elegant degree-based characterizations, Hamiltonian problems are significantly more difficult and often require exponential-time search. Despite their computational complexity, they appear naturally in routing, scheduling, logistics, circuit design, and optimization problems such as the Traveling Salesman Problem. Understanding Hamiltonian paths provides an important introduction to graph-search problems where global structure matters more than local properties.