Skip to content

feat: add Traveling Salesman Problem implementation #6205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions src/main/java/com/thealgorithms/graph/TravelingSalesman.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package com.thealgorithms.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* This class provides solutions to the Traveling Salesman Problem (TSP) using both brute-force and dynamic programming approaches.
* For more information, see <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">Uncyclopedia</a>.
* @author <a href="https://github.com/DenizAltunkapan">Deniz Altunkapan</a>
*/

public final class TravelingSalesman {

// Private constructor to prevent instantiation
private TravelingSalesman() {
}

/**
* Solves the Traveling Salesman Problem (TSP) using brute-force approach.
* This method generates all possible permutations of cities, calculates the total distance for each route, and returns the shortest distance found.
*
* @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j.
* @return The shortest possible route distance visiting all cities exactly once and returning to the starting city.
*/
public static int bruteForce(int[][] distanceMatrix) {
if (distanceMatrix.length <= 1) {
return 0;
}

List<Integer> cities = new ArrayList<>();
for (int i = 1; i < distanceMatrix.length; i++) {
cities.add(i);
}

List<List<Integer>> permutations = generatePermutations(cities);
int minDistance = Integer.MAX_VALUE;

for (List<Integer> permutation : permutations) {
List<Integer> route = new ArrayList<>();
route.add(0);
route.addAll(permutation);
int currentDistance = calculateDistance(distanceMatrix, route);
if (currentDistance < minDistance) {
minDistance = currentDistance;
}
}

return minDistance;
}

/**
* Computes the total distance of a given route.
*
* @param distanceMatrix A square matrix where element [i][j] represents the
* distance from city i to city j.
* @param route A list representing the order in which the cities are visited.
* @return The total distance of the route, or Integer.MAX_VALUE if the route is invalid.
*/
public static int calculateDistance(int[][] distanceMatrix, List<Integer> route) {
int distance = 0;
for (int i = 0; i < route.size() - 1; i++) {
int d = distanceMatrix[route.get(i)][route.get(i + 1)];
if (d == Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
distance += d;
}
int returnDist = distanceMatrix[route.get(route.size() - 1)][route.get(0)];
return (returnDist == Integer.MAX_VALUE) ? Integer.MAX_VALUE : distance + returnDist;
}

/**
* Generates all permutations of a given list of cities.
*
* @param cities A list of cities to permute.
* @return A list of all possible permutations.
*/
private static List<List<Integer>> generatePermutations(List<Integer> cities) {
List<List<Integer>> permutations = new ArrayList<>();
permute(cities, 0, permutations);
return permutations;
}

/**
* Recursively generates permutations using backtracking.
*
* @param arr The list of cities.
* @param k The current index in the permutation process.
* @param output The list to store generated permutations.
*/
private static void permute(List<Integer> arr, int k, List<List<Integer>> output) {
if (k == arr.size()) {
output.add(new ArrayList<>(arr));
return;
}
for (int i = k; i < arr.size(); i++) {
Collections.swap(arr, i, k);
permute(arr, k + 1, output);
Collections.swap(arr, i, k);
}
}

/**
* Solves the Traveling Salesman Problem (TSP) using dynamic programming with the Held-Karp algorithm.
*
* @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j.
* @return The shortest possible route distance visiting all cities exactly once and returning to the starting city.
* @throws IllegalArgumentException if the input matrix is not square.
*/
public static int dynamicProgramming(int[][] distanceMatrix) {
if (distanceMatrix.length == 0) {
return 0;
}
int n = distanceMatrix.length;

for (int[] row : distanceMatrix) {
if (row.length != n) {
throw new IllegalArgumentException("Matrix must be square");
}
}

int[][] dp = new int[n][1 << n];
for (int[] row : dp) {
Arrays.fill(row, Integer.MAX_VALUE);
}
dp[0][1] = 0;

for (int mask = 1; mask < (1 << n); mask++) {
for (int u = 0; u < n; u++) {
if ((mask & (1 << u)) == 0 || dp[u][mask] == Integer.MAX_VALUE) {
continue;
}
for (int v = 0; v < n; v++) {
if ((mask & (1 << v)) != 0 || distanceMatrix[u][v] == Integer.MAX_VALUE) {
continue;
}
int newMask = mask | (1 << v);
dp[v][newMask] = Math.min(dp[v][newMask], dp[u][mask] + distanceMatrix[u][v]);
}
}
}

int minDistance = Integer.MAX_VALUE;
int fullMask = (1 << n) - 1;
for (int i = 1; i < n; i++) {
if (dp[i][fullMask] != Integer.MAX_VALUE && distanceMatrix[i][0] != Integer.MAX_VALUE) {
minDistance = Math.min(minDistance, dp[i][fullMask] + distanceMatrix[i][0]);
}
}

return minDistance == Integer.MAX_VALUE ? 0 : minDistance;
}
}
127 changes: 127 additions & 0 deletions src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.thealgorithms.graph;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class TravelingSalesmanTest {

// Test Case 1: A simple distance matrix with 4 cities
@Test
public void testBruteForceSimple() {
int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}};
int expectedMinDistance = 80;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingSimple() {
int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}};
int expectedMinDistance = 80;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 2: A distance matrix with 3 cities
@Test
public void testBruteForceThreeCities() {
int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}};
int expectedMinDistance = 60;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingThreeCities() {
int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}};
int expectedMinDistance = 60;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 3: A distance matrix with 5 cities (larger input)
@Test
public void testBruteForceFiveCities() {
int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}};
int expectedMinDistance = 15;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingFiveCities() {
int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}};
int expectedMinDistance = 15;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 4: A distance matrix with 2 cities (simple case)
@Test
public void testBruteForceTwoCities() {
int[][] distanceMatrix = {{0, 1}, {1, 0}};
int expectedMinDistance = 2;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingTwoCities() {
int[][] distanceMatrix = {{0, 1}, {1, 0}};
int expectedMinDistance = 2;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 5: A distance matrix with identical distances
@Test
public void testBruteForceEqualDistances() {
int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}};
int expectedMinDistance = 40;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingEqualDistances() {
int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}};
int expectedMinDistance = 40;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 6: A distance matrix with only one city
@Test
public void testBruteForceOneCity() {
int[][] distanceMatrix = {{0}};
int expectedMinDistance = 0;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingOneCity() {
int[][] distanceMatrix = {{0}};
int expectedMinDistance = 0;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

// Test Case 7: Distance matrix with large numbers
@Test
public void testBruteForceLargeNumbers() {
int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}};
int expectedMinDistance = 4500000;
int result = TravelingSalesman.bruteForce(distanceMatrix);
assertEquals(expectedMinDistance, result);
}

@Test
public void testDynamicProgrammingLargeNumbers() {
int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}};
int expectedMinDistance = 4500000;
int result = TravelingSalesman.dynamicProgramming(distanceMatrix);
assertEquals(expectedMinDistance, result);
}
}