Introduction
.
The Traveling Salesman Problem (TSP) is a classic problem in computer science and mathematics. The goal is to find the shortest possible route that allows a salesman to visit each city exactly once and return to the starting city.
It’s a problem that can be solved using different methods, and one effective approach is the Christofides Algorithm.
The TSP can be solved using various methods
- Brute Force – Check all possible routes and choose the shortest one. This method guarantees the optimal solution but is impractical for large datasets due to its high computational cost.
- Greedy Algorithms – Heuristics that make local optimal choices at each step to find a global optimum. It is also called Nearest Neighbour Problem.
- Dynamic Programming – Optimizes the problem by solving subproblems and combining their solutions. It is also called Held-Karp algorithm.
- Branch & Bound Approach – breaking the problem into smaller subproblems (branching) and calculating a lower bound for each subproblem. If the lower bound exceeds the current best solution, that branch is pruned (ignored).
- Approximation Algorithms – Provides near-optimal solutions in a reasonable time, useful for large instances. It is also called Christofides Algorithm.
We first solve the Travelling Salesman Problem by Greedy algorithm, then solve by Dynamic Programming and now we are solving it by approximation algorithm and the problem name is Christofides Algorithm. It provides an approximation solution guaranteeing that the solution is at most 1.5 times the optimal solution.
.
Problem Statement
Given a set of n cities and the distances between every pair of cities, the objective is to find a near-optimal tour using the Christofides algorithm. The tour should start from one city, visit each city exactly once, and return to the starting city, such that the total travel cost is as small as possible.
- The number of cities (n ≥ 3).
- A distance matrix dist[][] where dist[i][j] represents the distance between city i and city j.
- The total cost of the tour, which will be at most 1.5 times the optimal solution.
.
History of the Christofides Algorithm
The Christofides algorithm was introduced by Nicos Christofides, a British-Cypriot mathematician, in 1976. The algorithm is designed to provide an approximate solution to the Traveling Salesman Problem (TSP), specifically for the metric TSP, where the triangle inequality holds (i.e., the direct path between two cities is never longer than going through an intermediate city).
Christofides developed a 3-step approximation algorithm that guarantees a solution within 1.5 times the optimal cost for metric TSP. This was a significant improvement over previous algorithms, as it combined graph theory concepts like Minimum Spanning Tree (MST) and Eulerian circuits with perfect matching to achieve this approximation ratio.
Even today, no algorithm has been found that consistently beats the 1.5 approximation factor for the metric TSP. Christofides work continues to influence modern algorithmic research in graph theory and combinatorial optimization.
.
Christofides Algorithm
- Start by constructing an MST from the graph that connects all cities with the minimum possible total edge weight. An MST ensures that all cities are connected but doesn’t ensure a tour.
- In the MST, find all vertices with an odd degree (an odd number of edges). According to graph theory, an MST will always have an even number of odd-degree vertices.
- Perform a minimum weight perfect matching on the odd-degree vertices. In a perfect matching, each odd-degree vertex is connected to another odd-degree vertex using the least possible edge weight from the original graph.
- Combine the edges of the MST and the matching to form a multigraph. This graph now has a Eulerian circuit, where each vertex has an even degree.
- Find a Eulerian circuit in the combined graph. A Eulerian circuit is a cycle where every edge is visited exactly once.
- Convert the Eulerian circuit into a Hamiltonian cycle (a cycle where each vertex is visited exactly once) by skipping repeated vertices.
.
Example
Let’s walk through Christofides algorithm step by step using the provided distance matrix between six cities – A, B, C, D, E, and F. In the graph, triangle inequality holds (i.e., the direct path between two cities is never longer than going through an intermediate city).
.
Step 1 – We first need to find the Minimum Spanning Tree of the graph. This tree must connect all cities with the minimum total edge weight.
We will use Kruskal’s algorithm to find the MST.
.
So, we have found an MST and its path are A -> F -> B -> E -> C -> D
Total Cost is 65.
.
Step 2 – Find Odd-Degree Vertices (Vertices with an odd number of edges).
Next, we identify the vertices in the MST with an odd degree.
- City A has degree 1 (odd).
- City D has degree 1 (odd).
.
Step 3 – Perform a minimum weight perfect matching on the odd-degree vertices.
There is only one edge between city A to city D and that cost is 30. We can’t have B, C, E and F in between A and D, because they are even degree vertices. Each odd-degree vertex is connected to another odd-degree vertex using the least possible edge weight from the original graph.
City A -> City D and its Cost = 30.
.
Step 4 – Combine the edges of the MST and the minimum weight perfect matching to form a multigraph.
.
Step 5 – A Eulerian circuit is a path that visits every edge exactly once. Since this graph has all even-degree vertices, we can find such a circuit.
A -> F -> B -> E -> C -> D -> A
.
Step 6 – Finally, we convert the Eulerian circuit to a Hamiltonian tour by visiting each city only once (except the starting city).
A -> F -> B -> E -> C -> D -> A
The Hamiltonian tour is A → F → B → D → E → C → A with a total cost of 95.
This is under 1.5-approximation of the optimal solution using the Christofides algorithm.
.
Source Code of Christofides Algorithm
// COMPUTER GEEK – compgeek.co.in
// Write a program for Christofides Algorithm
Â
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
Â
#define INF 999999
Â
// Structure to represent an edge in the graph
struct Edge {
   int u, v, weight;
};
Â
// Structure to represent a subset for Union-Find
struct Subset {
   int parent;
   int rank;
};
Â
// Function to find the parent of a node in Union-Find
int find(struct Subset subsets[], int i) {
   if (subsets[i].parent != i)
       subsets[i].parent = find(subsets, subsets[i].parent);
   return subsets[i].parent;
}
Â
// Function to perform union of two sets in Union-Find
void Union(struct Subset subsets[], int x, int y) {
   int rootX = find(subsets, x);
   int rootY = find(subsets, y);
  Â
   if (subsets[rootX].rank < subsets[rootY].rank)
       subsets[rootX].parent = rootY;
   else if (subsets[rootX].rank > subsets[rootY].rank)
       subsets[rootY].parent = rootX;
   else {
       subsets[rootY].parent = rootX;
       subsets[rootX].rank++;
   }
}
Â
// Function to compare two edges based on their weight for sorting
int compareEdges(const void *a, const void *b) {
   struct Edge *edgeA = (struct Edge *)a;
   struct Edge *edgeB = (struct Edge *)b;
   return edgeA->weight – edgeB->weight;
}
Â
// Kruskal’s algorithm to find the Minimum Spanning Tree (MST)
void kruskalMST(int n, int edgesCount, struct Edge edges[], int mst[][n]) {
   qsort(edges, edgesCount, sizeof(edges[0]), compareEdges);
  Â
   struct Subset *subsets = (struct Subset *)malloc(n * sizeof(struct Subset));
   for (int v = 0; v < n; ++v) {
       subsets[v].parent = v;
       subsets[v].rank = 0;
   }
Â
   int edgesInMST = 0, i = 0;
   while (edgesInMST < n – 1 && i < edgesCount) {
       struct Edge nextEdge = edges[i++];
       int x = find(subsets, nextEdge.u);
       int y = find(subsets, nextEdge.v);
Â
       if (x != y) {
           mst[nextEdge.u][nextEdge.v] = nextEdge.weight;
           mst[nextEdge.v][nextEdge.u] = nextEdge.weight;
           Union(subsets, x, y);
           edgesInMST++;
       }
   }
Â
   free(subsets);
}
Â
// Function to find odd-degree vertices from the MST
void findOddDegreeVertices(int n, int mst[][n], int oddDegreeVertices[], int *oddCount) {
   *oddCount = 0;
   for (int i = 0; i < n; i++) {
       int degree = 0;
       for (int j = 0; j < n; j++) {
           if (mst[i][j] != 0)
               degree++;
       }
       if (degree % 2 == 1) {
           oddDegreeVertices[*oddCount] = i;
           (*oddCount)++;
       }
   }
}
Â
// Function to generate a Minimum Perfect Matching (using greedy approach)
void minPerfectMatching(int n, int oddDegreeVertices[], int oddCount, int dist[][n], int mst[][n]) {
   bool matched[oddCount];
   for (int i = 0; i < oddCount; i++)
       matched[i] = false;
Â
   for (int i = 0; i < oddCount; i++) {
       if (!matched[i]) {
           int minDist = INF, minIndex = -1;
           for (int j = i + 1; j < oddCount; j++) {
               if (!matched[j] && dist[oddDegreeVertices[i]][oddDegreeVertices[j]] < minDist) {
                   minDist = dist[oddDegreeVertices[i]][oddDegreeVertices[j]];
                   minIndex = j;
               }
           }
           mst[oddDegreeVertices[i]][oddDegreeVertices[minIndex]] = dist[oddDegreeVertices[i]][oddDegreeVertices[minIndex]];
           mst[oddDegreeVertices[minIndex]][oddDegreeVertices[i]] = dist[oddDegreeVertices[minIndex]][oddDegreeVertices[i]];
           matched[i] = true;
           matched[minIndex] = true;
       }
   }
}
Â
// Function to perform DFS to find Eulerian Tour
void dfs(int v, int n, int mst[][n], bool visited[], int path[], int *pathIndex) {
   visited[v] = true;
   path[(*pathIndex)++] = v;
   for (int i = 0; i < n; i++) {
       if (mst[v][i] != 0 && !visited[i]) {
           dfs(i, n, mst, visited, path, pathIndex);
       }
   }
}
Â
// Function to calculate the Hamiltonian Tour and its cost
int calculateHamiltonianTour(int n, int dist[][n], int path[], int pathLength) {
   bool visited[n];
   for (int i = 0; i < n; i++)
       visited[i] = false;
Â
   int totalCost = 0;
   int tour[n], tourIndex = 0;
Â
   for (int i = 0; i < pathLength; i++) {
       if (!visited[path[i]]) {
           tour[tourIndex++] = path[i];
           visited[path[i]] = true;
       }
   }
Â
   tour[tourIndex] = tour[0]; // Complete the tour by returning to the start
Â
   printf(“Hamiltonian Tour: “);
   for (int i = 0; i <= tourIndex; i++)
       printf(“%d “, tour[i] + 1); // Printing 1-based city index
   printf(“\n”);
Â
   // Calculate the total cost of the Hamiltonian tour
   for (int i = 0; i < tourIndex; i++) {
       totalCost += dist[tour[i]][tour[i + 1]];
   }
Â
   return totalCost;
}
Â
int main() {
   int n;
   printf(“Enter the number of cities: “);
   scanf(“%d”, &n);
Â
   int dist[n][n];
   printf(“Enter the distance matrix:\n”);
   for (int i = 0; i < n; i++) {
       for (int j = 0; j < n; j++) {
           scanf(“%d”, &dist[i][j]);
       }
   }
Â
   int edgesCount = 0;
   struct Edge edges[n * (n – 1) / 2];
Â
   // Create an edge list from the distance matrix
   for (int i = 0; i < n; i++) {
       for (int j = i + 1; j < n; j++) {
           edges[edgesCount].u = i;
           edges[edgesCount].v = j;
           edges[edgesCount].weight = dist[i][j];
           edgesCount++;
       }
   }
Â
   // Kruskal’s algorithm to generate the Minimum Spanning Tree
   int mst[n][n];
   for (int i = 0; i < n; i++)
       for (int j = 0; j < n; j++)
           mst[i][j] = 0;
Â
   kruskalMST(n, edgesCount, edges, mst);
Â
   // Find odd-degree vertices
   int oddDegreeVertices[n], oddCount;
   findOddDegreeVertices(n, mst, oddDegreeVertices, &oddCount);
Â
   // Generate minimum perfect matching
   minPerfectMatching(n, oddDegreeVertices, oddCount, dist, mst);
Â
   // Find Eulerian Tour using DFS
   bool visited[n];
   for (int i = 0; i < n; i++)
       visited[i] = false;
Â
   int path[2 * n], pathIndex = 0;
   dfs(0, n, mst, visited, path, &pathIndex);
Â
   // Calculate the Hamiltonian Tour and its total cost
   int totalCost = calculateHamiltonianTour(n, dist, path, pathIndex);
   printf(“Total Cost of the Hamiltonian Tour: %d\n”, totalCost);
Â
   return 0;
}
// COMPUTER GEEK – compgeek.co.in
// Write a program for Christofides Algorithm
Â
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
Â
struct Edge {
   int u, v, weight;
};
Â
class Subset {
public:
   int parent;
   int rank;
};
Â
int find(Subset subsets[], int i) {
   if (subsets[i].parent != i)
       subsets[i].parent = find(subsets, subsets[i].parent);
   return subsets[i].parent;
}
Â
void Union(Subset subsets[], int x, int y) {
   int rootX = find(subsets, x);
   int rootY = find(subsets, y);
Â
   if (subsets[rootX].rank < subsets[rootY].rank)
       subsets[rootX].parent = rootY;
   else if (subsets[rootX].rank > subsets[rootY].rank)
       subsets[rootY].parent = rootX;
   else {
       subsets[rootY].parent = rootX;
       subsets[rootX].rank++;
   }
}
Â
bool compareEdges(Edge a, Edge b) {
   return a.weight < b.weight;
}
Â
void kruskalMST(int n, int edgesCount, Edge edges[], vector<vector<int>>& mst) {
   sort(edges, edges + edgesCount, compareEdges);
   Subset *subsets = new Subset[n];
   for (int v = 0; v < n; ++v) {
       subsets[v].parent = v;
       subsets[v].rank = 0;
   }
Â
   int edgesInMST = 0, i = 0;
   while (edgesInMST < n – 1 && i < edgesCount) {
       Edge nextEdge = edges[i++];
       int x = find(subsets, nextEdge.u);
       int y = find(subsets, nextEdge.v);
Â
       if (x != y) {
           mst[nextEdge.u][nextEdge.v] = nextEdge.weight;
           mst[nextEdge.v][nextEdge.u] = nextEdge.weight;
           Union(subsets, x, y);
           edgesInMST++;
       }
   }
Â
   delete[] subsets;
}
Â
void findOddDegreeVertices(int n, vector<vector<int>>& mst, vector<int>& oddDegreeVertices) {
   for (int i = 0; i < n; i++) {
       int degree = 0;
       for (int j = 0; j < n; j++) {
           if (mst[i][j] != 0)
               degree++;
       }
       if (degree % 2 == 1) {
           oddDegreeVertices.push_back(i);
       }
   }
}
Â
void minPerfectMatching(int n, vector<int>& oddDegreeVertices, int dist[][100], vector<vector<int>>& mst) {
   vector<bool> matched(oddDegreeVertices.size(), false);
Â
   for (size_t i = 0; i < oddDegreeVertices.size(); i++) {
       if (!matched[i]) {
           int minDist = INT_MAX, minIndex = -1;
           for (size_t j = i + 1; j < oddDegreeVertices.size(); j++) {
               if (!matched[j] && dist[oddDegreeVertices[i]][oddDegreeVertices[j]] < minDist) {
                   minDist = dist[oddDegreeVertices[i]][oddDegreeVertices[j]];
                   minIndex = j;
               }
           }
           mst[oddDegreeVertices[i]][oddDegreeVertices[minIndex]] = dist[oddDegreeVertices[i]][oddDegreeVertices[minIndex]];
           mst[oddDegreeVertices[minIndex]][oddDegreeVertices[i]] = dist[oddDegreeVertices[minIndex]][oddDegreeVertices[i]];
           matched[i] = true;
           matched[minIndex] = true;
       }
   }
}
Â
void dfs(int v, int n, vector<vector<int>>& mst, vector<bool>& visited, vector<int>& path) {
   visited[v] = true;
   path.push_back(v);
   for (int i = 0; i < n; i++) {
       if (mst[v][i] != 0 && !visited[i]) {
           dfs(i, n, mst, visited, path);
       }
   }
}
Â
int calculateHamiltonianTour(int n, int dist[][100], vector<int>& path) {
   vector<bool> visited(n, false);
   vector<int> tour;
   int totalCost = 0;
Â
   for (int i : path) {
       if (!visited[i]) {
           tour.push_back(i);
           visited[i] = true;
       }
   }
Â
   tour.push_back(tour[0]); // Complete the tour by returning to the start
Â
   cout << “Hamiltonian Tour: “;
   for (int city : tour)
       cout << city + 1 << ” “; // Print 1-based city index
   cout << endl;
Â
   // Calculate the total cost of the Hamiltonian tour
   for (size_t i = 0; i < tour.size() – 1; i++) {
       totalCost += dist[tour[i]][tour[i + 1]];
   }
Â
   return totalCost;
}
Â
int main() {
   int n;
   cout << “Enter the number of cities: “;
   cin >> n;
Â
   int dist[100][100];
   cout << “Enter the distance matrix:” << endl;
   for (int i = 0; i < n; i++) {
       for (int j = 0; j < n; j++) {
           cin >> dist[i][j];
       }
   }
Â
   int edgesCount = 0;
   Edge edges[n * (n – 1) / 2];
Â
   // Create an edge list from the distance matrix
   for (int i = 0; i < n; i++) {
       for (int j = i + 1; j < n; j++) {
           edges[edgesCount].u = i;
           edges[edgesCount].v = j;
           edges[edgesCount].weight = dist[i][j];
           edgesCount++;
       }
   }
Â
   // Kruskal’s algorithm to generate the Minimum Spanning Tree
   vector<vector<int>> mst(n, vector<int>(n, 0));
   kruskalMST(n, edgesCount, edges, mst);
Â
   // Find odd-degree vertices
   vector<int> oddDegreeVertices;
   findOddDegreeVertices(n, mst, oddDegreeVertices);
Â
   // Generate minimum perfect matching
   minPerfectMatching(n, oddDegreeVertices, dist, mst);
Â
   // Find Eulerian Tour using DFS
   vector<bool> visited(n, false);
   vector<int> path;
   dfs(0, n, mst, visited, path);
Â
   // Calculate the Hamiltonian Tour and its total cost
   int totalCost = calculateHamiltonianTour(n, dist, path);
   cout << “Total Cost of the Hamiltonian Tour: ” << totalCost << endl;
Â
   return 0;
}
// COMPUTER GEEK – compgeek.co.in
// Write a program for Christofides Algorithm
Â
import java.util.*;
Â
public class Christofides {
   static final int INF = Integer.MAX_VALUE;
Â
   static class Edge {
       int u, v, weight;
Â
       Edge(int u, int v, int weight) {
           this.u = u;
           this.v = v;
           this.weight = weight;
       }
   }
Â
   static int find(int[] parent, int i) {
       if (parent[i] != i) {
           parent[i] = find(parent, parent[i]);
       }
       return parent[i];
   }
Â
   static void union(int[] parent, int[] rank, int x, int y) {
       int rootX = find(parent, x);
       int rootY = find(parent, y);
       if (rank[rootX] < rank[rootY]) {
           parent[rootX] = rootY;
       } else if (rank[rootX] > rank[rootY]) {
           parent[rootY] = rootX;
       } else {
           parent[rootY] = rootX;
           rank[rootX]++;
       }
   }
Â
   static void kruskalMST(int n, List<Edge> edges, int[][] mst) {
       edges.sort(Comparator.comparingInt(e -> e.weight));
       int[] parent = new int[n];
       int[] rank = new int[n];
Â
       for (int i = 0; i < n; i++) {
           parent[i] = i;
           rank[i] = 0;
       }
Â
       int edgesInMST = 0;
       for (Edge edge : edges) {
           if (edgesInMST >= n – 1) break;
           int x = find(parent, edge.u);
           int y = find(parent, edge.v);
           if (x != y) {
               mst[edge.u][edge.v] = edge.weight;
               mst[edge.v][edge.u] = edge.weight;
               union(parent, rank, x, y);
               edgesInMST++;
           }
       }
   }
Â
   static void findOddDegreeVertices(int n, int[][] mst, List<Integer> oddVertices) {
       for (int i = 0; i < n; i++) {
           int degree = 0;
           for (int j = 0; j < n; j++) {
               if (mst[i][j] != 0) degree++;
           }
           if (degree % 2 == 1) oddVertices.add(i);
       }
   }
Â
   static void minPerfectMatching(int n, List<Integer> oddVertices, int[][] dist, int[][] mst) {
       boolean[] matched = new boolean[oddVertices.size()];
       for (int i = 0; i < oddVertices.size(); i++) {
           if (!matched[i]) {
               int minDist = INF, minIndex = -1;
               for (int j = i + 1; j < oddVertices.size(); j++) {
                   if (!matched[j] && dist[oddVertices.get(i)][oddVertices.get(j)] < minDist) {
                       minDist = dist[oddVertices.get(i)][oddVertices.get(j)];
                       minIndex = j;
                   }
               }
               mst[oddVertices.get(i)][oddVertices.get(minIndex)] = dist[oddVertices.get(i)][oddVertices.get(minIndex)];
               mst[oddVertices.get(minIndex)][oddVertices.get(i)] = dist[oddVertices.get(minIndex)][oddVertices.get(i)];
               matched[i] = true;
               matched[minIndex] = true;
           }
       }
   }
Â
   static void dfs(int v, int n, int[][] mst, boolean[] visited, List<Integer> path) {
       visited[v] = true;
       path.add(v);
       for (int i = 0; i < n; i++) {
           if (mst[v][i] != 0 && !visited[i]) {
               dfs(i, n, mst, visited, path);
           }
       }
   }
Â
   static int calculateHamiltonianTour(int n, int[][] dist, List<Integer> path) {
       boolean[] visited = new boolean[n];
       int totalCost = 0;
       List<Integer> tour = new ArrayList<>();
Â
       for (int city : path) {
           if (!visited[city]) {
               tour.add(city);
               visited[city] = true;
           }
       }
       tour.add(tour.get(0)); // Complete the tour
Â
       System.out.print(“Hamiltonian Tour: “);
       for (int city : tour) {
           System.out.print((city + 1) + ” “); // 1-based index
       }
       System.out.println();
Â
       for (int i = 0; i < tour.size() – 1; i++) {
           totalCost += dist[tour.get(i)][tour.get(i + 1)];
       }
Â
       return totalCost;
   }
Â
   public static void main(String[] args) {
       Scanner scanner = new Scanner(System.in);
       System.out.print(“Enter the number of cities: “);
       int n = scanner.nextInt();
Â
       int[][] dist = new int[n][n];
       System.out.println(“Enter the distance matrix:”);
       for (int i = 0; i < n; i++) {
           for (int j = 0; j < n; j++) {
               dist[i][j] = scanner.nextInt();
           }
       }
Â
       List<Edge> edges = new ArrayList<>();
       for (int i = 0; i < n; i++) {
           for (int j = i + 1; j < n; j++) {
               edges.add(new Edge(i, j, dist[i][j]));
           }
       }
Â
       int[][] mst = new int[n][n];
       kruskalMST(n, edges, mst);
Â
       List<Integer> oddVertices = new ArrayList<>();
       findOddDegreeVertices(n, mst, oddVertices);
Â
       minPerfectMatching(n, oddVertices, dist, mst);
Â
       boolean[] visited = new boolean[n];
       List<Integer> path = new ArrayList<>();
       dfs(0, n, mst, visited, path);
Â
       int totalCost = calculateHamiltonianTour(n, dist, path);
       System.out.println(“Total Cost of the Hamiltonian Tour: ” + totalCost);
   }
}
# COMPUTER GEEK – compgeek.co.in
# Write a program for Christofides Algorithm
Â
# Helper function to find the root of a vertex (with path compression)
def find(parent, i):
   if parent[i] == i:
       return i
   else:
       parent[i] = find(parent, parent[i])
       return parent[i]
