Travelling Salesman Problem: Visit Each City Once In The Shortest Route and Finish Where You Started.

pacman::p_load(pacman,geosphere,GA)

# Read the CSV file correctly
cities <- read.csv("UK_Cities.csv", header = TRUE, sep = ",")

# Split the City, Latitude, Longitude, and Population into separate columns
cities_split <- do.call(rbind, strsplit(as.character(cities$City.Latitude.Longitude.Population), ","))
cities <- data.frame(
  City = cities_split[, 1],
  Latitude = as.numeric(cities_split[, 2]),
  Longitude = as.numeric(cities_split[, 3]),
  Population = as.numeric(cities_split[, 4])
)

# Calculate the distance matrix
distance_matrix <- distm(cities[, c("Longitude", "Latitude")])

# Nearest Neighbour Heuristic
nearest_neighbour_tsp <- function(distance_matrix, start_city) {
  visited <- rep(FALSE, nrow(cities))
  route <- integer(nrow(cities))
  current_city <- start_city
  route[1] <- current_city
  visited[current_city] <- TRUE
  
  for (i in 2:nrow(cities)) {
    distances <- distance_matrix[current_city, ]
    distances[visited] <- Inf
    next_city <- which.min(distances)
    route[i] <- next_city
    visited[next_city] <- TRUE
    current_city <- next_city
  }
  
  # Return to start
  route <- c(route, route[1])
  total_distance <- sum(sapply(1:(length(route) - 1), function(i) {
    distance_matrix[route[i], route[i + 1]]
  }))
  
  return(list(route = route, total_distance = total_distance))
}

# Genetic Algorithm with a specified starting point
genetic_algorithm_tsp <- function(distance_matrix, start_city) {
  tsp_fitness <- function(route) {
    total_distance <- sum(sapply(1:(length(route) - 1), function(i) {
      distance_matrix[route[i], route[i + 1]]
    })) + distance_matrix[route[length(route)], route[1]] # return to start
    return(-total_distance)
  }
  
  cities_indices <- setdiff(1:nrow(cities), start_city)
  ga_tsp <- ga(
    type = "permutation",
    fitness = function(route) tsp_fitness(c(start_city, cities_indices[route])),
    lower = 1,
    upper = nrow(cities) - 1,
    popSize = 50,
    maxiter = 500,
    run = 100
  )
  
  best_route <- c(start_city, cities_indices[ga_tsp@solution[1, ]], start_city)
  best_distance <- -ga_tsp@fitnessValue
  
  return(list(route = best_route, total_distance = best_distance))
}

# Run NN and GA from each possible starting point and compare
shortest_nn_route <- NULL
shortest_nn_distance <- Inf

shortest_ga_route <- NULL
shortest_ga_distance <- Inf

for (i in 1:nrow(cities)) {
  # Run Nearest Neighbour
  nn_result <- nearest_neighbour_tsp(distance_matrix, i)
  if (nn_result$total_distance < shortest_nn_distance) {
    shortest_nn_distance <- nn_result$total_distance
    shortest_nn_route <- nn_result$route
  }
  
  # Run Genetic Algorithm
  ga_result <- genetic_algorithm_tsp(distance_matrix, i)
  if (ga_result$total_distance < shortest_ga_distance) {
    shortest_ga_distance <- ga_result$total_distance
    shortest_ga_route <- ga_result$route
  }
}

# Convert distances to kilometres and miles
shortest_nn_distance_km <- shortest_nn_distance / 1000
shortest_nn_distance_miles <- shortest_nn_distance / 1609.34

shortest_ga_distance_km <- shortest_ga_distance / 1000
shortest_ga_distance_miles <- shortest_ga_distance / 1609.34

