Skip to content

Commit 35936ea

Browse files
authored
Add traveling Salesman algorithm (#244)
1 parent ac2f0c8 commit 35936ea

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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

Comments
 (0)