Visiting every vertex once is NP-complete (unlike edges, Euler Tours). Exact tools: Held–Karp bitmask DP in (or for the decision version with bitsets), inclusion–exclusion counting in polynomial space, and branch and bound. Polynomial special cases: tournaments always have a Hamiltonian path; Dirac/Ore degree conditions guarantee cycles; bitonic tours are . Metric TSP has approximations (2 via MST doubling, 3/2 Christofides); marathon-style instances use 2-opt/Or-opt local search (Heuristics).

Held–Karp

pair<ll, vector<int>> tsp(const vector<vector<ll>>& w) {     // minimum Hamiltonian cycle through 0, and the tour
    int n = sz(w); const ll INF = LLONG_MAX / 4;
    if (n == 1) return {0, {0}};
    vector<vector<ll>> dp(1 << n, vector<ll>(n, INF)); dp[1][0] = 0;
    for (int mask = 1; mask < (1 << n); mask += 2) for (int i = 0; i < n; i++) {
        if (dp[mask][i] >= INF) continue;
        for (int j = 0; j < n; j++) if (!(mask >> j & 1)) dp[mask | 1 << j][j] = min(dp[mask | 1 << j][j], dp[mask][i] + w[i][j]);
    }
    int full = (1 << n) - 1, last = -1; ll best = INF;
    for (int i = 1; i < n; i++) if (dp[full][i] + w[i][0] < best) best = dp[full][i] + w[i][0], last = i;
    vector<int> tour; int mask = full, cur = last;                  // walk back by re-deriving predecessors
    while (cur != 0) {
        tour.push_back(cur); int pm = mask ^ 1 << cur, prev = 0;
        for (int j = 0; j < n; j++) if ((pm >> j & 1) && dp[pm][j] < INF && dp[pm][j] + w[j][cur] == dp[mask][cur]) { prev = j; break; }
        mask = pm; cur = prev;
    }
    tour.push_back(0); reverse(all(tour));
    return {best, tour};
}

Memory is the binding constraint: values of 8 bytes is 168 MB. Store 32-bit costs, or only the masks containing vertex 0 (half), or iterate by popcount layers.

states transitionsmemory (8-byte cells)
154 MB
1838 MB
20168 MB — tight
22738 MB — usually too much

Decision version in : let ends[mask] be the bitmask of vertices such that some Hamiltonian path of mask ends at ; then iff .

bool hamiltonianPath(const vector<int>& adj) {               // adj[v] = neighbour bitmask, n <= ~25
    int n = sz(adj); vector<int> ends(1 << n);
    for (int v = 0; v < n; v++) ends[1 << v] = 1 << v;
    for (int mask = 1; mask < (1 << n); mask++) for (int v = 0; v < n; v++)
        if ((mask >> v & 1) && mask != (1 << v) && (ends[mask ^ 1 << v] & adj[v])) ends[mask] |= 1 << v;
    return ends[(1 << n) - 1] != 0;
}

Counting Hamiltonian paths/cycles in polynomial space

Inclusion–exclusion over the set of vertices allowed: the number of Hamiltonian paths equals , where is the number of walks with edges using only vertices of (computed by a DP over walk length in per subset). time, extra memory.

ll countHamPaths(const vector<vector<int>>& g, ll p) {        // number of Hamiltonian paths (directed sequences) mod p
    int n = sz(g); ll res = 0;
    for (int S = 0; S < (1 << n); S++) {
        vector<ll> cur(n), nxt(n);
        for (int v = 0; v < n; v++) cur[v] = S >> v & 1;
        for (int step = 1; step < n; step++) {
            fill(all(nxt), 0);
            for (int u = 0; u < n; u++) if (cur[u]) for (int v : g[u]) if (S >> v & 1) nxt[v] = (nxt[v] + cur[u]) % p;
            swap(cur, nxt);
        }
        ll walks = 0; for (ll x : cur) walks = (walks + x) % p;
        res = ((n - __builtin_popcount(S)) % 2 ? res - walks + p : res + walks) % p;
    }
    return res;
}

