import java.util.*;
/**
* Java Implementation for Unit 4 Assignment Activity (TSP with DP).
*
* This file includes:
* 1) Held–Karp Dynamic Programming solution for TSP (exact, exponential).
* 2) Brute-force permutation baseline (exact, factorial) for comparison on small n.
* 3) A test driver that runs instances from n = 5 to 15, reports timing, and prints tours.
*
* Notes for the assignment:
* - Start city is fixed at index 0.
* - Distances are generated as a reproducible symmetric matrix using a fixed seed.
* - DP runs for n = 5..15 (as required).
* - Brute force runs only up to a safe limit (default 10) to avoid excessive runtime.
*/
public class Main {
/**
* Result container: minimum cost and the full tour (0 -> ... -> 0).
*/
public static class Result {
public final double minCost;
public final List<Integer> tour;
public Result(double minCost, List<Integer> tour) {
this.minCost = minCost;
this.tour = tour;
}
}
// ============================================================
// Part 1: Held–Karp Dynamic Programming (Exact)
// ============================================================
/**
* Solves TSP using Held–Karp DP.
*
* dp[mask][i] = minimum cost to start at 0, visit exactly the cities in "mask",
* and end at city i.
*
* Time Complexity: O(n^2 * 2^n)
* Space Complexity: O(n * 2^n)
*
* @param dist distance matrix dist[i][j]. Can be symmetric or asymmetric.
* @return Result containing min tour cost and the reconstructed tour.
*/
public static Result solveDP(double[][] dist) {
int n = dist.length;
// Edge case: n=1 => tour is 0 -> 0 with cost 0.
if (n == 1) {
return new Result
(0.0,
Arrays.
asList(0,
0)); }
int ALL = (1 << n) - 1;
// dp[mask][i] best cost to reach i after visiting mask.
double[][] dp = new double[1 << n][n];
// parent[mask][i] stores the predecessor city that leads to best dp[mask][i].
int[][] parent = new int[1 << n][n];
// Initialize DP table.
for (int mask = 0; mask <= ALL; mask++) {
Arrays.
fill(parent
[mask
],
-1); }
// Base case: only city 0 visited, at city 0, cost = 0.
dp[1][0] = 0.0;
// Build up solutions by expanding masks that include the start city.
for (int mask = 1; mask <= ALL; mask++) {
if ((mask & 1) == 0) continue; // must include city 0
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) == 0) continue; // i not in mask
double currentCost = dp[mask][i];
if (Double.
isInfinite(currentCost
)) continue;
// Try adding a new city k not yet visited.
for (int k = 0; k < n; k++) {
if ((mask & (1 << k)) != 0) continue; // already visited
int nextMask = mask | (1 << k);
double nextCost = currentCost + dist[i][k];
if (nextCost < dp[nextMask][k]) {
dp[nextMask][k] = nextCost;
parent[nextMask][k] = i;
}
}
}
}
// Close the tour: return to city 0 from the best last city.
double bestCost
= Double.
POSITIVE_INFINITY; int bestLast = -1;
for (int i = 1; i < n; i++) {
double cost = dp[ALL][i] + dist[i][0];
if (cost < bestCost) {
bestCost = cost;
bestLast = i;
}
}
if (Double.
isInfinite(bestCost
)) { // No feasible tour (e.g., disconnected matrix).
}
// Reconstruct the path from parent pointers: state (ALL, bestLast).
List<Integer> reversed = new ArrayList<>();
int mask = ALL;
int cur = bestLast;
reversed.add(cur);
while (cur != 0) {
int prev = parent[mask][cur];
mask = mask ^ (1 << cur); // remove cur from mask
cur = prev;
reversed.add(cur);
}
reversed.add(0); // return to start
return new Result(bestCost, reversed);
}
// ============================================================
// Part 2: Brute Force Permutations (Exact Baseline)
// ============================================================
/**
* Brute-force TSP by enumerating all permutations of cities 1..n-1.
* This is used as a baseline for small n.
*
* Time Complexity: O((n-1)! * n)
* Space Complexity: O(n)
*
* @param dist distance matrix dist[i][j]
* @return Result with exact minimum tour cost and tour
*/
public static Result solveBruteForce(double[][] dist) {
int n = dist.length;
if (n == 1) {
return new Result
(0.0,
Arrays.
asList(0,
0)); }
// Create the list of cities excluding the start city 0.
int[] cities = new int[n - 1];
for (int i = 0; i < n - 1; i++) {
cities[i] = i + 1;
}
double best
= Double.
POSITIVE_INFINITY;
// Generate permutations in-place using Heap's algorithm or next-permutation.
// Here we use a simple recursive permutation generator for clarity.
permute(cities, 0, dist, new BestHolder());
// The recursive method stores the best result in a static holder.
best = BestHolder.bestCost;
bestTour = BestHolder.bestTour;
return new Result(best, bestTour);
}
/**
* Helper class used by brute force to store the best cost/tour found so far.
* Static fields allow a clean interface for the recursive permute method.
*/
private static class BestHolder {
static double bestCost
= Double.
POSITIVE_INFINITY; static List
<Integer
> bestTour
= Collections.
emptyList();
BestHolder() {
// Reset between calls
bestCost
= Double.
POSITIVE_INFINITY; }
}
/**
* Recursive permutation of cities[] from index "pos" onward.
* For each full permutation, compute tour cost: 0 -> perm -> 0.
*/
private static void permute(int[] cities, int pos, double[][] dist, BestHolder holder) {
int n = dist.length;
if (pos == cities.length) {
// Evaluate tour for the current permutation.
double cost = 0.0;
int prev = 0;
for (int city : cities) {
cost += dist[prev][city];
prev = city;
}
cost += dist[prev][0]; // return to start
if (cost < BestHolder.bestCost) {
BestHolder.bestCost = cost;
// Build the best tour list [0, perm..., 0].
List<Integer> tour = new ArrayList<>();
tour.add(0);
for (int city : cities) tour.add(city);
tour.add(0);
BestHolder.bestTour = tour;
}
return;
}
for (int i = pos; i < cities.length; i++) {
swap(cities, pos, i);
permute(cities, pos + 1, dist, holder);
swap(cities, pos, i); // backtrack
}
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void runNamedExample() {
String[] offices
= {"Madrid",
"Sevilla",
"Valencia",
"Bilbao",
"Zaragoza"};
// Symmetric example distances (km-like toy values)
double[][] dist = {
{0, 530, 360, 400, 320},
{530, 0, 650, 880, 800},
{360, 650, 0, 610, 310},
{400, 880, 610, 0, 300},
{320, 800, 310, 300, 0}
};
Result dpRes = solveDP(dist);
System.
out.
println("Example input (offices): " + Arrays.
toString(offices
)); printDistanceMatrix(offices, dist);
System.
out.
println("DP minimum cost: " + String.
format("%.2f", dpRes.
minCost)); System.
out.
println("DP tour (indices): " + dpRes.
tour); System.
out.
println("DP tour (names): " + tourToNames
(dpRes.
tour, offices
));
Result bfRes = solveBruteForce(dist);
System.
out.
println("(Brute force shown for Q3 comparison)"); System.
out.
println("Brute force minimum cost: " + String.
format("%.2f", bfRes.
minCost)); System.
out.
println("Brute force tour (names): " + tourToNames
(bfRes.
tour, offices
)); }
public static void printDistanceMatrix
(String[] offices,
double[][] dist
) { System.
out.
println("Distance matrix (dist[i][j]):");
// Header row
System.
out.
printf("%12s",
""); System.
out.
printf("%12s", name
); }
// Each row i
for (int i = 0; i < offices.length; i++) {
System.
out.
printf("%12s", offices
[i
]); for (int j = 0; j < offices.length; j++) {
System.
out.
printf("%12.0f", dist
[i
][j
]); // 0 decimals because our example uses integers }
}
}
// ============================================================
// Part 3: Test Driver (n = 5..15) + Timing
// ============================================================
/**
* Generates a random symmetric distance matrix with integer weights in [minW, maxW].
* Fixed seed makes results reproducible for the report and screenshots.
*/
public static double[][] randomSymmetricMatrix(int n, int minW, int maxW, long seed) {
double[][] dist = new double[n][n];
for (int i = 0; i < n; i++) dist[i][i] = 0.0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int w = minW + rnd.nextInt(maxW - minW + 1);
dist[i][j] = w;
dist[j][i] = w;
}
}
return dist;
}
/**
* Measures the average runtime (ms) of DP over "repeats" runs.
* This reduces noise from JVM warm-up and short-duration timing.
*/
public static double measureDPAvgMs(double[][] dist, int repeats) {
// Warm-up (helps JIT stabilize a little before timing)
solveDP(dist);
long totalNs = 0L;
for (int r = 0; r < repeats; r++) {
solveDP(dist);
totalNs += (t1 - t0);
}
return (totalNs / (double) repeats) / 1_000_000.0;
}
public static String tourToNames
(List
<Integer
> tour,
String[] offices
) { StringBuilder sb = new StringBuilder();
for (int i = 0; i < tour.size(); i++) {
sb.append(offices[tour.get(i)]);
if (i < tour.size() - 1) sb.append(" -> ");
}
return sb.toString();
}
/**
* Main: prints outputs explicitly mapped to the three assignment tasks.
* - Q1 (Task 1): Shows an explicit input (city names + distance matrix) and outputs the minimum tour.
* - Q2 (Task 2): Runs DP scalability tests for n = 5..15 (DP-only table).
* - Q3 (Task 3): Compares DP vs brute force for small n (separate comparison table).
*/
public static void main
(String[] args
) {
// =========================
// Q1 (Task 1): Explicit input
// =========================
System.
out.
println("===================================================================="); System.
out.
println("Q1 (Task 1): DP TSP on an explicit input (cities + distance matrix)"); System.
out.
println("===================================================================="); runNamedExample(); // includes cities + matrix + DP output (and BF note if you kept it)
// Shared parameters
final int MIN_N = 5;
final int MAX_N = 15;
final int BRUTE_FORCE_LIMIT = 10;
final int DP_REPEATS = 5;
final int MIN_W = 1;
final int MAX_W = 100;
final long BASE_SEED = 12345L;
// =========================
// Q2 (Task 2): DP scalability table (n=5..15)
// =========================
System.
out.
println("===================================================================="); System.
out.
println("Q2 (Task 2): Scalability test for DP (n = 5..15 offices)"); System.
out.
println("===================================================================="); System.
out.
println("Distances: random symmetric [1..100] generated with a fixed seed (reproducible)."); System.
out.
println("DP timing: average of " + DP_REPEATS
+ " runs (ms)."); System.
out.
println("--------------------------------------------------------------------"); System.
out.
printf("%-4s %-12s %-12s %-14s%n",
"n",
"DP_cost",
"DP_time(ms)",
"DP_tour_len"); System.
out.
println("--------------------------------------------------------------------");
for (int n = MIN_N; n <= MAX_N; n++) {
double[][] dist = randomSymmetricMatrix(n, MIN_W, MAX_W, BASE_SEED + n);
Result dpRes = solveDP(dist);
double dpMs = measureDPAvgMs(dist, DP_REPEATS);
int tourLen = dpRes.tour.size();
System.
out.
printf("%-4d %-12.2f %-12.3f %-14d%n", n, dpRes.
minCost, dpMs, tourLen
); }
System.
out.
println("--------------------------------------------------------------------");
// =========================
// Q3 (Task 3): DP vs brute force (n=5..10)
// =========================
System.
out.
println("===================================================================="); System.
out.
println("Q3 (Task 3): DP vs brute-force comparison (n = 5.." + BRUTE_FORCE_LIMIT
+ ")"); System.
out.
println("===================================================================="); System.
out.
println("Note: Brute force is limited to n <= " + BRUTE_FORCE_LIMIT
+ " because the permutation approach grows factorially and becomes impractical.");
System.
out.
println("--------------------------------------------------------------------"); System.
out.
printf("%-4s %-12s %-12s %-12s %-12s %-10s%n",
"n", "DP_cost", "DP_time(ms)", "BF_cost", "BF_time(ms)", "Match?");
System.
out.
println("--------------------------------------------------------------------");
for (int n = MIN_N; n <= BRUTE_FORCE_LIMIT; n++) {
double[][] dist = randomSymmetricMatrix(n, MIN_W, MAX_W, BASE_SEED + n);
// DP (single timed run here is OK; Q2 already did averaged timing)
long dpT0
= System.
nanoTime(); Result dpRes = solveDP(dist);
long dpT1
= System.
nanoTime(); double dpMs = (dpT1 - dpT0) / 1_000_000.0;
// Brute force
long bfT0
= System.
nanoTime(); Result bfRes = solveBruteForce(dist);
long bfT1
= System.
nanoTime(); double bfMs = (bfT1 - bfT0) / 1_000_000.0;
boolean match
= Math.
abs(dpRes.
minCost - bfRes.
minCost) <= 1e
-9
;
System.
out.
printf("%-4d %-12.2f %-12.3f %-12.2f %-12.3f %-10s%n",
n, dpRes.minCost, dpMs, bfRes.minCost, bfMs, match ? "YES" : "NO");
}
System.
out.
println("--------------------------------------------------------------------"); }
}