|
| 1 | +# Traveling Salesman Problem (TSP) using Bitmask Dynamic Programming |
| 2 | +# |
| 3 | +# The Traveling Salesman Problem finds the shortest possible route that visits |
| 4 | +# each city exactly once and returns to the starting city. This implementation |
| 5 | +# uses bitmask DP to efficiently track visited cities. |
| 6 | +# |
| 7 | +# Time Complexity: O(n² * 2^n) where n = number of cities |
| 8 | +# Space Complexity: O(n * 2^n) for memoization table |
| 9 | +# |
| 10 | +# Applications: |
| 11 | +# - Route optimization and logistics |
| 12 | +# - Circuit board drilling and manufacturing |
| 13 | +# - DNA sequencing and genome mapping |
| 14 | +# - Network design and optimization |
| 15 | +# - Scheduling and planning problems |
| 16 | +# - Microchip design and fabrication |
| 17 | + |
| 18 | +# Main TSP function using bitmask DP |
| 19 | +tsp_bitmask_dp <- function(dist) { |
| 20 | + #' Solve the Traveling Salesman Problem using Bitmask Dynamic Programming |
| 21 | + #' @param dist: 2D matrix where dist[i, j] is the distance from city i to city j |
| 22 | + #' @return: Minimum cost to visit all cities and return to starting city |
| 23 | + |
| 24 | + n <- nrow(dist) |
| 25 | + |
| 26 | + # Bitmask when all cities are visited |
| 27 | + ALL_VISITED <- bitwShiftL(1, n) - 1 |
| 28 | + |
| 29 | + # Initialize memoization table |
| 30 | + # memo[pos, mask] = minimum cost starting from pos with visited cities in mask |
| 31 | + memo <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n)) |
| 32 | + |
| 33 | + # Recursive DP helper function |
| 34 | + dp <- function(mask, pos) { |
| 35 | + #' Recursive DP function to compute minimum travel cost |
| 36 | + #' @param mask: Bitmask representing visited cities |
| 37 | + #' @param pos: Current city position (0-indexed) |
| 38 | + #' @return: Minimum travel cost from current state |
| 39 | + |
| 40 | + # Base case: all cities visited, return to starting city (city 0) |
| 41 | + if (mask == ALL_VISITED) { |
| 42 | + return(dist[pos + 1, 1]) |
| 43 | + } |
| 44 | + |
| 45 | + # Check memoization table |
| 46 | + if (!is.na(memo[pos + 1, mask + 1])) { |
| 47 | + return(memo[pos + 1, mask + 1]) |
| 48 | + } |
| 49 | + |
| 50 | + # Initialize minimum cost as infinity |
| 51 | + min_cost <- Inf |
| 52 | + |
| 53 | + # Try visiting each unvisited city |
| 54 | + for (city in 0:(n - 1)) { |
| 55 | + # Check if city is not visited (bit is 0) |
| 56 | + if (bitwAnd(mask, bitwShiftL(1, city)) == 0) { |
| 57 | + # Mark city as visited |
| 58 | + new_mask <- bitwOr(mask, bitwShiftL(1, city)) |
| 59 | + # Calculate cost: distance to city + cost from city |
| 60 | + cost <- dist[pos + 1, city + 1] + dp(new_mask, city) |
| 61 | + min_cost <- min(min_cost, cost) |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + # Store result in memo table |
| 66 | + memo[pos + 1, mask + 1] <<- min_cost |
| 67 | + return(min_cost) |
| 68 | + } |
| 69 | + |
| 70 | + # Start from city 0 with only city 0 visited (mask = 1) |
| 71 | + result <- dp(1, 0) |
| 72 | + return(result) |
| 73 | +} |
| 74 | + |
| 75 | +# Function to get the optimal path (with path reconstruction) |
| 76 | +tsp_bitmask_with_path <- function(dist) { |
| 77 | + #' Solve TSP and return both minimum cost and the optimal path |
| 78 | + #' @param dist: 2D distance matrix |
| 79 | + #' @return: List containing minimum cost and optimal path |
| 80 | + |
| 81 | + n <- nrow(dist) |
| 82 | + ALL_VISITED <- bitwShiftL(1, n) - 1 |
| 83 | + |
| 84 | + # Memoization tables |
| 85 | + memo <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n)) |
| 86 | + parent <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n)) |
| 87 | + |
| 88 | + # DP function with path tracking |
| 89 | + dp <- function(mask, pos) { |
| 90 | + if (mask == ALL_VISITED) { |
| 91 | + return(dist[pos + 1, 1]) |
| 92 | + } |
| 93 | + |
| 94 | + if (!is.na(memo[pos + 1, mask + 1])) { |
| 95 | + return(memo[pos + 1, mask + 1]) |
| 96 | + } |
| 97 | + |
| 98 | + min_cost <- Inf |
| 99 | + best_city <- -1 |
| 100 | + |
| 101 | + for (city in 0:(n - 1)) { |
| 102 | + if (bitwAnd(mask, bitwShiftL(1, city)) == 0) { |
| 103 | + new_mask <- bitwOr(mask, bitwShiftL(1, city)) |
| 104 | + cost <- dist[pos + 1, city + 1] + dp(new_mask, city) |
| 105 | + if (cost < min_cost) { |
| 106 | + min_cost <- cost |
| 107 | + best_city <- city |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + memo[pos + 1, mask + 1] <<- min_cost |
| 113 | + parent[pos + 1, mask + 1] <<- best_city |
| 114 | + return(min_cost) |
| 115 | + } |
| 116 | + |
| 117 | + # Get minimum cost |
| 118 | + min_cost <- dp(1, 0) |
| 119 | + |
| 120 | + # Reconstruct path |
| 121 | + path <- c(0) # Start at city 0 |
| 122 | + mask <- 1 |
| 123 | + pos <- 0 |
| 124 | + |
| 125 | + while (mask != ALL_VISITED) { |
| 126 | + next_city <- parent[pos + 1, mask + 1] |
| 127 | + path <- c(path, next_city) |
| 128 | + mask <- bitwOr(mask, bitwShiftL(1, next_city)) |
| 129 | + pos <- next_city |
| 130 | + } |
| 131 | + |
| 132 | + path <- c(path, 0) # Return to starting city |
| 133 | + |
| 134 | + return(list( |
| 135 | + min_cost = min_cost, |
| 136 | + path = path, |
| 137 | + path_cities = path + 1 # Convert to 1-indexed for display |
| 138 | + )) |
| 139 | +} |
| 140 | + |
| 141 | +# Helper function to print distance matrix |
| 142 | +print_distance_matrix <- function(dist) { |
| 143 | + #' Print a formatted distance matrix |
| 144 | + #' @param dist: Distance matrix to print |
| 145 | + |
| 146 | + n <- nrow(dist) |
| 147 | + cat("Distance Matrix:\n") |
| 148 | + cat(" ") |
| 149 | + for (j in 1:n) { |
| 150 | + cat(sprintf("C%-3d ", j)) |
| 151 | + } |
| 152 | + cat("\n") |
| 153 | + |
| 154 | + for (i in 1:n) { |
| 155 | + cat(sprintf("C%-3d ", i)) |
| 156 | + for (j in 1:n) { |
| 157 | + cat(sprintf("%-4d ", dist[i, j])) |
| 158 | + } |
| 159 | + cat("\n") |
| 160 | + } |
| 161 | + cat("\n") |
| 162 | +} |
| 163 | + |
| 164 | +# ========== Example Usage ========== |
| 165 | + |
| 166 | +# Example 1: Small 4-city problem |
| 167 | +cat("========== Example 1: 4 Cities ==========\n\n") |
| 168 | +dist_matrix_1 <- matrix(c( |
| 169 | + 0, 10, 15, 20, |
| 170 | + 10, 0, 35, 25, |
| 171 | + 15, 35, 0, 30, |
| 172 | + 20, 25, 30, 0 |
| 173 | +), nrow = 4, byrow = TRUE) |
| 174 | + |
| 175 | +print_distance_matrix(dist_matrix_1) |
| 176 | + |
| 177 | +min_cost_1 <- tsp_bitmask_dp(dist_matrix_1) |
| 178 | +cat(sprintf("Minimum cost to visit all cities: %d\n\n", min_cost_1)) |
| 179 | + |
| 180 | +# Get path as well |
| 181 | +result_1 <- tsp_bitmask_with_path(dist_matrix_1) |
| 182 | +cat(sprintf("Optimal path: %s\n", paste(result_1$path_cities, collapse = " -> "))) |
| 183 | +cat(sprintf("Total cost: %d\n\n", result_1$min_cost)) |
| 184 | + |
| 185 | +# Example 2: Another 4-city problem |
| 186 | +cat("========== Example 2: Another 4-City Problem ==========\n\n") |
| 187 | +dist_matrix_2 <- matrix(c( |
| 188 | + 0, 20, 42, 35, |
| 189 | + 20, 0, 30, 34, |
| 190 | + 42, 30, 0, 12, |
| 191 | + 35, 34, 12, 0 |
| 192 | +), nrow = 4, byrow = TRUE) |
| 193 | + |
| 194 | +print_distance_matrix(dist_matrix_2) |
| 195 | + |
| 196 | +result_2 <- tsp_bitmask_with_path(dist_matrix_2) |
| 197 | +cat(sprintf("Optimal path: %s\n", paste(result_2$path_cities, collapse = " -> "))) |
| 198 | +cat(sprintf("Total cost: %d\n\n", result_2$min_cost)) |
| 199 | + |
| 200 | +# Example 3: Small 5-city problem |
| 201 | +cat("========== Example 3: 5 Cities ==========\n\n") |
| 202 | +dist_matrix_3 <- matrix(c( |
| 203 | + 0, 12, 10, 19, 8, |
| 204 | + 12, 0, 3, 7, 6, |
| 205 | + 10, 3, 0, 2, 20, |
| 206 | + 19, 7, 2, 0, 4, |
| 207 | + 8, 6, 20, 4, 0 |
| 208 | +), nrow = 5, byrow = TRUE) |
| 209 | + |
| 210 | +print_distance_matrix(dist_matrix_3) |
| 211 | + |
| 212 | +result_3 <- tsp_bitmask_with_path(dist_matrix_3) |
| 213 | +cat(sprintf("Optimal path: %s\n", paste(result_3$path_cities, collapse = " -> "))) |
| 214 | +cat(sprintf("Total cost: %d\n\n", result_3$min_cost)) |
| 215 | + |
| 216 | +# Performance note |
| 217 | +cat("========== Performance Note ==========\n") |
| 218 | +cat("This algorithm works well for small n (typically n <= 20).\n") |
| 219 | +cat("For larger instances, consider:\n") |
| 220 | +cat(" - Heuristic approaches (Nearest Neighbor, 2-opt)\n") |
| 221 | +cat(" - Approximation algorithms (Christofides algorithm)\n") |
| 222 | +cat(" - Metaheuristics (Genetic Algorithms, Simulated Annealing)\n") |
0 commit comments