From 743f9660a88df89c091ccaad5a1a05a8e4597575 Mon Sep 17 00:00:00 2001
From: Deniz Altunkapan <93663085+DenizAltunkapan@users.noreply.github.com>
Date: Tue, 1 Apr 2025 00:18:19 +0200
Subject: [PATCH] Add Traveling Salesman Problem (#6205)
---
.../graph/TravelingSalesman.java | 155 ++++++++++++++++++
.../graph/TravelingSalesmanTest.java | 127 ++++++++++++++
2 files changed, 282 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/graph/TravelingSalesman.java
create mode 100644 src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java
diff --git a/src/main/java/com/thealgorithms/graph/TravelingSalesman.java b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java
new file mode 100644
index 000000000..14bf89f57
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java
@@ -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 Wikipedia.
+ * @author Deniz Altunkapan
+ */
+
+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 cities = new ArrayList<>();
+ for (int i = 1; i < distanceMatrix.length; i++) {
+ cities.add(i);
+ }
+
+ List> permutations = generatePermutations(cities);
+ int minDistance = Integer.MAX_VALUE;
+
+ for (List permutation : permutations) {
+ List 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 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> generatePermutations(List cities) {
+ List> 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 arr, int k, List> 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;
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java
new file mode 100644
index 000000000..b93c9f89c
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java
@@ -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);
+ }
+}