Existence results

ResultStatement
Rédeievery tournament has a Hamiltonian path (insertion: binary search where to insert each new vertex)
Camiona strongly connected tournament has a Hamiltonian cycle
Dirac, ⟹ Hamiltonian cycle
Ore for all non-adjacent ⟹ Hamiltonian cycle
Bondy–Chvátal is Hamiltonian iff its closure (repeatedly join non-adjacent with ) is
necessaryno cut vertex (for a cycle); bipartite with unequal sides ⟹ no cycle; at most two degree-1 vertices (for a path)
grid graphpolynomial-time decidable (Itai–Papadimitriou–Szwarcfiter)
interval graphpolynomial
treeHamiltonian path iff the tree already is one
cubic planarstill NP-complete

The bipartite-parity and cut-vertex checks above are the cheapest necessary conditions — apply them before any expensive search.

vector<int> tournamentPath(int n, function<bool(int, int)> beats) {   // beats(u, v): edge u -> v
    vector<int> path;
    for (int v = 0; v < n; v++) {                           // insert v before the first vertex it beats, found by binary search
        int lo = 0, hi = sz(path);                         // invariant: path[lo-1] -> v (or lo = 0), v -> path[hi] (or hi = end)
        while (lo < hi) { int mid = (lo + hi) / 2; if (beats(path[mid], v)) lo = mid + 1; else hi = mid; }
        path.insert(path.begin() + lo, v);
    }
    return path;
}

TSP beyond exact DP

SettingMethodGuarantee
bitonic tours (points sorted by , go right then return left) = best pair of paths ending at , extend with exact,
metricdouble the MST and shortcut2
metricChristofides (MST + min perfect matching on odd vertices)3/2
EuclideanArora’s PTAS
general (no triangle inequality)no constant approximation unless P = NP—
marathon instancesnearest neighbour + 2-opt / Or-opt / Lin–Kernighan, simulated annealingnone, near-optimal in practice
–branch and bound with 1-tree (Held–Karp Lagrangian) boundsexact
bottleneck TSPbinary search on the threshold + Hamiltonicity testexact if the test is

Branch and bound pruning

For plain Hamiltonian-path search beyond the bitmask range, strong pruning matters more than the search order:

  • prune when an unvisited vertex has degree 0 in the remaining graph;
  • prune when the remaining graph becomes disconnected;
  • prune when two or more unvisited vertices have degree 1 (at most one can be the path’s other endpoint);
  • order candidates by fewest remaining options first (Warnsdorff’s rule — the reason knight’s tours are solvable on large boards almost instantly).

For TSP branch and bound specifically, lower bounds in increasing strength:

  1. sum of the two cheapest edges at each city, halved;
  2. MST of the unvisited cities plus the two cheapest edges connecting it back to the visited path;
  3. the Held–Karp 1-tree bound — a Lagrangian relaxation that iteratively adjusts per-vertex penalties, typically within 1% of optimal; this is a different “Held–Karp” from the exact DP above (same two authors, same paper, two different results — don’t confuse a bitmask-DP solver with a 1-tree-bound pruner when reading about either), and the reason Concorde solves enormous instances exactly.

Variants worth naming

VariantNote
Path instead of cycledrop the closing edge
Asymmetric TSP ()Held–Karp is unchanged; Christofides does not apply (needs the triangle inequality on a symmetric metric)
Bottleneck TSPbinary search the threshold + Hamiltonicity test
TSP with time windowsmuch harder in general; usually heuristic
Multiple salesmen (mTSP)add copies of the depot
Vehicle routing (VRP)TSP plus capacities — the industrial version
Steiner TSPmay skip non-required cities — Dreyfus–Wagner territory ([[21-complete-search/07-steiner-tree