|
| 1 | +""" |
| 2 | +title : Travelling Sales man Problem |
| 3 | +references : https://en.wikipedia.org/wiki/Travelling_salesman_problem |
| 4 | +author : SunayBhoyar |
| 5 | +""" |
| 6 | +import itertools |
| 7 | +import math |
| 8 | + |
| 9 | +demo_graph_points = { |
| 10 | + "A": [10, 20], |
| 11 | + "B": [30, 21], |
| 12 | + "C": [15, 35], |
| 13 | + "D": [40, 10], |
| 14 | + "E": [25, 5], |
| 15 | + "F": [5, 15], |
| 16 | + "G": [50, 25] |
| 17 | +} |
| 18 | + |
| 19 | +# Euclidean distance - shortest distance between 2 points |
| 20 | +def distance(point1, point2): |
| 21 | + """ |
| 22 | + Calculate the Euclidean distance between two points. |
| 23 | + @input: point1, point2 (coordinates of two points as lists [x, y]) |
| 24 | + @return: Euclidean distance between point1 and point2 |
| 25 | + @example: |
| 26 | + >>> distance([0, 0], [3, 4]) |
| 27 | + 5.0 |
| 28 | + """ |
| 29 | + return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2) |
| 30 | + |
| 31 | +""" |
| 32 | +Brute force |
| 33 | +Time Complexity - O(n!×n) |
| 34 | +Space Complexity - O(n) |
| 35 | +""" |
| 36 | +def travelling_sales_man_problem_brute_force(graph_points): |
| 37 | + """ |
| 38 | + Solve the Travelling Salesman Problem using brute force (permutations). |
| 39 | + @input: graph_points (dictionary with node names as keys and coordinates as values) |
| 40 | + @return: shortest path, total distance (list of nodes representing the shortest path and the distance of that path) |
| 41 | + @example: |
| 42 | + >>> travelling_sales_man_problem_brute_force({'A': [0, 0], 'B': [0, 1], 'C': [1, 0]}) |
| 43 | + (['A', 'B', 'C', 'A'], 3.414) |
| 44 | + """ |
| 45 | + nodes = list(graph_points.keys()) |
| 46 | + |
| 47 | + min_path = None |
| 48 | + min_distance = float('inf') |
| 49 | + |
| 50 | + # Considering the first Node as the start position |
| 51 | + start_node = nodes[0] |
| 52 | + other_nodes = nodes[1:] |
| 53 | + |
| 54 | + for perm in itertools.permutations(other_nodes): |
| 55 | + path = [start_node] + list(perm) + [start_node] |
| 56 | + total_distance = 0 |
| 57 | + for i in range(len(path) - 1): |
| 58 | + total_distance += distance(graph_points[path[i]], graph_points[path[i+1]]) |
| 59 | + |
| 60 | + if total_distance < min_distance: |
| 61 | + min_distance = total_distance |
| 62 | + min_path = path |
| 63 | + |
| 64 | + return min_path, min_distance |
| 65 | + |
| 66 | +""" |
| 67 | +dynamic_programming |
| 68 | +Time Complexity - O(n^2×2^n) |
| 69 | +Space Complexity - O(n×2^n) |
| 70 | +""" |
| 71 | +def travelling_sales_man_problem_dp(graph_points): |
| 72 | + """ |
| 73 | + Solve the Travelling Salesman Problem using dynamic programming. |
| 74 | + @input: graph_points (dictionary with node names as keys and coordinates as values) |
| 75 | + @return: shortest path, total distance (list of nodes representing the shortest path and the distance of that path) |
| 76 | + @example: |
| 77 | + >>> travelling_sales_man_problem_dp({'A': [0, 0], 'B': [0, 1], 'C': [1, 0]}) |
| 78 | + (['A', 'B', 'C', 'A'], 3.414) |
| 79 | + """ |
| 80 | + n = len(graph_points) |
| 81 | + nodes = list(graph_points.keys()) |
| 82 | + |
| 83 | + # Precompute distances between every pair of nodes |
| 84 | + dist = [[0] * n for _ in range(n)] |
| 85 | + for i in range(n): |
| 86 | + for j in range(n): |
| 87 | + dist[i][j] = distance(graph_points[nodes[i]], graph_points[nodes[j]]) |
| 88 | + |
| 89 | + # dp[mask][i] represents the minimum distance to visit all nodes in the 'mask' set, ending at node i |
| 90 | + dp = [[float('inf')] * n for _ in range(1 << n)] |
| 91 | + dp[1][0] = 0 # Start at node 0 |
| 92 | + |
| 93 | + # Iterate over all subsets of nodes (represented by mask) |
| 94 | + for mask in range(1 << n): |
| 95 | + for u in range(n): |
| 96 | + if mask & (1 << u): |
| 97 | + for v in range(n): |
| 98 | + if mask & (1 << v) == 0: |
| 99 | + next_mask = mask | (1 << v) |
| 100 | + dp[next_mask][v] = min(dp[next_mask][v], dp[mask][u] + dist[u][v]) |
| 101 | + |
| 102 | + # Reconstruct the path and find the minimum distance to return to the start |
| 103 | + final_mask = (1 << n) - 1 |
| 104 | + min_cost = float('inf') |
| 105 | + end_node = -1 |
| 106 | + |
| 107 | + # Find the minimum distance from any node back to the starting node |
| 108 | + for u in range(1, n): |
| 109 | + if min_cost > dp[final_mask][u] + dist[u][0]: |
| 110 | + min_cost = dp[final_mask][u] + dist[u][0] |
| 111 | + end_node = u |
| 112 | + |
| 113 | + # Reconstruct the path using the dp table |
| 114 | + path = [] |
| 115 | + mask = final_mask |
| 116 | + while end_node != 0: |
| 117 | + path.append(nodes[end_node]) |
| 118 | + for u in range(n): |
| 119 | + if mask & (1 << u) and dp[mask][end_node] == dp[mask ^ (1 << end_node)][u] + dist[u][end_node]: |
| 120 | + mask ^= (1 << end_node) |
| 121 | + end_node = u |
| 122 | + break |
| 123 | + |
| 124 | + path.append(nodes[0]) |
| 125 | + path.reverse() |
| 126 | + |
| 127 | + return path, min_cost |
| 128 | + |
| 129 | + |
| 130 | +if __name__ == "__main__": |
| 131 | + print(f"Travelling salesman problem solved using Brute Force:") |
| 132 | + path, distance_travelled = travelling_sales_man_problem_brute_force(demo_graph_points) |
| 133 | + print(f"Shortest path: {path}") |
| 134 | + print(f"Total distance: {distance_travelled:.2f}") |
| 135 | + |
| 136 | + print(f"\nTravelling salesman problem solved using Dynamic Programming:") |
| 137 | + path, distance_travelled = travelling_sales_man_problem_dp(demo_graph_points) |
| 138 | + print(f"Shortest path: {path}") |
| 139 | + print(f"Total distance: {distance_travelled:.2f}") |
0 commit comments