Implementation

class HamiltonianPath:
    def __init__(self, edges):
        self.g = HamiltonianPath.__create_graph(edges)

    @ staticmethod
    def __create_graph(edges):
        g = {}
        for u, v in edges:
            g[u] = set()
            g[v] = set()
        for u, v in edges:
            g[u].add(v)
        return g

    def getHamiltonianPath(self):
        def dfs(start):
            s = [(start, None)]
            h_path = []
            visited = set()
            while s:
                top, parent = s.pop()
                h_path.append(top)
                visited.add(top)
                if len(h_path) == len(self.g):
                    return h_path

                none = True
                for edge in self.g[top]:
                    if edge not in visited:
                        none = False
                        s.append((edge, top))
                if none:
                    if not s:
                        return []
                    # back track until we reach the next element in the stack
                    # backtracking in this context means iterating backwards
                    # through the current path we collected
                    #
                    # our stopping condition is when:
                    # 1. The current element in the path is connected to the top of the stack
                    # 2. The parent of the top of the stack is current path element
                    while h_path and s[-1][0] not in self.g[h_path[-1]] or s[-1][1] != h_path[-1]:
                        tmp = h_path.pop()
                        visited.remove(tmp)
            return []

        for vertex, _ in self.g.items():
            s = dfs(vertex)
            if s:
                return s
        return []

Testing

hg1 = HamiltonianPath(edges = [
    [0, 1], [1, 0],
    [1, 2], [2, 1],
    [1, 3], [3, 1]
])
print(hg1.getHamiltonianPath())


hg2 = HamiltonianPath(edges = [
    [0, 1], [1, 0],
    [1, 2], [2, 1],
    [1, 3], [3, 1],
    [3, 2]
])
print(hg2.getHamiltonianPath())


hg3 = HamiltonianPath(edges = [
    [10, 6], [6, 10],
    [6, 3], [3, 6],
    [3, 1], [1, 3],
    [1, 8], [8, 1],
    [3, 13], [13, 3],
    [13, 12], [12, 13],
    [12, 4], [4, 12],
    [4, 5], [5, 4],
    [5, 11], [11, 5],
    [11, 14], [14, 11],
    [14, 2], [2, 14],
    [2, 7], [7, 2],
    [7, 9], [9, 7]
])
print(hg3.getHamiltonianPath())


hg4 = HamiltonianPath(edges = [
    [10, 6], [6, 10],
    [6, 3], [3, 6],
    [3, 1], [1, 3],
    [1, 8], [8, 1],
    [3, 13], [13, 3],
    [13, 12], [12, 13],
    [12, 4], [4, 12],
    [4, 5], [5, 4],
    [5, 11], [11, 5],
    [11, 14], [14, 11],
    [14, 2], [2, 14],
    [2, 7], [7, 2],
    [7, 9], [9, 7],
    [15, 1], [1, 15],
    [15, 10], [10, 15]
])
print(hg4.getHamiltonianPath())


hg4 = HamiltonianPath(edges = [
    [10, 6], [6, 10],
    [6, 3], [3, 6],
    [3, 1], [1, 3],
    [1, 8], [8, 1],
    [3, 13], [13, 3],
    [13, 12], [12, 13],
    [12, 4], [4, 12],
    [4, 5], [5, 4],
    [5, 11], [11, 5],
    [11, 14], [14, 11],
    [14, 2], [2, 14],
    [2, 7], [7, 2],
    [7, 9], [9, 7],
    [15, 1], [1, 15],
    [15, 10], [10, 15],
    [9, 16], [16, 9],
    [17, 8], [8, 17],
    [7, 18], [18, 7]
])
print(hg4.getHamiltonianPath())

hg5 = HamiltonianPath(edges = [
    ['E', 'D'], ['D', 'E'],
    ['D', 'C'], ['C', 'D'],
    ['D', 'A'], ['A', 'D'],
    ['C', 'B'], ['B', 'C'],
    ['C', 'A'], ['A', 'C'],
    ['A', 'B'], ['B', 'A'],
])
print(hg5.getHamiltonianPath())

# output
# []
# [0, 1, 3, 2]
# []
# [8, 1, 15, 10, 6, 3, 13, 12, 4, 5, 11, 14, 2, 7, 9]
# []
# ['E', 'D', 'C', 'A', 'B']

Discussion

The biggest challenge for me when implementing this iteratively, was knowing when stop backtracking. Consider the graph that does not have a Hamiltonian Path:

Our DFS tree for vertex 1 looks like follows:

Last updated