Â
# Helper function to union two subsets
def union(parent, rank, x, y):
   root_x = find(parent, x)
   root_y = find(parent, y)
Â
   if rank[root_x] < rank[root_y]:
       parent[root_x] = root_y
   elif rank[root_x] > rank[root_y]:
       parent[root_y] = root_x
   else:
       parent[root_y] = root_x
       rank[root_x] += 1
Â
# Helper function to find the Minimum Spanning Tree using Kruskal’s algorithm
def kruskal_mst(graph):
   num_vertices = len(graph)
   edges = []
Â
   # Create a list of all edges
   for i in range(num_vertices):
       for j in range(i + 1, num_vertices):
           if graph[i][j] != 0: # 0 means no edge
               edges.append((graph[i][j], i, j))
Â
   # Sort edges by weight
   edges.sort()
Â
   parent = list(range(num_vertices))
   rank = [0] * num_vertices
   mst_edges = []
Â
   # Kruskal’s algorithm to form the MST
   for weight, u, v in edges:
       root_u = find(parent, u)
       root_v = find(parent, v)
Â
       if root_u != root_v:
           mst_edges.append((u, v, weight))
           union(parent, rank, root_u, root_v)
Â
   return mst_edges
Â
# Helper function to find odd degree vertices
def find_odd_degree_vertices(mst_edges, num_vertices):
   degree = [0] * num_vertices
   for u, v, _ in mst_edges:
       degree[u] += 1
       degree[v] += 1
