Traveling Salesman Bitmask
S
# Traveling Salesman Problem (TSP) using Bitmask Dynamic Programming
#
# The Traveling Salesman Problem finds the shortest possible route that visits
# each city exactly once and returns to the starting city. This implementation
# uses bitmask DP to efficiently track visited cities.
#
# Time Complexity: O(n² * 2^n) where n = number of cities
# Space Complexity: O(n * 2^n) for memoization table
#
# Applications:
# - Route optimization and logistics
# - Circuit board drilling and manufacturing
# - DNA sequencing and genome mapping
# - Network design and optimization
# - Scheduling and planning problems
# - Microchip design and fabrication
# Main TSP function using bitmask DP
tsp_bitmask_dp <- function(dist) {
#' Solve the Traveling Salesman Problem using Bitmask Dynamic Programming
#' @param dist: 2D matrix where dist[i, j] is the distance from city i to city j
#' @return: Minimum cost to visit all cities and return to starting city
n <- nrow(dist)
# Bitmask when all cities are visited
ALL_VISITED <- bitwShiftL(1, n) - 1
# Initialize memoization table
# memo[pos, mask] = minimum cost starting from pos with visited cities in mask
memo <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n))
# Recursive DP helper function
dp <- function(mask, pos) {
#' Recursive DP function to compute minimum travel cost
#' @param mask: Bitmask representing visited cities
#' @param pos: Current city position (0-indexed)
#' @return: Minimum travel cost from current state
# Base case: all cities visited, return to starting city (city 0)
if (mask == ALL_VISITED) {
return(dist[pos + 1, 1])
}
# Check memoization table
if (!is.na(memo[pos + 1, mask + 1])) {
return(memo[pos + 1, mask + 1])
}
# Initialize minimum cost as infinity
min_cost <- Inf
# Try visiting each unvisited city
for (city in 0:(n - 1)) {
# Check if city is not visited (bit is 0)
if (bitwAnd(mask, bitwShiftL(1, city)) == 0) {
# Mark city as visited
new_mask <- bitwOr(mask, bitwShiftL(1, city))
# Calculate cost: distance to city + cost from city
cost <- dist[pos + 1, city + 1] + dp(new_mask, city)
min_cost <- min(min_cost, cost)
}
}
# Store result in memo table
memo[pos + 1, mask + 1] <<- min_cost
return(min_cost)
}
# Start from city 0 with only city 0 visited (mask = 1)
result <- dp(1, 0)
return(result)
}
# Function to get the optimal path (with path reconstruction)
tsp_bitmask_with_path <- function(dist) {
#' Solve TSP and return both minimum cost and the optimal path
#' @param dist: 2D distance matrix
#' @return: List containing minimum cost and optimal path
n <- nrow(dist)
ALL_VISITED <- bitwShiftL(1, n) - 1
# Memoization tables
memo <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n))
parent <- matrix(NA, nrow = n, ncol = bitwShiftL(1, n))
# DP function with path tracking
dp <- function(mask, pos) {
if (mask == ALL_VISITED) {
return(dist[pos + 1, 1])
}
if (!is.na(memo[pos + 1, mask + 1])) {
return(memo[pos + 1, mask + 1])
}
min_cost <- Inf
best_city <- -1
for (city in 0:(n - 1)) {
if (bitwAnd(mask, bitwShiftL(1, city)) == 0) {
new_mask <- bitwOr(mask, bitwShiftL(1, city))
cost <- dist[pos + 1, city + 1] + dp(new_mask, city)
if (cost < min_cost) {
min_cost <- cost
best_city <- city
}
}
}
memo[pos + 1, mask + 1] <<- min_cost
parent[pos + 1, mask + 1] <<- best_city
return(min_cost)
}
# Get minimum cost
min_cost <- dp(1, 0)
# Reconstruct path
path <- c(0) # Start at city 0
mask <- 1
pos <- 0
while (mask != ALL_VISITED) {
next_city <- parent[pos + 1, mask + 1]
path <- c(path, next_city)
mask <- bitwOr(mask, bitwShiftL(1, next_city))
pos <- next_city
}
path <- c(path, 0) # Return to starting city
return(list(
min_cost = min_cost,
path = path,
path_cities = path + 1 # Convert to 1-indexed for display
))
}
# Helper function to print distance matrix
print_distance_matrix <- function(dist) {
#' Print a formatted distance matrix
#' @param dist: Distance matrix to print
n <- nrow(dist)
cat("Distance Matrix:\n")
cat(" ")
for (j in 1:n) {
cat(sprintf("C%-3d ", j))
}
cat("\n")
for (i in 1:n) {
cat(sprintf("C%-3d ", i))
for (j in 1:n) {
cat(sprintf("%-4d ", dist[i, j]))
}
cat("\n")
}
cat("\n")
}
# ========== Example Usage ==========
# Example 1: Small 4-city problem
cat("========== Example 1: 4 Cities ==========\n\n")
dist_matrix_1 <- matrix(c(
0, 10, 15, 20,
10, 0, 35, 25,
15, 35, 0, 30,
20, 25, 30, 0
), nrow = 4, byrow = TRUE)
print_distance_matrix(dist_matrix_1)
min_cost_1 <- tsp_bitmask_dp(dist_matrix_1)
cat(sprintf("Minimum cost to visit all cities: %d\n\n", min_cost_1))
# Get path as well
result_1 <- tsp_bitmask_with_path(dist_matrix_1)
cat(sprintf("Optimal path: %s\n", paste(result_1$path_cities, collapse = " -> ")))
cat(sprintf("Total cost: %d\n\n", result_1$min_cost))
# Example 2: Another 4-city problem
cat("========== Example 2: Another 4-City Problem ==========\n\n")
dist_matrix_2 <- matrix(c(
0, 20, 42, 35,
20, 0, 30, 34,
42, 30, 0, 12,
35, 34, 12, 0
), nrow = 4, byrow = TRUE)
print_distance_matrix(dist_matrix_2)
result_2 <- tsp_bitmask_with_path(dist_matrix_2)
cat(sprintf("Optimal path: %s\n", paste(result_2$path_cities, collapse = " -> ")))
cat(sprintf("Total cost: %d\n\n", result_2$min_cost))
# Example 3: Small 5-city problem
cat("========== Example 3: 5 Cities ==========\n\n")
dist_matrix_3 <- matrix(c(
0, 12, 10, 19, 8,
12, 0, 3, 7, 6,
10, 3, 0, 2, 20,
19, 7, 2, 0, 4,
8, 6, 20, 4, 0
), nrow = 5, byrow = TRUE)
print_distance_matrix(dist_matrix_3)
result_3 <- tsp_bitmask_with_path(dist_matrix_3)
cat(sprintf("Optimal path: %s\n", paste(result_3$path_cities, collapse = " -> ")))
cat(sprintf("Total cost: %d\n\n", result_3$min_cost))
# Performance note
cat("========== Performance Note ==========\n")
cat("This algorithm works well for small n (typically n <= 20).\n")
cat("For larger instances, consider:\n")
cat(" - Heuristic approaches (Nearest Neighbor, 2-opt)\n")
cat(" - Approximation algorithms (Christofides algorithm)\n")
cat(" - Metaheuristics (Genetic Algorithms, Simulated Annealing)\n")