Skip to content

Commit 3ae99f7

Browse files
williamfisetclaude
andauthored
Refactor TspDynamicProgrammingIterative with docs and phase comments (williamfiset#1283)
* Refactor TspDynamicProgrammingIterative: add docs, phase comments, cross-reference Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Restore original comments in combinations() helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7bf3377 commit 3ae99f7

File tree

1 file changed

+75
-34
lines changed

1 file changed

+75
-34
lines changed

src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java

Lines changed: 75 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
1-
/**
2-
* An implementation of the traveling salesman problem in Java using dynamic programming to improve
3-
* the time complexity from O(n!) to O(n^2 * 2^n).
4-
*
5-
* <p>Time Complexity: O(n^2 * 2^n) Space Complexity: O(n * 2^n)
6-
*
7-
* @author William Fiset, william.alexandre.fiset@gmail.com
8-
*/
91
package com.williamfiset.algorithms.graphtheory;
102

113
import java.util.ArrayList;
124
import java.util.Collections;
135
import java.util.List;
146

7+
/**
8+
* Traveling Salesman Problem — Iterative DP with Bitmask
9+
*
10+
* Given a complete weighted graph of n nodes, find the minimum-cost
11+
* Hamiltonian cycle (a tour that visits every node exactly once and
12+
* returns to the starting node).
13+
*
14+
* This iterative (bottom-up) approach builds solutions for increasing
15+
* subset sizes. For each subset S of visited nodes and each endpoint
16+
* node i in S, we compute the minimum cost to reach i having visited
17+
* exactly the nodes in S. The recurrence is:
18+
*
19+
* memo[next][S | (1 << next)] = min over end in S of
20+
* memo[end][S] + distance[end][next]
21+
*
22+
* After filling the table, we close the tour by connecting back to
23+
* the start node and backtrack through the table to reconstruct
24+
* the optimal path.
25+
*
26+
* See also: {@link TspDynamicProgrammingRecursive} for the top-down variant.
27+
*
28+
* Time: O(n^2 * 2^n)
29+
* Space: O(n * 2^n)
30+
*
31+
* @author William Fiset, william.alexandre.fiset@gmail.com
32+
*/
1533
public class TspDynamicProgrammingIterative {
1634

1735
private final int N, start;
@@ -39,67 +57,82 @@ public TspDynamicProgrammingIterative(int start, double[][] distance) {
3957
this.distance = distance;
4058
}
4159

42-
// Returns the optimal tour for the traveling salesman problem.
60+
/**
61+
* Returns the optimal tour for the traveling salesman problem.
62+
*
63+
* @return ordered list of node indices forming the optimal tour
64+
* (starts and ends with the start node)
65+
*/
4366
public List<Integer> getTour() {
4467
if (!ranSolver) solve();
4568
return tour;
4669
}
4770

48-
// Returns the minimal tour cost.
71+
/**
72+
* Returns the minimal tour cost.
73+
*
74+
* @return the total cost of the optimal Hamiltonian cycle
75+
*/
4976
public double getTourCost() {
5077
if (!ranSolver) solve();
5178
return minTourCost;
5279
}
5380

54-
// Solves the traveling salesman problem and caches solution.
81+
/**
82+
* Solves the TSP and caches the result. Subsequent calls are no-ops.
83+
*
84+
* Phase 1: Fill the DP table bottom-up for subsets of size 2..N.
85+
* Phase 2: Close the tour by connecting the last node back to start.
86+
* Phase 3: Backtrack through the table to reconstruct the tour.
87+
*/
5588
public void solve() {
56-
5789
if (ranSolver) return;
5890

5991
final int END_STATE = (1 << N) - 1;
6092
Double[][] memo = new Double[N][1 << N];
6193

62-
// Add all outgoing edges from the starting node to memo table.
94+
// Phase 1a: Seed the memo table with direct edges from the start node.
95+
// memo[end][{start, end}] = distance from start to end
6396
for (int end = 0; end < N; end++) {
6497
if (end == start) continue;
6598
memo[end][(1 << start) | (1 << end)] = distance[start][end];
6699
}
67100

101+
// Phase 1b: Build solutions for subsets of increasing size (3..N).
102+
// For each subset, try extending the path to each node in the subset.
68103
for (int r = 3; r <= N; r++) {
69104
for (int subset : combinations(r, N)) {
70105
if (notIn(start, subset)) continue;
71106
for (int next = 0; next < N; next++) {
72107
if (next == start || notIn(next, subset)) continue;
108+
// Consider all possible previous endpoints
73109
int subsetWithoutNext = subset ^ (1 << next);
74110
double minDist = Double.POSITIVE_INFINITY;
75111
for (int end = 0; end < N; end++) {
76112
if (end == start || end == next || notIn(end, subset)) continue;
77113
double newDistance = memo[end][subsetWithoutNext] + distance[end][next];
78-
if (newDistance < minDist) {
114+
if (newDistance < minDist)
79115
minDist = newDistance;
80-
}
81116
}
82117
memo[next][subset] = minDist;
83118
}
84119
}
85120
}
86121

87-
// Connect tour back to starting node and minimize cost.
122+
// Phase 2: Close the tour — find the cheapest way to return to start.
88123
for (int i = 0; i < N; i++) {
89124
if (i == start) continue;
90125
double tourCost = memo[i][END_STATE] + distance[i][start];
91-
if (tourCost < minTourCost) {
126+
if (tourCost < minTourCost)
92127
minTourCost = tourCost;
93-
}
94128
}
95129

130+
// Phase 3: Reconstruct the tour by backtracking through the memo table.
96131
int lastIndex = start;
97132
int state = END_STATE;
98133
tour.add(start);
99134

100-
// Reconstruct TSP path from memo table.
101135
for (int i = 1; i < N; i++) {
102-
103136
int bestIndex = -1;
104137
double bestDist = Double.POSITIVE_INFINITY;
105138
for (int j = 0; j < N; j++) {
@@ -122,48 +155,56 @@ public void solve() {
122155
ranSolver = true;
123156
}
124157

158+
/** Returns true if the given element's bit is not set in the subset bitmask. */
125159
private static boolean notIn(int elem, int subset) {
126160
return ((1 << elem) & subset) == 0;
127161
}
128162

129-
// This method generates all bit sets of size n where r bits
130-
// are set to one. The result is returned as a list of integer masks.
163+
/**
164+
* Generates all bitmasks of n bits where exactly r bits are set.
165+
* Used to enumerate subsets of a given size.
166+
*
167+
* @param r - number of bits to set
168+
* @param n - total number of bits
169+
* @return list of integer bitmasks
170+
*/
131171
public static List<Integer> combinations(int r, int n) {
132172
List<Integer> subsets = new ArrayList<>();
133173
combinations(0, 0, r, n, subsets);
134174
return subsets;
135175
}
136176

137-
// To find all the combinations of size r we need to recurse until we have
138-
// selected r elements (aka r = 0), otherwise if r != 0 then we still need to select
139-
// an element which is found after the position of our last selected element
177+
/**
178+
* Recursively builds combinations by deciding whether to include
179+
* each bit position. Backtracks when not enough positions remain.
180+
*/
140181
private static void combinations(int set, int at, int r, int n, List<Integer> subsets) {
141-
142-
// Return early if there are more elements left to select than what is available.
182+
// Not enough positions remaining to pick r more bits
143183
int elementsLeftToPick = n - at;
144184
if (elementsLeftToPick < r) return;
145185

146-
// We selected 'r' elements so we found a valid subset!
147186
if (r == 0) {
148187
subsets.add(set);
149188
} else {
150189
for (int i = at; i < n; i++) {
151190
// Try including this element
152191
set ^= (1 << i);
153-
154192
combinations(set, i + 1, r - 1, n, subsets);
155-
156193
// Backtrack and try the instance where we did not include this element
157194
set ^= (1 << i);
158195
}
159196
}
160197
}
161198

199+
// ==================== Main ====================
200+
162201
public static void main(String[] args) {
163-
// Create adjacency matrix
202+
// Create a 6-node directed graph with a known optimal tour
164203
int n = 6;
165204
double[][] distanceMatrix = new double[n][n];
166-
for (double[] row : distanceMatrix) java.util.Arrays.fill(row, 10000);
205+
for (double[] row : distanceMatrix)
206+
java.util.Arrays.fill(row, 10000);
207+
167208
distanceMatrix[5][0] = 10;
168209
distanceMatrix[1][5] = 12;
169210
distanceMatrix[4][1] = 2;
@@ -175,10 +216,10 @@ public static void main(String[] args) {
175216
TspDynamicProgrammingIterative solver =
176217
new TspDynamicProgrammingIterative(startNode, distanceMatrix);
177218

178-
// Prints: [0, 3, 2, 4, 1, 5, 0]
219+
// Tour: [0, 3, 2, 4, 1, 5, 0]
179220
System.out.println("Tour: " + solver.getTour());
180221

181-
// Print: 42.0
222+
// Tour cost: 42.0
182223
System.out.println("Tour cost: " + solver.getTourCost());
183224
}
184225
}

0 commit comments

Comments
 (0)