1- """ Travelling Salesman Problem (TSP) """
1+ """Travelling Salesman Problem (TSP)"""
22
33import itertools
44import math
55
6+
67class InvalidGraphError (ValueError ):
78 """Custom error for invalid graph inputs."""
89
10+
911def euclidean_distance (point1 : list [float ], point2 : list [float ]) -> float :
1012 """
1113 Calculate the Euclidean distance between two points in 2D space.
@@ -28,6 +30,7 @@ def euclidean_distance(point1: list[float], point2: list[float]) -> float:
2830 except TypeError :
2931 raise ValueError ("Invalid input: Points must be numerical coordinates" )
3032
33+
3134def validate_graph (graph_points : dict [str , list [float ]]) -> None :
3235 """
3336 Validate the input graph to ensure it has valid nodes and coordinates.
@@ -41,12 +44,12 @@ def validate_graph(graph_points: dict[str, list[float]]) -> None:
4144 Traceback (most recent call last):
4245 ...
4346 InvalidGraphError: Each node must have a valid 2D coordinate [x, y]
44-
47+
4548 >>> validate_graph([10, 20]) # Invalid input type
4649 Traceback (most recent call last):
4750 ...
4851 InvalidGraphError: Graph must be a dictionary with node names and coordinates
49-
52+
5053 >>> validate_graph({"A": [10, 20], "B": [30, 21], "C": [15]}) # Missing coordinate
5154 Traceback (most recent call last):
5255 ...
@@ -66,6 +69,7 @@ def validate_graph(graph_points: dict[str, list[float]]) -> None:
6669 ):
6770 raise InvalidGraphError ("Each node must have a valid 2D coordinate [x, y]" )
6871
72+
6973# TSP in Brute Force Approach
7074def travelling_salesman_brute_force (
7175 graph_points : dict [str , list [float ]],
@@ -89,7 +93,7 @@ def travelling_salesman_brute_force(
8993 raise InvalidGraphError ("Graph must have at least two nodes" )
9094
9195 min_path = [] # List that stores shortest path
92- min_distance = float ("inf" ) # Initialize minimum distance to infinity
96+ min_distance = float ("inf" ) # Initialize minimum distance to infinity
9397
9498 start_node = nodes [0 ]
9599 other_nodes = nodes [1 :]
@@ -111,6 +115,7 @@ def travelling_salesman_brute_force(
111115
112116 return min_path , min_distance
113117
118+
114119# TSP in Dynamic Programming approach
115120def travelling_salesman_dynamic_programming (
116121 graph_points : dict [str , list [float ]],
@@ -127,20 +132,26 @@ def travelling_salesman_dynamic_programming(
127132 """
128133 validate_graph (graph_points )
129134
130- n = len (graph_points ) # Extracting the node names (keys)
135+ n = len (graph_points ) # Extracting the node names (keys)
131136
132137 # There shoukd be atleast 2 nodes for a valid TSP
133138 if n < 2 :
134139 raise InvalidGraphError ("Graph must have at least two nodes" )
135140
136- nodes = list (graph_points .keys ()) # Extracting the node names (keys)
141+ nodes = list (graph_points .keys ()) # Extracting the node names (keys)
137142
138143 # Initialize distance matrix with float values
139- dist = [[euclidean_distance (graph_points [nodes [i ]], graph_points [nodes [j ]]) for j in range (n )] for i in range (n )]
140-
141- # Initialize a dynamic programming table with infinity
144+ dist = [
145+ [
146+ euclidean_distance (graph_points [nodes [i ]], graph_points [nodes [j ]])
147+ for j in range (n )
148+ ]
149+ for i in range (n )
150+ ]
151+
152+ # Initialize a dynamic programming table with infinity
142153 dp = [[float ("inf" )] * n for _ in range (1 << n )]
143- dp [1 ][0 ] = 0 # Only visited node is the starting point at node 0
154+ dp [1 ][0 ] = 0 # Only visited node is the starting point at node 0
144155
145156 # Iterate through all masks of visited nodes
146157 for mask in range (1 << n ):
@@ -149,14 +160,16 @@ def travelling_salesman_dynamic_programming(
149160 if mask & (1 << u ):
150161 # Traverse nodes 'v' such that u->v
151162 for v in range (n ):
152- if mask & (1 << v ) == 0 : # If v is not visited
153- next_mask = mask | (1 << v ) # Upodate mask to include 'v'
163+ if mask & (1 << v ) == 0 : # If v is not visited
164+ next_mask = mask | (1 << v ) # Upodate mask to include 'v'
154165 # Update dynamic programming table with minimum distance
155- dp [next_mask ][v ] = min (dp [next_mask ][v ], dp [mask ][u ] + dist [u ][v ])
166+ dp [next_mask ][v ] = min (
167+ dp [next_mask ][v ], dp [mask ][u ] + dist [u ][v ]
168+ )
156169
157170 final_mask = (1 << n ) - 1
158171 min_cost = float ("inf" )
159- end_node = - 1 # Track the last node in the optimal path
172+ end_node = - 1 # Track the last node in the optimal path
160173
161174 for u in range (1 , n ):
162175 if min_cost > dp [final_mask ][u ] + dist [u ][0 ]:
@@ -175,7 +188,7 @@ def travelling_salesman_dynamic_programming(
175188 == dp [mask ^ (1 << end_node )][u ] + dist [u ][end_node ]
176189 ):
177190 mask ^= 1 << end_node # Update mask to remove end node
178- end_node = u # Set the previous node as end node
191+ end_node = u # Set the previous node as end node
179192 break
180193
181194 path .append (nodes [0 ]) # Bottom-up Order
0 commit comments