Â
   odd_vertices = [i for i in range(num_vertices) if degree[i] % 2 == 1]
   return odd_vertices
Â
# Helper function to perform minimum weight matching on odd vertices
def minimum_weight_matching(odd_vertices, graph):
   matched = []
   while odd_vertices:
       u = odd_vertices.pop()
       min_edge = float(‘inf’)
       min_v = -1
       for v in odd_vertices:
           if graph[u][v] < min_edge:
               min_edge = graph[u][v]
               min_v = v
       odd_vertices.remove(min_v)
       matched.append((u, min_v))
Â
   return matched
Â
# Helper function to perform Eulerian circuit
def eulerian_circuit(graph, mst_edges, matching_edges):
   num_vertices = len(graph)
   adj_list = [[] for _ in range(num_vertices)]
Â
   # Adding edges of MST
   for u, v, _ in mst_edges:
       adj_list[u].append(v)
       adj_list[v].append(u)
Â
   # Adding matching edges
   for u, v in matching_edges:
       adj_list[u].append(v)
       adj_list[v].append(u)
Â
   # Finding Eulerian circuit using Hierholzer’s Algorithm
   circuit = []
   stack = [0]
   current_path = []
   while stack:
       u = stack[-1]
       if adj_list[u]:
           v = adj_list[u].pop()
           adj_list[v].remove(u)
           stack.append(v)
       else:
           current_path.append(stack.pop())
