可变邻域搜索(Variable Neighborhood Search, VNS)是一种常用于求解组合优化问题的元启发式算法。VNS的核心思想是通过系统地改变搜索空间的邻域来跳出局部最优,从而达到全局最优解。对于旅行商问题(TSP),VNS可以通过不同邻域结构的交替搜索来优化路线。下面是基于VNS求解TSP问题的基本步骤:
1. 初始化
- 随机生成一个可行的初始解 sss,比如使用最近邻算法(Nearest Neighbor)或随机生成一个旅行路线。
- 设定初始邻域结构集合(如交换、逆序、插入等多种邻域操作),以及最大迭代次数或其他终止条件。
2. 邻域定义
- 在TSP中,常用的邻域结构包括:
- 交换邻域:交换两座城市的位置。
- 逆序邻域:选择一个子路径,将其顺序反转。
- 插入邻域:将一个城市插入到另一位置。
3. 迭代搜索
VNS主要包含以下几个关键步骤:
-
基本邻域搜索:从当前解 sss 开始,在第一个邻域 kkk 中搜索得到一个候选解 s′s's′。如果 s′s's′ 比当前解 sss 优,则将其设为新的当前解,并重置 k=1k = 1k=1。否则, k=k+1k = k + 1k=k+1。
-
改变邻域:如果当前解 sss 没有改进,则在不同邻域中继续寻找候选解,这样可以跳出局部最优。通过系统地增加邻域大小,可以更全面地搜索解空间。
-
接受准则:当找到一个比当前解更优的解时,立即接受该解作为当前解并重新开始邻域搜索,重复这个过程直到达到终止条件。
4. 终止条件
- 迭代次数达到预设的最大值。
- 长时间没有解的改进。
- 邻域结构循环结束。
5.代码实现
import random
import math
import matplotlib.pyplot as plt
import time
import osdef calculate_total_distance(route, distance_matrix):"""计算路径的总距离"""total_distance = sum(distance_matrix[route[i]][route[i + 1]] for i in range(len(route) - 1))total_distance += distance_matrix[route[-1]][route[0]]return total_distancedef nearest_neighbor_solution(distance_matrix):"""使用最近邻算法生成初始解"""num_cities = len(distance_matrix)start = random.randint(0, num_cities - 1)unvisited = set(range(num_cities))route = [start]unvisited.remove(start)while unvisited:last = route[-1]next_city = min(unvisited, key=lambda city: distance_matrix[last][city])route.append(next_city)unvisited.remove(next_city)return routedef two_opt(route, distance_matrix):"""2-Opt 局部优化"""best_route = route[:]best_distance = calculate_total_distance(best_route, distance_matrix)for i in range(len(route) - 1):for j in range(i + 2, len(route)):if j - i == 1:continuenew_route = route[:i] + route[i:j][::-1] + route[j:]new_distance = calculate_total_distance(new_route, distance_matrix)if new_distance < best_distance:best_route, best_distance = new_route, new_distancereturn best_routedef swap(route):"""交换邻域操作"""i, j = random.sample(range(len(route)), 2)route[i], route[j] = route[j], route[i]return routedef reverse_subroute(route):"""反转邻域操作"""i, j = sorted(random.sample(range(len(route)), 2))route[i:j + 1] = reversed(route[i:j + 1])return routedef insert(route):"""插入邻域操作"""i, j = random.sample(range(len(route)), 2)city = route.pop(i)route.insert(j, city)return routedef VNS(distance_matrix, max_iter=500, seed=None):"""变量邻域搜索 (VNS) 算法主函数"""current_solution = nearest_neighbor_solution(distance_matrix)best_solution = current_solution[:]best_distance = calculate_total_distance(best_solution, distance_matrix)neighborhood_operations = [swap, reverse_subroute, insert]random.seed(seed)iteration = 0no_improve_count = 0adaptive_cooling = 1start_time = time.time() # 开始计时while iteration < max_iter:k = 0improved = Falsewhile k < len(neighborhood_operations):new_solution = neighborhood_operations[k](current_solution[:])new_solution = two_opt(new_solution, distance_matrix)new_distance = calculate_total_distance(new_solution, distance_matrix)if new_distance < best_distance:best_solution, best_distance = new_solution[:], new_distancecurrent_solution = new_solution[:]improved = Truek = 0no_improve_count = 0else:k += 1if not improved:no_improve_count += 1if no_improve_count > 10:adaptive_cooling = max(0.9, adaptive_cooling - 0.01)else:adaptive_cooling = 1no_improve_count = 0iteration += 1end_time = time.time() # 结束计时elapsed_time = end_time - start_timereturn best_solution, best_distance, elapsed_timedef read_tsp_file(filename):"""读取 TSP 文件并返回坐标字典"""coordinates = {}with open(filename, 'r') as f:lines = f.readlines()node_section = Falsefor line in lines:if line.strip() == "NODE_COORD_SECTION":node_section = Truecontinueelif line.strip() == "EOF":breakif node_section:parts = line.strip().split()node_id = int(parts[0])x, y = int(parts[1]), int(parts[2])coordinates[node_id] = (x, y)return coordinatesdef euclidean_distance(coord1, coord2):"""计算欧几里得距离"""return math.sqrt((coord1[0] - coord2[0]) ** 2 + (coord1[1] - coord2[1]) ** 2)def build_distance_matrix(coordinates):"""构建距离矩阵"""num_cities = len(coordinates)distance_matrix = [[0] * num_cities for _ in range(num_cities)]for i in range(num_cities):for j in range(i + 1, num_cities):dist = euclidean_distance(coordinates[i + 1], coordinates[j + 1])distance_matrix[i][j] = distdistance_matrix[j][i] = distreturn distance_matrixdef plot_path(city_coords, path, save_path=None):"""绘制路径并保存图像"""plt.figure(figsize=(20, 15))x = [city_coords[i + 1][0] for i in path] + [city_coords[path[0] + 1][0]]y = [city_coords[i + 1][1] for i in path] + [city_coords[path[0] + 1][1]]plt.plot(x, y, linestyle='--', linewidth=2, markersize=10, color='red')plt.plot(x, y, marker='o', markersize=10, linewidth=0, color='black')for i in path:if i == path[0]:plt.text(city_coords[i + 1][0], city_coords[i + 1][1], str(i + 1), fontsize=20, ha='right', color='red')else:plt.text(city_coords[i + 1][0], city_coords[i + 1][1], str(i + 1), fontsize=18, ha='right')plt.title('VNS_TSP Optimal Path', fontsize=20)plt.xlabel('X', fontsize=20)plt.ylabel('Y', fontsize=20)plt.xticks(fontsize=20)plt.yticks(fontsize=20)plt.grid(True)if save_path:plt.savefig(save_path)plt.close()def save_results(coords, best_solution, best_distance, elapsed_time, save_dir='results'):"""保存最优路径、最短距离、运算时间和图像"""os.makedirs(save_dir, exist_ok=True)with open(os.path.join(save_dir, 'best_path.txt'), 'w') as file:file.write("最优访问路径: " + ' '.join(map(lambda x: str(x + 1), best_solution)) + "\n")file.write(f"最短总距离: {best_distance}\n")file.write(f"运算时间: {elapsed_time:.4f} 秒\n")plot_path(coords, best_solution, os.path.join(save_dir, 'best_path.png'))# 测试代码
filename = 'kroB100.tsp'
coordinates = read_tsp_file(filename)
distance_matrix = build_distance_matrix(coordinates)# 运行 VNS 算法并保存结果
best_route, best_route_distance, elapsed_time = VNS(distance_matrix, max_iter=500, seed=42)
save_results(coordinates, best_route, best_route_distance, elapsed_time)