Skip to content

Eulerian and Hamiltonian Paths

Eulerian paths visit every EDGE exactly once; Hamiltonian paths visit every VERTEX exactly once. Eulerian problems are polynomial (simple degree conditions + Hierholzer's O(|E|) algorithm). Hamiltonian problems are NP-complete.

Key Comparison

Eulerian Hamiltonian
What to traverse Every edge exactly once Every vertex exactly once
Complexity to find O(|E|) - polynomial NP-complete
Existence conditions Simple degree conditions No simple characterization
Algorithm Hierholzer's Backtracking O(n!), DP O(2^n * n^2)

Eulerian Trail/Circuit Conditions

Undirected Graph

Condition Result
All non-zero-degree vertices connected AND all even degree Eulerian circuit
All non-zero-degree vertices connected AND exactly 2 odd-degree vertices Eulerian trail (start/end at odd vertices)
More than 2 odd-degree vertices No Eulerian trail

Directed Graph

Condition Result
Strongly connected AND in-degree == out-degree for all Eulerian circuit
Weakly connected AND exactly one vertex out-in=1 (start), one in-out=1 (end), rest balanced Eulerian trail

Patterns

Check Eulerian Circuit (Undirected)

def has_eulerian_circuit(graph):
    non_isolated = [v for v in graph.adj_list if graph.adj_list[v]]
    if not non_isolated:
        return True
    # Check connectivity via DFS
    visited = set()
    stack = [non_isolated[0]]
    while stack:
        v = stack.pop()
        if v not in visited:
            visited.add(v)
            for u in graph.adj_list[v]:
                stack.append(u)
    if any(v not in visited for v in non_isolated):
        return False
    # Check all degrees even
    return all(len(graph.adj_list[v]) % 2 == 0 for v in graph.adj_list)

Hierholzer's Algorithm - O(|E|)

def hierholzer(graph):
    start = next(v for v in graph.adj_list if graph.adj_list[v])
    adj = {v: list(neighbors) for v, neighbors in graph.adj_list.items()}
    stack = [start]
    circuit = []

    while stack:
        v = stack[-1]
        if adj[v]:
            u = adj[v].pop()
            adj[u].remove(v)  # remove reverse edge for undirected
            stack.append(u)
        else:
            circuit.append(stack.pop())

    return circuit[::-1]

Reconstruct Itinerary (Directed Eulerian Path)

Given airline tickets, reconstruct lexicographic itinerary using all tickets exactly once.

from collections import defaultdict

def find_itinerary(tickets):
    graph = defaultdict(list)
    for src, dst in sorted(tickets, reverse=True):  # reverse sort for efficient pop
        graph[src].append(dst)

    result = []
    stack = ["JFK"]
    while stack:
        while graph[stack[-1]]:
            stack.append(graph[stack[-1]].pop())
        result.append(stack.pop())

    return result[::-1]

Hamiltonian Path - Backtracking O(n!)

def hamiltonian_path(graph, path, visited):
    if len(path) == len(graph.adj_list):
        return path

    current = path[-1]
    for neighbor in graph.adj_list[current]:
        if neighbor not in visited:
            visited.add(neighbor)
            path.append(neighbor)
            result = hamiltonian_path(graph, path, visited)
            if result:
                return result
            path.pop()
            visited.remove(neighbor)
    return None

def find_hamiltonian(graph):
    for start in graph.adj_list:
        result = hamiltonian_path(graph, [start], {start})
        if result:
            return result
    return None

Held-Karp DP for Hamiltonian - O(2^n * n^2)

Better than O(n!) for exact solution. See [[traveling-salesman-problem]] for bitmask DP implementation.

Key Facts

  • Euler (1736): Seven Bridges of Konigsberg - first graph theory result
  • Ore's theorem: If deg(u) + deg(v) >= n for all non-adjacent pairs, Hamiltonian cycle exists (sufficient, not necessary)
  • Hierholzer's works by greedily following edges until stuck, then backtracking to find extensions
  • Historical origin of graph theory: can you cross all 7 bridges exactly once?

Gotchas

  • Eulerian = edges, Hamiltonian = vertices - easy to confuse
  • Hierholzer's for undirected graphs must remove edges in BOTH directions
  • For Hamiltonian, no simple condition exists (unlike Eulerian) - must try or use DP
  • Hamiltonian path vs cycle: path doesn't return to start, cycle does
  • Euler trail with 2 odd-degree vertices: MUST start at one and end at the other

See Also

  • [[traveling-salesman-problem]] - Hamiltonian cycle with minimum weight
  • [[graph-traversal-bfs-dfs]] - DFS used in Hierholzer's
  • [[complexity-classes]] - Hamiltonian path is NP-complete