Solving the Travelling Salesman Problem in C++: A Comprehensive Guide

The Travelling Salesman Problem (TSP) is a well-known optimization problem in the field of computer science and operations research. It poses a simple yet challenging question: given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city? This problem is NP-hard, meaning it is computationally intensive and cannot be solved efficiently as the number of cities increases. In this blog, we will delve into various approaches to solving TSP using C++, one of the most powerful programming languages for such tasks.

Introduction to the Travelling Salesman Problem

The Travelling Salesman Problem is one of the most intensely studied problems in computational theory and operations research due to its complex nature and practical applications. It has applications in logistics, manufacturing, and even in DNA sequencing. Given its NP-hard status, the problem does not have a known polynomial-time solution, but several exact, approximation, and heuristic methods are available to tackle it.

Exact Algorithms

Brute Force Method

The brute force approach is the simplest to understand but computationally expensive. It involves generating all possible permutations of cities and calculating the total distance for each permutation. The permutation with the shortest distance is the solution.

Implementation in C++

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>

using namespace std;

// Function to calculate the total distance of a given path
int calculateDistance(vector<int>& path, vector<vector<int>>& distanceMatrix) {
    int totalDistance = 0;
    for (size_t i = 0; i < path.size() - 1; ++i) {
        totalDistance += distanceMatrix[path[i]][path[i+1]];
    }
    totalDistance += distanceMatrix[path.back()][path[0]]; // Return to the starting city
    return totalDistance;
}

// Brute force approach to TSP
void tspBruteForce(vector<vector<int>>& distanceMatrix) {
    int numCities = distanceMatrix.size();
    vector<int> cities(numCities);
    for (int i = 0; i < numCities; ++i) cities[i] = i;

    int minDistance = INT_MAX;
    vector<int> bestPath;

    do {
        int currentDistance = calculateDistance(cities, distanceMatrix);
        if (currentDistance < minDistance) {
            minDistance = currentDistance;
            bestPath = cities;
        }
    } while (next_permutation(cities.begin(), cities.end()));

    cout << "Minimum distance: " << minDistance << "\nBest path: ";
    for (int city : bestPath) cout << city << " ";
    cout << endl;
}

Dynamic Programming

The dynamic programming approach to TSP uses memoization to store intermediate results and thus reduces the redundant calculations. One popular method is the Held-Karp algorithm, which has a time complexity of (O(n^2 \cdot 2^n)).

Implementation in C++

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

const int INF = 1e9;

int tspDP(int mask, int pos, vector<vector<int>>& distanceMatrix, vector<vector<int>>& dp) {
    if (mask == ((1 << distanceMatrix.size()) - 1)) return distanceMatrix[pos][0];
    if (dp[mask][pos] != -1) return dp[mask][pos];

    int ans = INF;
    for (int city = 0; city < distanceMatrix.size(); ++city) {
        if ((mask & (1 << city)) == 0) {
            int newAns = distanceMatrix[pos][city] + tspDP(mask | (1 << city), city, distanceMatrix, dp);
            ans = min(ans, newAns);
        }
    }

    return dp[mask][pos] = ans;
}

void solveTSPWithDP(vector<vector<int>>& distanceMatrix) {
    int numCities = distanceMatrix.size();
    vector<vector<int>> dp(1 << numCities, vector<int>(numCities, -1));
    int minCost = tspDP(1, 0, distanceMatrix, dp);

    cout << "Minimum cost using DP: " << minCost << endl;
}

Approximation Algorithms

Nearest Neighbor Algorithm

The nearest neighbor algorithm is a simple heuristic that starts from an arbitrary city and repeatedly visits the nearest unvisited city until all cities have been visited. This algorithm does not guarantee the optimal solution but often produces a reasonably good result in a shorter time.

Implementation in C++

#include <iostream>
#include <vector>
#include <limits>

using namespace std;

void tspNearestNeighbor(vector<vector<int>>& distanceMatrix) {
    int numCities = distanceMatrix.size();
    vector<bool> visited(numCities, false);
    vector<int> path;

    int currentCity = 0;
    visited[currentCity] = true;
    path.push_back(currentCity);

    for (int i = 1; i < numCities; ++i) {
        int nearestCity = -1;
        int minDistance = INT_MAX;

        for (int j = 0; j < numCities; ++j) {
            if (!visited[j] && distanceMatrix[currentCity][j] < minDistance) {
                nearestCity = j;
                minDistance = distanceMatrix[currentCity][j];
            }
        }

        currentCity = nearestCity;
        visited[currentCity] = true;
        path.push_back(currentCity);
    }

    // Return to the starting city
    path.push_back(path[0]);

    cout << "Path using Nearest Neighbor: ";
    for (int city : path) cout << city << " ";
    cout << endl;
}

Genetic Algorithm

A genetic algorithm is a search heuristic that mimics the process of natural evolution. It uses techniques such as selection, crossover, and mutation to evolve a population of candidate solutions towards better solutions.

