The Traveling Salesman Problem (TSP) can be viewed as finding a Hamiltonian cycle in a weighted graph (where the weights represent distances or costs), with the additional constraint that this cycle must have the minimum total weight. In other words, TSP seeks the shortest possible tour that visits each city exactly once and returns to the starting city.
Pre Requisities
Hamiltonian Path and Hamiltonian Cycle:
Hamiltonian Path:
A Hamiltonian path in a graph is a path that visits each vertex exactly once. In other words, it’s a way to traverse the graph such that every node is visited once and only once. If a Hamiltonian path exists that starts at one vertex and ends at another, it is known as a Hamiltonian circuit.
Hamiltonian Cycle:
A Hamiltonian cycle is a special case of a Hamiltonian path. It is a cycle that visits each vertex exactly once, and it returns to the starting vertex, forming a closed loop.
Real World Problem Statement:
A real-world problem related to Hamiltonian paths and cycles could be the Delivery Route Problem:
Problem Statement:
Imagine a delivery person needs to visit a set of locations (represented as nodes in a graph) to drop off packages. The goal is to find the shortest route that starts and ends at the same location, while visiting each location exactly once.
In this problem, the locations can be represented as nodes in a graph, and the connections between them (representing valid paths) are the edges. The graph would be undirected because the delivery person can travel in both directions along a road.
Assumptions
The Traveling Salesman Problem (TSP) is typically formulated with several assumptions:
Complete Graph: This implies that the problem is defined on a complete graph, where every city is directly reachable from every other city.
Symmetric Distances: The distance or cost of traveling between two cities is assumed to be the same regardless of the direction. This means that the distance from City A to City B is the same as the distance from City B to City A.
Triangle Inequality: In the context of TSP, this means that the direct path between any two cities must be shorter than or equal to the sum of the direct paths through any other city. In other words, AB <= AC + CB.
Non-Negative Distances: Distances or costs between cities are non-negative. This is a practical assumption, as distances cannot be negative in real-world scenarios.
Single Salesman: There is only one salesman who is responsible for visiting all the cities.
graph LR
A ---|20| B
A ---|42| C
A ---|25| D
B ---|30| C
B ---|34| D
C ---|10| D
Brute Force Approach
What Needs to Be Found Out
The objective is to find a Hamiltonian cycle with minimum weight in this context. This would be the optimal route for the delivery person to visit all locations exactly once and return to the starting point.
Hamiltonian Cycle vs TSP
In this case, Hamiltonian tour exists (because the graph is complete) and in fact, many such tours exist, the problem is to find the minimum weight Hamiltonian cycle.
The Path A - B - C - D - A is a hamiltonian cycle. There exists many such cycles. Since this is a complete graph, there are n! possibilities.
Starting from Node A:
Row
Path
Weight
1
A -> B -> C -> D -> A
85
2
A -> B -> D -> C -> A
106
3
A -> C -> B -> D -> A
131
4
A -> C -> D -> B -> A
106
5
A -> D -> B -> C -> A
131
6
A -> D -> C -> B -> A
85
Starting from Node B:
Row
Path
Weight
7
B -> A -> C -> D -> B
106
8
B -> A -> D -> C -> B
85
9
B -> C -> A -> D -> B
131
10
B -> C -> D -> A -> B
85
11
B -> D -> A -> C -> B
131
12
B -> D -> C -> A -> B
106
Starting from Node C:
Row
Path
Weight
13
C -> A -> B -> D -> C
106
14
C -> A -> D -> B -> C
131
15
C -> B -> A -> D -> C
85
16
C -> B -> D -> A -> C
131
17
C -> D -> A -> B -> C
85
18
C -> D -> B -> A -> C
106
Starting from Node D:
Row
Path
Weight
19
D -> A -> B -> C -> D
85
20
D -> A -> C -> B -> D
131
21
D -> B -> A -> C -> D
106
22
D -> B -> C -> A -> D
131
23
D -> C -> A -> B -> D
106
24
D -> C -> B -> A -> D
85
If we know the starting node, the total number of Hamiltonian paths in a complete graph with n nodes can be calculated as: (n−1) × (n−2) × (n−3) × … × 1, which is equivalent to (n−1)!. Note that the weight of a Hamiltonian cycle is equal to that of its reverse due to symmetrical distances. So, the complexity of this approach is (n-1)! / 2
publicclassTSP{// Recursive function to find the minimum cost of a Hamiltonian cyclepublicstaticinttsp(int[][]graph,boolean[]visited,intcurrent,intn,intcount){// Base case: If all nodes have been visited if(count==n&&graph[current][0]>0){// Return the total cost of the path from current node to starting nodereturngraph[current][0];}visited[current]=true;// Mark the node as visitedintminCost=Integer.MAX_VALUE;// Initialize minimum cost// Try visiting each unvisited node from the current nodefor(inti=0;i<n;i++){if(!visited[i]){intnewCost=graph[current][i]+tsp(graph,visited,i,n,count+1);// Update minimum cost if a shorter path is foundminCost=Math.min(minCost,newCost);}}// Backtrack: Mark the node as unvisitedvisited[current]=false;returnminCost;// Return the minimum cost}// Main function to solve TSPpublicstaticvoidsolveTSP(int[][]graph){intn=graph.length;// Get the number of nodes in the graphboolean[]visited=newboolean[n];// Array to keep track of visited nodes// Initialize all nodes as unvisited// Arrays.fill(visited, false); // Call the recursive function to find minimum costintminCost=tsp(graph,visited,0,n,1);// Print the resultsSystem.out.println("Minimum Cost: "+minCost);}publicstaticvoidmain(String[]args){int[][]graph={{0,20,42,25},{20,0,30,34},{42,30,0,10},{25,34,10,0}};solveTSP(graph);// Call the function to solve TSP}}
deftsp(graph,visited,current,n,count):# Base case: If all nodes have been visited and there is a valid edge back to the starting nodeifcount==nandgraph[current][0]>0:returngraph[current][0]# Return the total cost of the path from current node to starting nodevisited[current]=True# Mark the current node as visitedmin_cost=float('inf')# Initialize minimum cost to a very large value# Try visiting each unvisited node from the current nodeforiinrange(n):ifnotvisited[i]:new_cost=graph[current][i]+tsp(graph,visited,i,n,count+1)# Update minimum cost if a shorter path is foundmin_cost=min(min_cost,new_cost)visited[current]=False# Backtrack: Mark the current node as unvisitedreturnmin_cost# Return the minimum costdefsolve_tsp(graph):n=len(graph)# Get the number of nodes in the graphvisited=[False]*n# Initialize an array to keep track of visited nodes# Call the recursive function to find minimum costmin_cost=tsp(graph,visited,0,n,1)# Print the minimum costprint(f"Minimum Cost: {min_cost}")# Define the graph as an adjacency matrixgraph=[[0,20,42,25],[20,0,30,34],[42,30,0,10],[25,34,10,0]]# Call the function to solve TSPsolve_tsp(graph)
packagemainimport("fmt""math")// Function to find the minimum cost of a Hamiltonian cycle using recursion
functsp(graph[][]int,visited[]bool,current,n,countint)int{// Base case: If all nodes have been visited and there is a valid edge back to the starting node
ifcount==n&&graph[current][0]>0{returngraph[current][0]// Return the total cost of the path from current node to starting node
}visited[current]=true// Mark the current node as visited
minCost:=math.MaxInt64// Initialize minimum cost to a very large value
// Try visiting each unvisited node from the current node
fori:=0;i<n;i++{if!visited[i]&&graph[current][i]>0{newCost:=graph[current][i]+tsp(graph,visited,i,n,count+1)// Update minimum cost if a shorter path is found
ifnewCost<minCost{minCost=newCost}}}visited[current]=false// Backtrack: Mark the current node as unvisited
returnminCost// Return the minimum cost
}// Function to solve the TSP
funcsolveTSP(graph[][]int){n:=len(graph)// Get the number of nodes in the graph
visited:=make([]bool,n)// Initialize an array to keep track of visited nodes
// Call the recursive function to find minimum cost
minCost:=tsp(graph,visited,0,n,1)// Print the minimum cost
fmt.Printf("Minimum Cost: %d\n",minCost)}funcmain(){graph:=[][]int{{0,20,42,25},{20,0,30,34},{42,30,0,10},{25,34,10,0},}// Call the function to solve TSP
solveTSP(graph)}
Complexity Analysis
Time Complexity
The time complexity of this approach is O(n^2 * 2^n). Here, n is the number of nodes in the graph.
Space Complexity
The space complexity is O(n * 2^n). This is due to the space required for the recursion stack (which can have at most n levels deep), and the additional space used for the visited array which keeps track of visited nodes.
Dynamic Programming
From the above implementation it is clear that we are depending on two variables i.e., visited and current. Using a straightforward approach, we might need a 2D array to store the state of all possible subsets of nodes. This can be extremely memory-intensive and impractical for large graphs.
// visited it self is an arraydp[visited][current]
Bit masking is used to efficiently represent and track the state of visited nodes in a more memory-efficient manner. By using bit masking, we can represent the set of visited nodes as a binary number. Each bit corresponds to a node, where 1 indicates the node has been visited and 0 indicates it hasn’t. This representation significantly reduces the memory footprint compared to a 2D array.
Bit Masking
Bit Masking is a technique used in programming where we manipulate individual bits of a number. It involves performing bitwise operations (like AND, OR, XOR) on binary representations of numbers.
In the context of the Traveling Salesman Problem (TSP), we can use bit masking to represent the visited nodes. Instead of using an array of booleans to keep track of visited nodes, we can use an integer where each bit represents the visited status of a node.
For example, if we have 4 nodes, we can represent the visited status as follows:
Visited Array
Binary Representation
Bitmask Num
Comment
[0, 0, 0, 0]
0000
0
All nodes unvisited
[0, 0, 0, 1]
0001
1
Only node A is visited
[0, 0, 1, 0]
0010
2
Only node B is visted
[0, 0, 1, 1]
0011
3
Nodes A and B are visited
[1, 1, 1, 1]
1111
15
All nodes are visited
Using bit masking in TSP reduces the space complexity, especially when dealing with a large number of nodes.
Implementation
publicclassTSPBitMasking{// Recursive function to find the minimum cost of a Hamiltonian cycle using bit maskingpublicstaticinttsp(int[][]graph,intvisited,intcurrent,intn,intcount){// Base case: If all nodes have been visited and there is a valid edge back to the starting nodeif(count==n&&(visited&1)==1){returngraph[current][0];// Return the total cost of the path from current node to starting node}intminCost=Integer.MAX_VALUE;for(inti=0;i<n;i++){// Check if node 'i' has not been visited and there exists an edge from 'current' to 'i'if((visited&(1<<i))==0&&graph[current][i]!=0){intnewMask=visited|(1<<i);// Update the visited nodes using bitwise ORintnewCost=graph[current][i]+tsp(graph,newMask,i,n,count+1);minCost=Math.min(minCost,newCost);// Update minimum cost if a shorter path is found}}returnminCost;}// Function to solve the TSP using bit maskingpublicstaticvoidsolveTSP(int[][]graph){intn=graph.length;// Get the number of nodes in the graphintmask=1;// Initialize the bitmask with the starting node (node 0)intminCost=tsp(graph,mask,0,n,1);// Call the recursive function to find minimum costSystem.out.println("Minimum Cost: "+minCost);// Print the minimum cost}publicstaticvoidmain(String[]args){int[][]graph={{0,20,42,25},{20,0,30,34},{42,30,0,10},{25,34,10,0}};solveTSP(graph);// Call the function to solve TSP}}
deftsp(graph,visited,current,n,count):# Base case: If all nodes have been visited and there is a valid edge back to the starting nodeifcount==nandvisited&1:returngraph[current][0]# Return the total cost of the path from current node to starting nodemin_cost=float('inf')foriinrange(n):# Check if node 'i' has not been visited and there exists an edge from 'current' to 'i'ifnot(visited&(1<<i))andgraph[current][i]!=0:new_mask=visited|(1<<i)# Update the visited nodes using bitwise ORnew_cost=graph[current][i]+tsp(graph,new_mask,i,n,count+1)min_cost=min(min_cost,new_cost)# Update minimum cost if a shorter path is foundreturnmin_costdefsolve_tsp(graph):n=len(graph)# Get the number of nodes in the graphmask=1# Initialize the bitmask with the starting node (node 0)min_cost=tsp(graph,mask,0,n,1)# Call the recursive function to find minimum costprint(f"Minimum Cost: {min_cost}")# Print the minimum costif__name__=="__main__":graph=[[0,20,42,25],[20,0,30,34],[42,30,0,10],[25,34,10,0]]solve_tsp(graph)# Call the function to solve TSP
packagemainimport"fmt"// Function to find the minimum cost of a Hamiltonian cycle using bit masking
functsp(graph[][]int,visitedint,current,n,countint)int{ifcount==n&&visited&1==1{returngraph[current][0]}minCost:=int(^uint(0)>>1)// Initialize minimum cost to maximum possible value
// Try visiting each unvisited node from the current node
fori:=0;i<n;i++{ifvisited&(1<<i)==0&&graph[current][i]!=0{newMask:=visited|(1<<i)// Update the visited nodes using bitwise OR
newCost:=graph[current][i]+tsp(graph,newMask,i,n,count+1)// Calculate new cost
ifnewCost<minCost{minCost=newCost// Update minimum cost if a shorter path is found
}}}returnminCost}// Function to solve the TSP using bit masking
funcsolveTSP(graph[][]int){n:=len(graph)// Get the number of nodes in the graph
mask:=1// Initialize the bitmask with the starting node (node 0)
minCost:=tsp(graph,mask,0,n,1)// Call the recursive function to find minimum cost
fmt.Printf("Minimum Cost: %d\n",minCost)// Print the minimum cost
}funcmain(){graph:=[][]int{{0,20,42,25},{20,0,30,34},{42,30,0,10},{25,34,10,0},}solveTSP(graph)// Call the function to solve TSP
}
Complexity Analysis
This implementation has a similar time complexity of O(n^2 * 2^n), but the space complexity is improved to O(n * 2^n) due to the reduced space required for the bitmask.