|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 |
|
3 |
| -from functools import lru_cache |
4 |
| - |
5 |
| - |
6 | 3 | def tsp(distances: list[list[int]]) -> int:
|
7 | 4 | """
|
8 |
| - Solves the Travelling Salesman Problem (TSP) using dynamic programming and bitmasking. |
9 |
| -
|
10 |
| - Args: |
11 |
| - distances: 2D list where distances[i][j] is the distance between city i and city j. |
12 |
| -
|
13 |
| - Returns: |
14 |
| - Minimum cost to complete the tour visiting all cities. |
15 |
| -
|
16 |
| - Raises: |
17 |
| - ValueError: If any distance is negative. |
18 |
| - >>> tsp([[0, 10, 15, 20], [10, 0, 35, 25], [15, 35, 0, 30], [20, 25, 30, 0]]) |
| 5 | + Solves the Travelling Salesman Problem (TSP) |
| 6 | + using dynamic programming and bitmasking. |
| 7 | + Args: |
| 8 | + distances: 2D list where distances[i][j] |
| 9 | + is the distance between city i and city j. |
| 10 | + Returns: |
| 11 | + Minimum cost to complete the tour visiting all cities. |
| 12 | +
|
| 13 | + Raises: |
| 14 | + ValueError: If any distance is negative. |
| 15 | +
|
| 16 | + >>> tsp([[0, 10, 15, 20], [10, 0, 35, 25], [15, 35, 0, 30], |
| 17 | + [20, 25, 30, 0]]) |
19 | 18 | 80
|
20 |
| - >>> tsp([[0, 29, 20, 21], [29, 0, 15, 17], [20, 15, 0, 28], [21, 17, 28, 0]]) |
| 19 | + >>> tsp([[0, 29, 20, 21], [29, 0, 15, 17], [20, 15, 0, 28], |
| 20 | + [21, 17, 28, 0]]) |
21 | 21 | 69
|
22 | 22 | >>> tsp([[0, 10, -15, 20], [10, 0, 35, 25], [15, 35, 0, 30], [20, 25, 30, 0]])
|
| 23 | + # doctest: +ELLIPSIS |
23 | 24 | Traceback (most recent call last):
|
24 |
| - ... |
25 |
| - ValueError: Distance cannot be negative |
| 25 | + ... |
| 26 | + ValueError: Distance cannot be negative |
26 | 27 | """
|
27 | 28 | n = len(distances)
|
28 | 29 | if any(distances[i][j] < 0 for i in range(n) for j in range(n)):
|
29 | 30 | raise ValueError("Distance cannot be negative")
|
30 |
| - |
| 31 | + # Create a memoization table |
| 32 | + memo = [[-1] * (1 << n) for _ in range(n)] |
31 | 33 | visited_all = (1 << n) - 1
|
32 | 34 |
|
33 |
| - @lru_cache(None) |
34 | 35 | def visit(city: int, mask: int) -> int:
|
35 | 36 | """Recursively calculates the minimum cost to visit all cities."""
|
36 | 37 | if mask == visited_all:
|
37 | 38 | return distances[city][0] # Return to start
|
38 | 39 |
|
39 |
| - min_cost = float("inf") # Large value to compare against |
| 40 | + if memo[city][mask] != -1: # Return cached result if exists |
| 41 | + return memo[city][mask] |
| 42 | + |
| 43 | + min_cost = float('inf') # Large value to compare against |
40 | 44 | for next_city in range(n):
|
41 | 45 | if not mask & (1 << next_city): # If unvisited
|
42 | 46 | new_cost = distances[city][next_city] + visit(
|
43 | 47 | next_city, mask | (1 << next_city)
|
44 | 48 | )
|
45 | 49 | min_cost = min(min_cost, new_cost)
|
46 |
| - return int(min_cost) # Ensure returning an integer |
47 |
| - |
| 50 | + memo[city][mask] = min_cost # Store result in the memoization table |
| 51 | + return min_cost |
48 | 52 | return visit(0, 1) # Start from city 0 with city 0 visited
|
49 |
| - |
50 |
| - |
51 | 53 | if __name__ == "__main__":
|
52 | 54 | import doctest
|
53 |
| - |
54 | 55 | doctest.testmod()
|
0 commit comments