Implementation in C++

#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <cstdlib>

using namespace std;

struct Individual {
    vector<int> path;
    int fitness;
};

int calculateDistance(vector<int>& path, vector<vector<int>>& distanceMatrix) {
    int totalDistance = 0;
    for (size_t i = 0; i < path.size() - 1; ++i) {
        totalDistance += distanceMatrix[path[i]][path[i+1]];
    }
    totalDistance += distanceMatrix[path.back()][path[0]]; // Return to the starting city
    return totalDistance;
}

void initializePopulation(vector<Individual>& population, int numCities, int populationSize) {
    vector<int> basePath(numCities);
    for (int i = 0; i < numCities; ++i) basePath[i] = i;

    for (int i = 0; i < populationSize; ++i) {
        random_shuffle(basePath.begin(), basePath.end());
        Individual individual;
        individual.path = basePath;
        individual.fitness = 0;
        population.push_back(individual);
    }
}

void evaluateFitness(vector<Individual>& population, vector<vector<int>>& distanceMatrix) {
    for (auto& individual : population) {
        individual.fitness = calculateDistance(individual.path, distanceMatrix);
    }
}

bool compareIndividuals(const Individual& a, const Individual& b) {
    return a.fitness < b.fitness;
}

void selectParents(vector<Individual>& population, Individual& parent1, Individual& parent2) {
    sort(population.begin(), population.end(), compareIndividuals);
    parent1 = population[0];
    parent2 = population[1];
}

Individual crossover(const Individual& parent1, const Individual& parent2) {
    int numCities = parent1.path.size();
    vector<int> childPath(numCities, -1);

    int start = rand() % numCities;
    int end = start + rand() % (numCities - start);

    for (int i = start; i <= end; ++i) {
        childPath[i] = parent1.path[i];
    }

    int childIndex = 0;
    for (int i = 0; i < numCities; ++i) {
        if (find(childPath.begin(), childPath.end(), parent2.path[i]) == childPath.end()) {
            while (childPath[childIndex] != -1) {
                ++childIndex;
            }
            childPath[childIndex] = parent2.path[i];
        }
    }

    Individual child;
    child.path = childPath;
    child.fitness = 0;
    return child;
}

void mutate(Individual& individual, double mutationRate) {
    for (size_t i = 0; i < individual.path.size(); ++i) {
        if ((rand() / double(RAND_MAX)) < mutationRate) {
            int swapIndex = rand() % individual.path.size();
            swap(individual.path[i], individual.path[swapIndex]);
        }
    }
}

void geneticAlgorithm(vector<vector<int>>& distanceMatrix, int populationSize, int generations, double mutationRate) {
    srand(time(0));
    int numCities = distanceMatrix.size();
    vector<Individual> population;

    initialize

Population(population, numCities, populationSize);
    evaluateFitness(population, distanceMatrix);

    for (int generation = 0; generation < generations; ++generation) {
        Individual parent1, parent2;
        selectParents(population, parent1, parent2);

        vector<Individual> newPopulation;
        newPopulation.push_back(parent1);
        newPopulation.push_back(parent2);

        for (int i = 2; i < populationSize; ++i) {
            Individual child = crossover(parent1, parent2);
            mutate(child, mutationRate);
            newPopulation.push_back(child);
        }

        evaluateFitness(newPopulation, distanceMatrix);
        population = newPopulation;
    }

    Individual bestIndividual = *min_element(population.begin(), population.end(), compareIndividuals);
    cout << "Best path using Genetic Algorithm: ";
    for (int city : bestIndividual.path) cout << city << " ";
    cout << "\nMinimum distance: " << bestIndividual.fitness << endl;
}

Heuristic Methods

Simulated Annealing

Simulated annealing is a probabilistic technique for approximating the global optimum of a given function. It is particularly useful for large optimization problems and is inspired by the annealing process in metallurgy.

Implementation in C++

#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <algorithm>

using namespace std;

double probability(double oldCost, double newCost, double temperature) {
    if (newCost < oldCost) return 1.0;
    return exp((oldCost - newCost) / temperature);
}

void simulatedAnnealing(vector<vector<int>>& distanceMatrix) {
    int numCities = distanceMatrix.size();
    vector<int> currentPath(numCities);
    for (int i = 0; i < numCities; ++i) currentPath[i] = i;
    random_shuffle(currentPath.begin(), currentPath.end());

    int currentDistance = calculateDistance(currentPath, distanceMatrix);
    int bestDistance = currentDistance;
    vector<int> bestPath = currentPath;

    double temperature = 10000.0;
    double coolingRate = 0.999;
    srand(time(0));

    while (temperature > 1.0) {
        vector<int> newPath = currentPath;
        int i = rand() % numCities;
        int j = rand() % numCities;
        swap(newPath[i], newPath[j]);

        int newDistance = calculateDistance(newPath, distanceMatrix);
        if (probability(currentDistance, newDistance, temperature) > (rand() / double(RAND_MAX))) {
            currentPath = newPath;
            currentDistance = newDistance;
        }

        if (currentDistance < bestDistance) {
            bestDistance = currentDistance;
            bestPath = currentPath;
        }

        temperature *= coolingRate;
    }

    cout << "Best path using Simulated Annealing: ";
    for (int city : bestPath) cout << city << " ";
    cout << "\nMinimum distance: " << bestDistance << endl;
}