# Print NN results
print(paste("Nearest Neighbour Shortest Route:", paste(cities$City[shortest_nn_route], collapse = " -> ")))
## [1] "Nearest Neighbour Shortest Route: Leicester -> Nottingham -> Derby -> Stoke-on-Trent -> Stockport -> Manchester -> Oldham -> Huddersfield -> Bradford -> Leeds -> Barnsley -> Sheffield -> York -> Hull -> Middlesbrough -> Sunderland -> Newcastle upon Tyne -> Edinburgh -> Glasgow -> Aberdeen -> Blackpool -> Southport -> Liverpool -> St Helens -> Warrington -> Wolverhampton -> Birmingham -> Coventry -> Northampton -> Milton Keynes -> Luton -> London -> Woking -> Reading -> Oxford -> Swindon -> Bristol -> Cardiff -> Exeter -> Plymouth -> Southampton -> Portsmouth -> Brighton -> Crawley -> Southend-on-Sea -> Ipswich -> Norwich -> Cambridge -> Peterborough -> Leicester"
print(paste("Nearest Neighbour Shortest Total Distance (metres):", shortest_nn_distance))
## [1] "Nearest Neighbour Shortest Total Distance (metres): 2838406.37621598"
print(paste("Nearest Neighbour Shortest Total Distance (km):", shortest_nn_distance_km))
## [1] "Nearest Neighbour Shortest Total Distance (km): 2838.40637621598"
print(paste("Nearest Neighbour Shortest Total Distance (miles):", shortest_nn_distance_miles))
## [1] "Nearest Neighbour Shortest Total Distance (miles): 1763.70833771359"
# Print GA results
print(paste("Genetic Algorithm Shortest Route:", paste(cities$City[shortest_ga_route], collapse = " -> ")))
## [1] "Genetic Algorithm Shortest Route: York -> Warrington -> St Helens -> Leicester -> Reading -> Exeter -> Plymouth -> Crawley -> Luton -> London -> Woking -> Swindon -> Bristol -> Cardiff -> Birmingham -> Oxford -> Milton Keynes -> Brighton -> Ipswich -> Southend-on-Sea -> Norwich -> Cambridge -> Peterborough -> Nottingham -> Derby -> Northampton -> Southampton -> Portsmouth -> Coventry -> Manchester -> Stockport -> Leeds -> Aberdeen -> Edinburgh -> Newcastle upon Tyne -> Bradford -> Glasgow -> Blackpool -> Oldham -> Southport -> Liverpool -> Wolverhampton -> Stoke-on-Trent -> Hull -> Huddersfield -> Sheffield -> Barnsley -> Middlesbrough -> Sunderland -> York"
print(paste("Genetic Algorithm Shortest Total Distance (metres):", shortest_ga_distance))
## [1] "Genetic Algorithm Shortest Total Distance (metres): 5159987.52252063"
print(paste("Genetic Algorithm Shortest Total Distance (km):", shortest_ga_distance_km))
## [1] "Genetic Algorithm Shortest Total Distance (km): 5159.98752252063"
print(paste("Genetic Algorithm Shortest Total Distance (miles):", shortest_ga_distance_miles))
## [1] "Genetic Algorithm Shortest Total Distance (miles): 3206.2755679475"

Code Explaination

knitr::opts_chunk$set(echo = TRUE): This configuration is identical to the previous version, ensuring that R code chunks are displayed in the rendered document.

pacman::p_load(…): This line loads the pacman, geosphere (for distance calculations), and GA (for the genetic algorithm) packages.

Reading the CSV data, splitting the combined column into separate ones, and calculating the distance matrix.

Enhanced Nearest Neighbour Algorithm.

This function is enhanced to take a start_city argument. It works similarly to the original version, but it begins the route from the specified start_city instead of always starting from the first city in the list.

Enhanced Genetic Algorithm.

This function also takes a start_city argument. The genetic algorithm now includes this specified starting city in every route it considers. The cities_indices line creates a vector of city indices excluding the start_city, which is used for generating permutations in the GA. The best route is then constructed by adding the start_city at the beginning and end.

Finding the Shortest Routes.

This section iterates over all cities (1:nrow(cities)). For each city, it:

Runs the Nearest Neighbour algorithm starting from that city.

If the resulting route is shorter than the previously found shortest NN route, it updates shortest_nn_route and shortest_nn_distance.

Does the same for the Genetic Algorithm.

By trying all possible starting points, it increases the chances of finding a better solution.

The final section converts the shortest distances found by NN and GA to kilometres and miles.

It then prints the shortest routes found by both algorithms, along with their distances in different units.