Solve the traveling salesman problem (TSP) in Python

The traveling salesman problem — find the shortest route that visits every city exactly once and returns to the start — is the most famous NP-hard problem there is. The default Python answers are itertools.permutations over every tour or a from-scratch nearest-neighbor heuristic. With quicopt you model it once and get the provably shortest tour.

The brute-force trap

Trying every tour is the direct translation:

brute_force.py
from itertools import permutations

D = [[0,3,4,2,7],[3,0,4,6,3],[4,4,0,5,8],[2,6,5,0,6],[7,3,8,6,0]]
n = len(D)
best = min(
    sum(D[t[i]][t[i+1]] for i in range(n-1)) + D[t[-1]][t[0]]
    for t in ((0,) + p for p in permutations(range(1, n)))
)
print(best)  # -> 19

Fine for five cities. But the number of tours is (n-1)!/2, which passes a trillion around 15 cities. Enumeration is a dead end.

Model it as a MILP

The compact Miller–Tucker–Zemlin (MTZ) formulation: a binary x[i,j] marks using the arc from city i to city j. Each city is entered once and left once, and the u position variables forbid disconnected subtours:

tsp.py
from ortools.math_opt.python import mathopt
from quicopt import Client
D = [[0,3,4,2,7],[3,0,4,6,3],[4,4,0,5,8],[2,6,5,0,6],[7,3,8,6,0]]
n = len(D)
model = mathopt.Model(name="tsp")
x = {(i,j): model.add_binary_variable(name=f"x_{i}_{j}") for i in range(n) for j in range(n) if i != j}
u = [model.add_variable(lb=0.0, ub=n - 1, name=f"u{i}") for i in range(n)]
for i in range(n):
    model.add_linear_constraint(sum(x[i,j] for j in range(n) if j != i) == 1)
    model.add_linear_constraint(sum(x[j,i] for j in range(n) if j != i) == 1)
for i in range(1, n):
    for j in range(1, n):
        if i != j:
            model.add_linear_constraint(u[i] - u[j] + n * x[i,j] <= n - 1)  # MTZ subtour elimination
model.minimize(sum(D[i][j] * x[i,j] for i in range(n) for j in range(n) if i != j))
print(Client("https://try.quicoptapi.pgi.fz-juelich.de").solve(model).display)
$ python tsp.py
├── status:     optimal
├── feasible:   true
├── objective:  19.0
├── x:          u0=0, u1=1, u2=0, u3=4, u4=2, x_0_1=0, …  (25 variables)
└── solve_time: 0.0153 s

What you get

status: optimal means the tour is proven shortest — length 19, matching the brute-force answer, found in about fifteen milliseconds. The x[i,j] arcs that are 1 spell out the route; the u variables carry the visiting order that rules out subtours.

Unlike enumeration, the MILP prunes the tour space with bounds instead of listing it, so it solves instances well past where itertools.permutations gives up. This compact MTZ formulation is best for small-to-medium instances; very large TSPs call for specialized routines (stronger cutting planes or dedicated heuristics).

Next

Reference: C. E. Miller, A. W. Tucker, R. A. Zemlin, Integer Programming Formulation of Traveling Salesman Problems, Journal of the ACM, 1960.

More cities — or a different problem?

Tell us what you're optimizing. We'll help you model it and point you at the right approach.

Talk to us →

Frequently asked questions

Isn't this the same as vehicle routing?

No — TSP is a single tour visiting every city once. Vehicle routing (VRP) splits the stops among several vehicles with capacities; it's a related but distinct model.

How many cities before brute force is hopeless?

The number of tours is (n-1)!/2 — about 181,000 at 10 cities and 6×10^16 at 20. The MILP prunes that search instead of walking it.

Is quicopt free to use?

Yes — pip install quicopt and your first call sets up a free key, no license.