Ant Colony Optimization

Ant Colony Optimization (ACO) is a probabilistic technique inspired by the behavior of ants finding paths to food sources. It uses pheromones to build solutions incrementally and favors solutions that have worked well in the past.

Implementation in C++

#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <algorithm>

using namespace std;

class Ant {
public:
    vector<int> path;
    int pathLength;

    Ant(int numCities) {
        path.reserve(numCities);
        pathLength = 0;
    }

    void visitCity(int city) {
        path.push_back(city);
    }

    bool visited(int city) {
        return find(path.begin(), path.end(), city) != path.end();
    }

    void reset() {
        path.clear();
        pathLength = 0;
    }
};

void antColonyOptimization(vector<vector<int>>& distanceMatrix, int numAnts, int maxIterations, double alpha, double beta, double evaporationRate) {
    int numCities = distanceMatrix.size();
    vector<vector<double>> pheromones(numCities, vector<double>(numCities, 1.0));
    vector<Ant> ants(numAnts, Ant(numCities));
    vector<int> bestPath;
    int bestPathLength = INT_MAX;

    srand(time(0));

    for (int iteration = 0; iteration < maxIterations; ++iteration) {
        for (auto& ant : ants) {
            ant.reset();
            int startCity = rand() % numCities;
            ant.visitCity(startCity);

            for (int step = 1; step < numCities; ++step) {
                int currentCity = ant.path.back();
                vector<double> probabilities(numCities, 0.0);
                double sumProbabilities = 0.0;

                for (int city = 0; city < numCities; ++city) {
                    if (!ant.visited(city)) {
                        double probability = pow(pheromones[currentCity][city], alpha) * pow(1.0 / distanceMatrix[currentCity][city], beta);
                        probabilities[city] = probability;
                        sumProbabilities += probability;
                    }
                }

                double randProb = (rand() / double(RAND_MAX)) * sumProbabilities;
                double cumulativeProbability = 0.0;
                int nextCity = -1;

                for (int city = 0; city < numCities; ++city) {
                    if (!ant.visited(city)) {
                        cumulativeProbability += probabilities[city];
                        if (randProb <= cumulativeProbability) {
                            nextCity = city;
                            break;
                        }
                    }
                }

                ant.visitCity(nextCity);
                ant.pathLength += distanceMatrix[currentCity][nextCity];
            }

            ant.pathLength += distanceMatrix[ant.path.back()][ant.path.front()];

            if (ant.pathLength < bestPathLength) {
                bestPathLength = ant.pathLength;
                bestPath = ant.path;
            }
        }

        for (int i = 0; i < numCities; ++i) {
            for (int j = 0; j < numCities; ++j) {
                pheromones[i][j] *= (1.0 - evaporationRate);
            }
        }

        for (const auto& ant : ants) {
            for (int i = 0; i < numCities - 1; ++i) {
                pheromones[ant.path[i]][ant.path[i+1]] += 1.0 / ant.pathLength;
            }
            pheromones[ant.path.back()][ant.path.front()] += 1.0 / ant.pathLength;
        }
    }

    cout << "Best path using Ant Colony Optimization: ";
    for (int city : bestPath) cout << city << " ";
    cout << "\nMinimum distance: " << bestPathLength << endl;
}

Implementing TSP in C++

Setting Up the Environment

To implement the TSP in C++, you need a C++ compiler like GCC or an IDE like Visual Studio. You can also use online compilers like repl.it or CodeChef IDE for quick testing.

Example Implementations

We have already covered implementations for brute force, dynamic programming, nearest neighbor, genetic algorithm, simulated annealing, and ant colony optimization. These implementations provide a solid foundation for understanding different approaches to solving TSP.

Also read

Solving the Travelling Salesman Problem in Python: A Comprehensive Guide

Solving the Travelling Salesman Problem in Java: A Comprehensive Guide

Exploring the Travelling Salesman Problem: A Comprehensive Guide in C

Conclusion

The Travelling Salesman Problem is a classic problem that continues to captivate researchers and practitioners. With C++, you have a powerful tool to implement various algorithms to tackle this problem. Whether you choose exact methods, approximation algorithms, or heuristic approaches, each has its own merits and use cases. Experimenting with these implementations will not only help you understand TSP better but also enhance your problem-solving skills in algorithm design and optimization.

Happy coding!