局部搜索概念
wikipedia:
局部搜索算法从一个候选解开始,反复探索其邻域,并移动到邻域中的最后解。所以要使用局部搜索算法必须首先在搜索空间中定义邻域。比如,在最小顶点覆盖问题中,一个覆盖的邻域可以是只改变一个顶点能够到达的另一种覆盖;在布尔可满足性问题中,一个变量赋值的邻域可以是只改变一个变量的值能到达的所有赋值。对于同一个问题可以有很多种邻域的定义。如果一个局部优化算法将邻域定义为只改变 k 个成分能够到达的解,那么这个算法可以称为 k-opt。
定义邻域
我们简单定义一个邻域,即随机交换路径中的两个城市的访问顺序,如下图所示:
反复迭代,直到达到设定的迭代次数
编码
我们使用python来实现这个简单的方法
计算距离矩阵
- 导入tsp文件
filepath = './china34.tsp'
with open(filepath, 'r') as file:
lines = file.readlines()
nodes = []
for i in range(len(lines)):
if i < 6:
continue
info = lines[i].split()
if info[0] == 'EOF':
break
coor = [float(info[1]), float(info[2])]
nodes.append(coor)
- 计算距离矩阵
D = np.radians(nodes)
city_cnt = len(nodes)
dist_mat = np.zeros((city_cnt, city_cnt))
for i in range(city_cnt):
for j in range(city_cnt):
if i == j:
# 相同城市不允许访问
dist_mat[i][j] = 0 # 修改为0表示同一个城市的距离为0
else:
# 使用 Haversine 公式计算距离
lat1, lon1 = D[i][1], D[i][0]
lat2, lon2 = D[j][1], D[j][0]
# Haversine 公式
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
distance = 6378.14 * c # 6378.14 是地球的半径,单位:km
dist_mat[i][j] = distance
- tsp文件
NAME: china34
TYPE: TSP
COMMENT: 34 locations in China
DIMENSION: 34
EDGE_WEIGHT_TYPE: WGS
NODE_COORD_SECTION
1 101.74 36.56
2 103.73 36.03
3 106.27 38.47
4 108.95 34.27
5 113.65 34.76
6 117.00 36.65
7 114.48 38.03
8 112.53 37.87
9 111.65 40.82
10 116.41 39.90
11 117.20 39.08
12 123.38 41.80
13 125.35 43.88
14 126.63 45.75
15 121.47 31.23
16 120.19 30.26
17 118.78 32.04
18 117.27 31.86
19 114.31 30.52
20 113.00 28.21
21 115.89 28.68
22 119.30 26.08
23 121.30 25.03
24 114.17 22.32
25 113.55 22.20
26 113.23 23.16
27 110.35 20.02
28 108.33 22.84
29 106.71 26.57
30 106.55 29.56
31 104.06 30.67
32 102.73 25.04
33 91.11 29.97
34 87.68 43.77
EOF
定义邻域方法
def two_swap(path):
cp = copy.deepcopy(path)
r = random.sample(list(range(len(cp))), 2)
cp[r[0]], cp[r[1]] = cp[r[1]], cp[r[0]]
return cp
邻域搜索
def local_search():
iterations = 10000
path0 = list(range(len(dist_mat)))
best = calcTSP(path0)
for i in range(iterations):
newpath = two_swap(path0)
if calcTSP(newpath) < best:
path0 = newpath
best = calcTSP(newpath)
print("best value:",best)
print("best path:", path0)
完整代码
import copy
import pandas as pd
import numpy as np
import math
import random
filepath = './china34.tsp'
with open(filepath, 'r') as file:
lines = file.readlines()
nodes = []
for i in range(len(lines)):
if i < 6:
continue
info = lines[i].split()
if info[0] == 'EOF':
break
coor = [float(info[1]), float(info[2])]
nodes.append(coor)
D = np.radians(nodes)
city_cnt = len(nodes)
dist_mat = np.zeros((city_cnt, city_cnt))
for i in range(city_cnt):
for j in range(city_cnt):
if i == j:
# 相同城市不允许访问
dist_mat[i][j] = 0 # 修改为0表示同一个城市的距离为0
else:
# 使用 Haversine 公式计算距离
lat1, lon1 = D[i][1], D[i][0]
lat2, lon2 = D[j][1], D[j][0]
# Haversine 公式
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
distance = 6378.14 * c # 6378.14 是地球的半径,单位:km
dist_mat[i][j] = distance
def local_search():
iterations = 10000
path0 = list(range(len(dist_mat)))
best = calcTSP(path0)
for i in range(iterations):
newpath = two_swap(path0)
if calcTSP(newpath) < best:
path0 = newpath
best = calcTSP(newpath)
print("best value:",best)
print("best path:", path0)
def two_swap(path):
cp = copy.deepcopy(path)
r = random.sample(list(range(len(cp))), 2)
cp[r[0]], cp[r[1]] = cp[r[1]], cp[r[0]]
return cp
def calcTSP(path):
sum = 0
for i in range(1, len(path)):
sum += dist_mat[path[i]][path[i-1]]
sum += dist_mat[path[0]][path[len(path)-1]]
return sum
local_search()
总结
简单的局部搜索会陷入局部最优,因此很多启发式算法针对局部搜索进行了优化,包括禁忌搜索(Tabu Search),模拟退火(SA),变邻域搜索(VNS),后面会针对这些方法做简单的介绍,并编码对比它们的效果。