Skip to content

Commit 763cbe4

Browse files
Implement bitwise dp algorithm and sos dp
1 parent c498379 commit 763cbe4

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
import java.util.Arrays;
4+
5+
/**
6+
* Solves the Traveling Salesman Problem using bitmask dynamic programming.
7+
*
8+
* <p>The DP state is {@code dp[mask][city]}, where {@code mask} stores the set of
9+
* visited cities and {@code city} is the current endpoint of the partial tour.
10+
* This is the classic Held-Karp formulation and is practical for small inputs
11+
* where {@code n <= 20}.</p>
12+
*
13+
* <p>This bitmask version complements {@code graph/TravelingSalesman.java} by
14+
* showing the subset-DP formulation explicitly, which is useful for small
15+
* instances and for problems that require visiting subsets in a specific order.</p>
16+
*/
17+
public final class TravelingSalesmanBitmask {
18+
19+
private static final int INF = Integer.MAX_VALUE / 4;
20+
21+
private TravelingSalesmanBitmask() {
22+
}
23+
24+
/**
25+
* Computes the minimum Hamiltonian cycle cost starting and ending at city 0.
26+
*
27+
* <p>The input must be a square matrix. Use {@link Integer#MAX_VALUE} for
28+
* missing edges. The method returns {@code 0} when no Hamiltonian cycle exists.
29+
*
30+
* @param distanceMatrix square matrix of edge weights; use {@link Integer#MAX_VALUE}
31+
* for missing edges
32+
* @return the minimum tour cost, or 0 when no tour exists
33+
* @throws IllegalArgumentException if the matrix is not square
34+
*/
35+
public static int solve(int[][] distanceMatrix) {
36+
if (distanceMatrix == null) {
37+
throw new IllegalArgumentException("Distance matrix cannot be null");
38+
}
39+
if (distanceMatrix.length == 0) {
40+
return 0;
41+
}
42+
43+
int n = distanceMatrix.length;
44+
for (int[] row : distanceMatrix) {
45+
if (row == null || row.length != n) {
46+
throw new IllegalArgumentException("Matrix must be square");
47+
}
48+
}
49+
50+
int[][] dp = new int[1 << n][n];
51+
for (int[] row : dp) {
52+
Arrays.fill(row, INF);
53+
}
54+
55+
dp[1][0] = 0;
56+
57+
for (int mask = 1; mask < (1 << n); mask++) {
58+
for (int currentCity = 0; currentCity < n; currentCity++) {
59+
if (!isBitSet(mask, currentCity) || dp[mask][currentCity] == INF) {
60+
continue;
61+
}
62+
63+
for (int nextCity = 0; nextCity < n; nextCity++) {
64+
if (isBitSet(mask, nextCity) || distanceMatrix[currentCity][nextCity] == Integer.MAX_VALUE) {
65+
continue;
66+
}
67+
68+
int nextMask = setBit(mask, nextCity);
69+
int newCost = safeAdd(dp[mask][currentCity], distanceMatrix[currentCity][nextCity]);
70+
if (newCost == INF) {
71+
continue;
72+
}
73+
if (newCost < dp[nextMask][nextCity]) {
74+
dp[nextMask][nextCity] = newCost;
75+
}
76+
}
77+
}
78+
}
79+
80+
int fullMask = (1 << n) - 1;
81+
int bestTour = INF;
82+
for (int lastCity = 1; lastCity < n; lastCity++) {
83+
if (dp[fullMask][lastCity] == INF || distanceMatrix[lastCity][0] == Integer.MAX_VALUE) {
84+
continue;
85+
}
86+
bestTour = Math.min(bestTour, safeAdd(dp[fullMask][lastCity], distanceMatrix[lastCity][0]));
87+
}
88+
89+
return bestTour == INF ? 0 : bestTour;
90+
}
91+
92+
private static boolean isBitSet(int mask, int bit) {
93+
return (mask & (1 << bit)) != 0;
94+
}
95+
96+
private static int setBit(int mask, int bit) {
97+
return mask | (1 << bit);
98+
}
99+
100+
private static int safeAdd(int left, int right) {
101+
if (left == INF || right == Integer.MAX_VALUE) {
102+
return INF;
103+
}
104+
long sum = (long) left + right;
105+
return sum >= INF ? INF : (int) sum;
106+
}
107+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
public final class TravelingSalesmanBitmaskTest {
9+
10+
@Test
11+
public void solveShouldReturnMinimumTourCost() {
12+
int[][] distanceMatrix = {
13+
{0, 10, 15, 20},
14+
{10, 0, 35, 25},
15+
{15, 35, 0, 30},
16+
{20, 25, 30, 0}
17+
};
18+
19+
assertEquals(80, TravelingSalesmanBitmask.solve(distanceMatrix));
20+
}
21+
22+
@Test
23+
public void solveShouldReturnZeroForEmptyMatrix() {
24+
assertEquals(0, TravelingSalesmanBitmask.solve(new int[0][0]));
25+
}
26+
27+
@Test
28+
public void solveShouldReturnZeroForSingleCity() {
29+
int[][] distanceMatrix = {
30+
{0}
31+
};
32+
33+
assertEquals(0, TravelingSalesmanBitmask.solve(distanceMatrix));
34+
}
35+
36+
@Test
37+
public void solveShouldReturnZeroWhenTourIsDisconnected() {
38+
int inf = Integer.MAX_VALUE;
39+
int[][] distanceMatrix = {
40+
{0, 1, inf},
41+
{1, 0, inf},
42+
{inf, inf, 0}
43+
};
44+
45+
assertEquals(0, TravelingSalesmanBitmask.solve(distanceMatrix));
46+
}
47+
48+
@Test
49+
public void solveShouldRejectNonSquareMatrix() {
50+
int[][] distanceMatrix = {
51+
{0, 1, 2},
52+
{1, 0, 3}
53+
};
54+
55+
assertThrows(IllegalArgumentException.class, () -> TravelingSalesmanBitmask.solve(distanceMatrix));
56+
}
57+
}

0 commit comments

Comments
 (0)