Â
   return current_path
Â
# Helper function to create Hamiltonian path from Eulerian circuit
def make_hamiltonian_path(eulerian_path):
   visited = [False] * len(eulerian_path)
   hamiltonian_path = []
Â
   for node in eulerian_path:
       if not visited[node]:
           hamiltonian_path.append(node)
           visited[node] = True
Â
   return hamiltonian_path
Â
# Helper function to calculate the total cost of the Hamiltonian path
def calculate_total_cost(hamiltonian_path, graph):
   total_cost = 0
   for i in range(len(hamiltonian_path) – 1):
       u = hamiltonian_path[i]
       v = hamiltonian_path[i + 1]
       total_cost += graph[u][v]
   # Add the cost to return to the starting point to make it a cycle
   total_cost += graph[hamiltonian_path[-1]][hamiltonian_path[0]]
  Â
   return total_cost
Â
# Main function to perform Christofides algorithm
def christofides(graph):
   num_vertices = len(graph)
Â
   # Step 1: Find Minimum Spanning Tree (MST) using Kruskal’s algorithm
   mst_edges = kruskal_mst(graph)
Â
   # Step 2: Find vertices with odd degree
   odd_vertices = find_odd_degree_vertices(mst_edges, num_vertices)
Â
   # Step 3: Perform minimum weight matching on odd vertices
   matching_edges = minimum_weight_matching(odd_vertices, graph)
