TS. Traveling Salesman - JulTob/Mathematics GitHub Wiki
📒 Example: The Traveling Salesman Problem (TSP)
Consider a logistics company that must design an optimal delivery route for its only plane. Given a list of cities and the travel times between them, the task is to find the fastest route that visits all cities and returns to the start (a Hamiltonian cycle).This is famously the Traveling Salesman Problem.
Modeling: Represent each city as a node, routes as edges with distances.
Computation: Searching for a minimal route can be done with approximate or exact algorithms.
We represent the times in the following diagram:
---
config:
look: handDrawn
theme: dark
---
flowchart TD
M@{ shape: notch-rect, label: "Madrid" }
P@{ shape: lean-r, label: "Paris" }
B@{ shape: lean-l, label: "Berlin" }
L@{ shape: rounded, label: "London" }
R@{ shape: lin-rect, label: "Roma" }
A@{ shape: delay, label: "Athens" }
M <==>|2h| P
M <==>|2h:30m| L
M <==>|2h:30m| R
P <==>|1h:30m| B
P <==>|1h:15m| L
P <==>|2h| R
B <==>|1h:30m| L
B <==>|2h| R
B <==>|2h:30m| A
L <==>|2h:30m| R
R <==>|2h| A
SOLUTION:
We can find the time from one city, say Madrid, to all the destinations as follows:
Route | Time |
---|---|
M-P | 2 |
M-L | 2.5 |
M-R | 2.5 |
M-P-L | 2+1.2 =3.2 (X) |
M-P-R | 2+2 = 4 (X) |
M-P-B | P: 3.5 |
M-L-B | L: 4 (X) |
M-R-B | L: 2.5 + 2 = 4.5 (X) |
M-P-B-A | 3.5 + 2.5 = 6 (X) |
M-R-A | 2.5 + 2 = 4.5 |
Here each chain of letters signals a route, and we can try getting to a city through different routes:
- We could go to London directly or through Paris.
- Madrid-Paris takes 2h.
- Madrid-London takes 2h30m.
- Is it possible it would be faster by going through Paris?
- Madrid-Paris-London takes 3h15m! ❌
- So the Direct flight is better! We mark that one as the route Madrid-x-London
- There are other paths that would end up in Paris, but as they are already over (or equal to) the 2h:30m time we already have, we may ignore these.
With this and equivalent tables for the other cities (left as an exercise) we can fill a Matrix / Table with the lesser of the paths from a city to another. We are not limited to choosing one city only once, so we could choose loops around them all that allows for a minimal loop that goes through all of them.
EXTRA QUESTION: What would be the loop's timing if you start from another city?
Algorithm (Ada)
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Traveling_Salesman is
-- 1. Define the Set of Cities
type City is (Madrid, Paris, Berlin, London, Rome, Athens);
subtype City_Index is City;
-- 2. Define "Infinity" for Unreachable Paths
Infinity : constant Integer := 10_000;
-- A large number representing "unreachable"
-- 3. Define the Distance Matrix (Graph Representation)
type Distance_Matrix_Type is array (City_Index, City_Index) of Integer;
Distance_Matrix : Distance_Matrix_Type :=
(( Infinity, 120, Infinity, 150, 150, Infinity), -- Madrid
(120, Infinity, 90, 75, 120, Infinity), -- Paris
(Infinity, 90, Infinity, 90, 120, 150), -- Berlin
(150, 75, 90, Infinity, 150, Infinity), -- London
(150, 120, 120, 150, Infinity, 120), -- Rome
(Infinity, Infinity, 150, Infinity, 120, Infinity)); -- Athens
-- 4. Define a Structure to Track a Path and Its Cost
type Path_Array is array (1 .. City'Pos(Athens) + 1) of City;
type Tour is record
Current_City : City;
Cost_So_Far : Integer;
Path : Path_Array;
Depth : Integer; -- Number of cities visited
end record;
-- 5. Store the Best Tour Found
Best_Cost : Integer := Infinity;
Best_Path : Path_Array;
-- 6. Procedure to Print a Matrix (for visualization)
procedure Print_Matrix(Matrix : Distance_Matrix_Type; Title : String) is
begin
Put_Line(Title);
for C in City loop
for D in City loop
if Matrix(C, D) = Infinity then
Put(" INF "); -- No direct path
else
Put(Integer'Image(Matrix(C, D)) & " ");
end if;
end loop;
New_Line;
end loop;
New_Line;
end Print_Matrix;
-- 7. Function to Find the Shortest Hamiltonian Cycle (Recursive Search)
type Visited_Set is array (City) of Boolean;
procedure Find_Shortest_Cycle(Current_Tour : in Tour; Visited : in out Visited_Set) is
begin
-- If all cities have been visited, return to Madrid
if Current_Tour.Depth = City'Pos(Athens) then
declare
Final_Cost : Integer := Current_Tour.Cost_So_Far + Distance_Matrix(Current_Tour.Current_City, Madrid);
begin
if Final_Cost < Best_Cost then
Best_Cost := Final_Cost;
Best_Path := Current_Tour.Path;
Best_Path(Current_Tour.Depth + 1) := Madrid; -- Completing the cycle
end if;
end;
return;
end if;
-- If the current cost exceeds the best known solution, prune this path
if Current_Tour.Cost_So_Far >= Best_Cost then
return;
end if;
-- Try visiting each unvisited city
for Next in City loop
if not Visited(Next) and then Distance_Matrix(Current_Tour.Current_City, Next) < Infinity then
declare
Next_Tour : Tour;
begin
-- Move to the next city
Next_Tour.Current_City := Next;
Next_Tour.Cost_So_Far := Current_Tour.Cost_So_Far + Distance_Matrix(Current_Tour.Current_City, Next);
Next_Tour.Path := Current_Tour.Path;
Next_Tour.Path(Current_Tour.Depth + 1) := Next;
Next_Tour.Depth := Current_Tour.Depth + 1;
-- Mark as visited
Visited(Next) := True;
Find_Shortest_Cycle(Next_Tour, Visited);
Visited(Next) := False; -- Unmark for other possibilities
end;
end if;
end loop;
end Find_Shortest_Cycle;
-- 8. Main Execution: Solve the TSP Problem
begin
-- Step 1: Print Initial Distance Matrix
Print_Matrix(Distance_Matrix, "Initial Distance Matrix (Direct Flights Only):");
-- Step 2: Apply Floyd-Warshall Algorithm to Compute Shortest Paths Between Cities
for Via in City loop
for From in City loop
for To in City loop
if (Distance_Matrix(From, Via) /= Infinity and Distance_Matrix(Via, To) /= Infinity) then
if Distance_Matrix(From, Via) + Distance_Matrix(Via, To) < Distance_Matrix(From, To) then
Distance_Matrix(From, To) := Distance_Matrix(From, Via) + Distance_Matrix(Via, To);
end if;
end if;
end loop;
end loop;
end loop;
-- Step 3: Print Final Distance Matrix After Shortest Path Computation
Print_Matrix(Distance_Matrix, "Final Distance Matrix (All-Pairs Shortest Paths):");
-- Step 4: Solve TSP from Madrid Using Recursive Pruning
declare
Start_Tour : Tour;
Visited : Visited_Set := (others => False);
begin
Start_Tour.Current_City := Madrid;
Start_Tour.Cost_So_Far := 0;
Start_Tour.Path := (others => Madrid); -- Initialize path
Start_Tour.Path(1) := Madrid;
Start_Tour.Depth := 1;
Visited(Madrid) := True;
Find_Shortest_Cycle(Start_Tour, Visited);
end;
-- Step 5: Output the Result in Hours and Minutes
Put_Line("Minimum travel time for the optimal round-trip: " &
Integer'Image(Best_Cost / 60) & " hours " &
Integer'Image(Best_Cost mod 60) & " minutes");
-- Step 6: Print the Optimal Path Taken
Put_Line("Optimal route: ");
for I in 1 .. City'Pos(Athens) + 1 loop
Put(City'Image(Best_Path(I)) & " → ");
end loop;
Put_Line("");
end Traveling_Salesman;