Â
   # Step 4: Find Eulerian circuit
   eulerian_path = eulerian_circuit(graph, mst_edges, matching_edges)
Â
   # Step 5: Create Hamiltonian circuit from Eulerian path
   hamiltonian_path = make_hamiltonian_path(eulerian_path)
Â
   return hamiltonian_path
Â
# Function to take input and call Christofides algorithm
def main():
   num_vertices = int(input(“Enter the number of vertices: “))
   graph = []
   print(“Enter the adjacency matrix (use 0 for no direct edge):”)
   for i in range(num_vertices):
       row = list(map(int, input().split()))
       graph.append(row)
Â
   # Run Christofides algorithm
   hamiltonian_path = christofides(graph)
Â
   # Calculate the total cost of the Hamiltonian path
   total_cost = calculate_total_cost(hamiltonian_path, graph)
Â
   print(“Approximate solution to TSP using Christofides Algorithm:”)
   print(“Path: ” + ” -> “.join(map(str, hamiltonian_path)))
   print(“Total cost:”, total_cost)
Â
Â
if __name__ == “__main__”:
   main()
Enter the number of cities: 6
Enter the distance matrix:
0 35 25 30 20 10
35 0 30 20 15 15
25 30 0 15 10 20
30 20 15 0 25 25
20 15 10 25 0 30
10 15 20 25 30 0
Hamiltonian Tour: 1 4 3 5 2 6 1
Total Cost of the Hamiltonian Tour: 95
Â
=== Code Execution Successful ===
Time Complexity of Christofides Algorithm
- Prims or Kruskal’s Algorithm (MST Calculation) takes O(E logV) where E is the number of edges and V are the number of vertices.
- Finding Odd Degree Vertices iterates through the edges of the MST and counts the degree of each vertex. It takes O(V) time.
- Minimum Weight Matching on Odd Degree Vertices takes O(V2) time.
- Constructing the Eulerian circuit from the MST and matching edges takes O(V+E).
- Constructing the Hamiltonian path from the Eulerian circuit is done in O(V).
Summing all the time complexities O(Elog V + V + V2 + V + E + V) will result O(Elog V + V2).
For a complete graph (which is typical in TSP), E=V(V−1)/2, in the worst case, this becomes O(V2logV).
.
Space Complexity of Christofides Algorithm
The adjacency matrix used to represent the graph takes O(V2) space, since we need to store weights for each pair of vertices.
.
Advantages of Christofides Algorithm
- Approximation Guarantee
- Christofides’ algorithm provides a 3/2-approximation for the Traveling Salesman Problem (TSP) in metric spaces, ensuring the solution is at most 1.5 times the optimal cost.
- Polynomial Time Complexity
- The algorithm runs in polynomial time, making it suitable for larger instances of TSP where exact solutions are computationally infeasible.
- Works for Metric TSP
- It is effective for metric TSPs, where distances satisfy the triangle inequality, applicable to many real-world routing and logistics problems.
- Combines Greedy and Matching Approaches
- By combining Kruskal’s greedy algorithm with minimum weight perfect matching, it efficiently forms an Eulerian circuit, balancing solution quality and computational efficiency.
- Practical for Real-World Applications
- Suitable for applications in vehicle routing, logistics, and network optimization where near-optimal solutions are desired.
- Easy to Implement
- The algorithm uses well-known techniques like MST and matching, making it relatively straightforward to code and understand.
- Edge Case Handling
- It effectively handles odd-degree vertices through matching techniques, ensuring the Eulerian circuit can be formed.
- Widely Used for Heuristics
- Many practical heuristics for TSP use Christofides’ algorithm as a foundation due to its balance between efficiency and accuracy.
.
Disadvantages of Christofides Algorithm
- Approximation Limitations
- While it guarantees a solution within 1.5 times the optimal cost, this may not be acceptable for applications requiring exact solutions.
- Assumption of Metric Space
- The algorithm is designed for metric spaces where distances satisfy the triangle inequality, limiting its applicability to certain types of problems.
- Complexity for Non-Metric Problems
- For TSP instances that do not conform to metric properties, the algorithm may not provide useful results or can be inefficient.
- Additional Computation Overhead
- The combination of finding a Minimum Spanning Tree and performing minimum weight matching can introduce additional computational overhead compared to simpler heuristics.
- Handling Large Graphs
- Although it is polynomial in time complexity, the algorithm may still struggle with very large graphs due to the inherent complexity of the MST and matching steps.
- Implementation Challenges
- While conceptually straightforward, the implementation of various components like matching can be complex and error-prone, especially for beginners.
.
Applications of Christofides Algorithm in Real Life
- Logistics and Supply Chain Management
- Used for optimizing delivery routes for trucks and vehicles, reducing transportation costs and improving efficiency in logistics operations.
- Vehicle Routing
- Helps in planning routes for garbage collection, public transportation, and delivery services to minimize travel distance and time.
- Telecommunications
- Applied in designing efficient network layouts and routing for data packets, ensuring minimal latency and reduced transmission costs.
- Manufacturing and Production
- Used in scheduling and routing tasks within manufacturing processes to optimize workflow and resource allocation.
- Tourism and Travel Planning
- Assists in creating optimal travel itineraries for tourists, ensuring that multiple destinations are visited in the shortest possible route.
- Robotics
- Utilized in path planning for autonomous robots, enabling them to navigate efficiently in environments with multiple points of interest.
- Computer Graphics
- Helps in rendering and traversing graphical data structures where optimal paths need to be computed, enhancing visual performance.
- Urban Planning
- Applied in the design of urban transport systems and public amenities to ensure efficient connectivity and accessibility across cities.
- Energy Distribution
- Used in optimizing routes for power line maintenance and monitoring, ensuring minimal travel distance for utility vehicles.
- Air Traffic Management
- Helps in optimizing flight paths and scheduling for airlines, reducing fuel consumption, and improving overall operational efficiency.
Test Yourself
Q1- Why is the Traveling Salesman Problem (TSP) considered NP-hard?
TSP is considered NP-hard because no polynomial-time algorithm is known that can solve it exactly for all possible inputs. The number of possible tours grows factorially with the number of cities, making the problem computationally intractable for large instances.
Q2- How does the Christofides algorithm guarantee an approximation of 1.5 times the optimal solution?
The Christofides algorithm constructs a Minimum Spanning Tree (MST), performs a minimum weight matching on odd-degree vertices, and then forms an Eulerian circuit, ensuring that the final Hamiltonian cycle costs no more than 1.5 times the optimal solution in metric TSP.
Q3- Explain the role of Minimum Spanning Tree (MST) in the Christofides algorithm.
The MST connects all cities with the minimum total edge weight without forming cycles. It is the first step in Christofides algorithm, which serves as a base for adding the extra edges needed to complete a tour.
Q4- Why does the Christofides algorithm only work for metric TSP problems?
Christofides relies on the triangle inequality, which holds in metric spaces, ensuring that the direct path between two cities is never longer than a detour through an intermediate city. This property is essential for the algorithm to achieve its 1.5 approximation guarantee.
Q5- What is the significance of odd-degree vertices in the Christofides algorithm?
In an MST, there is always an even number of vertices with an odd degree. These odd-degree vertices must be paired using a minimum weight matching to ensure that the final Eulerian circuit can be converted into a Hamiltonian cycle.
Q6- What is the role of Eulerian circuits in the Christofides algorithm?
After constructing an MST and performing minimum weight matching, an Eulerian circuit is created, where each vertex is visited an even number of times. This allows the algorithm to convert the circuit into a Hamiltonian cycle by skipping repeated vertices.
Q7- What are the key steps in converting a Eulerian circuit to a Hamiltonian cycle in the Christofides algorithm?
In a Eulerian circuit, some cities might be visited more than once. To convert this into a Hamiltonian cycle, the algorithm skips repeated cities, ensuring each is visited exactly once.
Q8- Why is the Christofides algorithm popular in logistics and transportation planning?
Christofides provides near-optimal solutions quickly, making it practical for real-world applications like vehicle routing and delivery services where exact solutions are often unnecessary but speed and cost-efficiency are critical.
Q9- What is the time complexity of the Christofides algorithm?
O(V²)
O(V² log V)
O(V log V)
O(V!)
Ans – (2)
Explanation – The Christofides algorithm involves constructing a Minimum Spanning Tree (O(E log V)) and a minimum weight matching (O(V²)), leading to a time complexity of O(V² log V).
Q10- Which of the following is NOT a method for solving TSP?
Brute Force
Greedy Algorithms
Dynamic Programming
Dijkstra’s Algorithm
Ans – (4)
Explanation – Dijkstra’s algorithm is used for finding the shortest path in a graph, not for solving the TSP.
Q11- The Christofides algorithm guarantees a solution within what factor of the optimal cost?
1.25
1.5
2
2.5
Ans – (2)
Explanation – Christofides guarantees that the solution will be within 1.5 times the optimal solution for metric TSP.
Q12- What property of metric spaces is crucial for the Christofides algorithm?
Symmetry
Transitivity
Triangle Inequality
Reflexivity
Ans – (3)
Explanation – The triangle inequality is essential as it ensures that the direct path between two cities is never longer than a detour through an intermediate city.
The triangle inequality is a mathematical principle that applies to distances in metric spaces. It states that for any three points A, B, and C, the direct distance between two points is always shorter or equal to the sum of the distances through an intermediate point. In formula form, it is expressed as:
d(A,C) ≤ d(A,B) + d(B,C)