遗传算法求解TSP旅行商问题python代码实战
一、遗传算法简介
1.1智能优化算法
遗传算法属于智能优化算法(Intelligent Optimization Algorithms)的一种,智能优化算法也被称为进化计算(Evolutionary Computation, EC)、群体智能算法(Swarm Intelligence Algorithms),属于优化算法大类,但不同于数学优化中的梯度下降、牛顿法,不是精确解法,不追求理论的严谨,而是模拟自然界的过程来寻找较优解。
禁忌搜索(Tabu Search或Taboo Search)、蚁群优化(Ant Colony Optimization, ACO)、粒子群优化(Particle Swarm Optimization, PSO)都属于智能优化算法,常用于参数调优、组合优化(如旅行商问题 TSP)、工程设计优化、机器学习模型超参数搜索等。
1.2遗传算法原理
遗传算法(Genetic Algorithm, GA),在智能优化算法中使用非常广泛,算法知名度也很高,很多研究学者以遗传算法为基础进行算法的创新。
遗传算法是由美国密歇根大学的John H.Holland教授及其学生于20世纪60年代末到70年代初提出的,算法是模拟自然界中生物遗传物质DNA的遗传和变异,以及生物接受自然选择,适者生存的这一自然过程而借鉴提出。

算法的基本过程为:
- 在搜索空间内产生一个初始种群,种群中的每个个体都是待求解问题的一个解;
- 根据问题构造适值函数,该函数衡量个体的适应度,即解的优劣,适值函数越大说明解越优秀;
- 根据某种选择策略选择种群中的个体进行繁殖,产生下一代,繁殖的过程就包括基因互换和变异,基因即为解的某种编码方式;
- 若干次繁衍后,得到的种群中适值函数最大的个体即为该算法得出的最优解。
二、TSP旅行商问题简介
2.1TSP描述
TSP(Traveling Salesman Problem)旅行商问题,可简要描述为:求解走完若干个城市的最短路线,要求除出发城市外每个城市只经过一次,且最终回到出发的城市。

2.2求解算法
假设有n个城市,那么考虑到起点固定和方向对称共有(n-1)!/2种路线,目前没有已知的多项式时间算法可以求得最优解,属于NP问题,求解难度大。
常见的解法主要有以下几种:
| 类型 | 算法 | 特点 |
|---|---|---|
| 寻求最优解 | 动态规划 | 能求最优解,但时间复杂度指数级 |
| 寻求次优解 | 智能优化算法,如遗传算法、禁忌搜索算法等 | 求解速度快,但结果是局部最优解,可能不是全局最优解 |
三、求解python代码
3.1共14个城市位置坐标
本节以求解14个城市的TSP问题为例,14个城市坐标如下:

3.2求解代码
python
import random
import math
import sys
from tqdm import tqdm
import copy
import matplotlib.pyplot as plt
import math
def init_group_func(init_group_num, code_len):
code_group = []
for i in range(init_group_num):
while True:
code = []
for j in range(code_len):
while True:
code_item = int(random.random() * code_len)
if code_item not in code:
code.append(code_item)
break
if code not in code_group:
code_group.append(code)
break
return code_group
def calc_fitness(code_group, city_pos_list, distance_matrix):
fitness_list = []
dist_list = []
for code in code_group:
sum_dist = 0.0
for i in range(-1, len(code) - 1):
dist = distance_matrix[code[i]][code[i + 1]]
sum_dist += dist
dist_list.append(round(sum_dist, 4))
sum_dist = 1.0 / sum_dist
sum_dist = round(sum_dist, 4)
fitness_list.append(sum_dist)
return fitness_list, dist_list
def cross_func(new_group01, new_group02):
new_group01 = copy.deepcopy(new_group01)
new_group02 = copy.deepcopy(new_group02)
r1 = int(random.random() * 10)
while True:
r2 = int(random.random() * 10)
if r2 != r1:
break
if r1 > r2:
r1, r2 = (r2, r1)
code_len = len(new_group01)
if r2 >= code_len:
r2 = code_len - 1
for i in range(r1):
tmp_val = new_group01[i]
while tmp_val in new_group02[r1:r2]:
tmp_index = new_group02[r1:r2].index(tmp_val)
tmp_val = new_group01[tmp_index + r1]
new_group01[i] = tmp_val
tmp_val = new_group02[i]
num = 0
tmp_list = []
tmp_indexlist = []
while tmp_val in new_group01[r1:r2]:
tmp_index = new_group01[r1:r2].index(tmp_val)
tmp_val = new_group02[tmp_index + r1]
tmp_list.append(tmp_val)
tmp_indexlist.append(tmp_index)
num += 1
if num > 10:
print(tmp_list)
print(tmp_indexlist)
sys.exit(0)
new_group02[i] = tmp_val
for i in range(r2, code_len):
tmp_val = new_group01[i]
while tmp_val in new_group02[r1:r2]:
tmp_index = new_group02[r1:r2].index(tmp_val)
tmp_val = new_group01[tmp_index + r1]
new_group01[i] = tmp_val
tmp_val = new_group02[i]
while tmp_val in new_group01[r1:r2]:
tmp_index = new_group01[r1:r2].index(tmp_val)
tmp_val = new_group02[tmp_index + r1]
new_group02[i] = tmp_val
new_group01[r1:r2], new_group02[r1:r2] = (new_group02[r1:r2], new_group01[r1:r2])
if len(new_group01) != len(set(new_group01)) or len(new_group02) != len(set(new_group02)):
print('cross process error')
return []
return new_group01, new_group02
def vary_func(new_group03):
new_group03 = copy.deepcopy(new_group03)
code_len = len(new_group03)
r1 = int(random.random() * 10)
if r1 >= code_len:
r1 = code_len - 1
while True:
r2 = int(random.random() * 10)
if r2 >= code_len:
r2 = code_len - 1
if r2 != r1:
break
new_group03[r1], new_group03[r2] = (new_group03[r2], new_group03[r1])
return new_group03
def vary_reverse_func(new_group04):
new_group04 = copy.deepcopy(new_group04)
code_len = len(new_group04)
r1 = int(random.random() * 10)
while True:
r2 = int(random.random() * 10)
if r2 != r1:
break
if r1 > r2:
r1, r2 = (r2, r1)
if r2 >= code_len:
r2 = code_len - 1
tmp_list = new_group04[r1:r2]
tmp_list.reverse()
new_group04[r1:r2] = tmp_list
return new_group04
def cross_vary_group(code_group, cross_num, vary_num, reverse_num, fitness_list, cum_list):
new_code_group = []
sorted_fitness_list = sorted(fitness_list, reverse = True)
best_f_index = fitness_list.index(max(fitness_list))
av = sum(fitness_list) / len(fitness_list)
for k in range(cross_num):
if k != best_f_index and fitness_list[k] < av:
new_group01, _ = cross_func(code_group[best_f_index], code_group[k])
new_code_group.append(new_group01)
else:
new_code_group.append(code_group[k])
#vary process
for _ in range(vary_num):
index03 = int(random.random() * len(code_group))
if index03 >= len(code_group):
index03 = len(code_group) - 1
new_group03 = code_group[index03]
new_group03 = vary_func(new_group03)
new_code_group.append(new_group03)
#reverse process
for _ in range(reverse_num):
index04 = int(random.random() * len(code_group))
if index04 >= len(code_group):
index04 = len(code_group) - 1
new_group04 = code_group[index04]
new_group04 = vary_reverse_func(new_group04)
new_code_group.append(new_group04)
for k in range(len(code_group) - len(new_code_group)):
tmp_prob01 = random.random()
index01 = 0
for i in range(len(cum_list)):
if tmp_prob01 <= cum_list[i]:
index01 = i
break
index01 = fitness_list.index(sorted_fitness_list[k])
new_group01 = code_group[index01]
new_code_group.append(new_group01)
return new_code_group
def GA_TSP(city_pos_list, distance_matrix, init_group_num, all_epoches, pc = 0.9, pm = 0.05, pr = 0.01, max_reult = None):
code_group = init_group_func(init_group_num, len(city_pos_list))
init_group_str = []
for code in code_group:
tmp_str = '-'.join([str(x) for x in code])
init_group_str.append(tmp_str)
if (len(init_group_str) != len(set(init_group_str))):
print('init group error....')
return
min_distance = 100
min_path = ''
min_epoch = 0
f = open('Rou_select_log.txt', 'w')
for k in tqdm(range(all_epoches)):
fitness_list, dist_list = calc_fitness(code_group, city_pos_list, distance_matrix)
mean_fitness = round(sum(fitness_list) / len(fitness_list), 6)
tmp_min = min(dist_list)
tmp_min_path = '-'.join([str(x) for x in code_group[fitness_list.index(max(fitness_list))]])
if tmp_min < min_distance:
min_distance = tmp_min
min_path = tmp_min_path
min_epoch = k + 1
f.write(str(mean_fitness))
f.write('\n')
sum_fitness = sum(fitness_list)
prob_list = [round(x / sum_fitness, 6) for x in fitness_list]
cum_list = []
cum_list.append(0.0)
for i in range(len(prob_list)):
cum_val = round(prob_list[i] + cum_list[-1], 6)
cum_list.append(cum_val)
cum_list.pop(0)
if (all_epoches + 1) % 10 == 0:
if (all_epoches + 1) % 20 == 0:
code_group = cross_vary_group(code_group, int(len(code_group) * pc), int(len(code_group) * pm), int(len(code_group) * pr), fitness_list, cum_list)
else:
code_group = cross_vary_group(code_group, int(len(code_group) * pc), int(len(code_group) * pm), 0, fitness_list, cum_list)
else:
code_group = cross_vary_group(code_group, int(len(code_group) * pc), 0, 0, fitness_list, cum_list)
if len(code_group) == 0:
return
f.close()
print('GA done')
print(f"min_epoch is {min_epoch}, min_distance is {min_distance}, min_path is {min_path}")
return
if __name__ == '__main__':
city_pos_list = [(16.47, 96.10), (16.47, 94.44), (20.09, 92.54), (22.39, 93.37), (25.23, 97.24), (22.00, 96.05), (20.47, 97.02),
(17.20, 96.29), (16.30, 97.38), (14.05, 98.12), (16.53, 97.38), (21.52, 95.59), (19.41, 97.13), (20.09, 92.55)]
distance_matrix = [
[0.0, 1.66, 5.0772, 6.5191, 8.8339, 5.5302, 4.1044, 0.7543, 1.2912, 3.1523, 1.2814, 5.0757, 3.1152, 5.0702],
[1.66, 0.0, 4.0883, 6.0159, 9.1966, 5.7596, 4.7599, 1.9888, 2.9449, 4.4044, 2.9406, 5.1793, 3.9849, 4.0837],
[5.0772, 4.0883, 0.0, 2.4452, 6.9649, 3.996, 4.4961, 4.7344, 6.1473, 8.223, 6.0083, 3.3686, 4.6401, 0.01],
[6.5191, 6.0159, 2.4452, 0.0, 4.8003, 2.7082, 4.1242, 5.955, 7.2917, 9.5978, 7.1007, 2.3844, 4.7977, 2.4418],
[8.8339, 9.1966, 6.9649, 4.8003, 0.0, 3.4422, 4.7651, 8.086, 8.9311, 11.2146, 8.7011, 4.0604, 5.821, 6.9581],
[5.5302, 5.7596, 3.996, 2.7082, 3.4422, 0.0, 1.8116, 4.806, 5.8531, 8.2151, 5.6294, 0.6648, 2.8062, 3.9872],
[4.1044, 4.7599, 4.4961, 4.1242, 4.7651, 1.8116, 0.0, 3.3505, 4.1855, 6.5136, 3.9564, 1.7741, 1.0657, 4.4861],
[0.7543, 1.9888, 4.7344, 5.955, 8.086, 4.806, 3.3505, 0.0, 1.4135, 3.643, 1.2795, 4.3763, 2.3643, 4.7265],
[1.2912, 2.9449, 6.1473, 7.2917, 8.9311, 5.8531, 4.1855, 1.4135, 0.0, 2.3686, 0.23, 5.5184, 3.12, 6.1395],
[3.1523, 4.4044, 8.223, 9.5978, 11.2146, 8.2151, 6.5136, 3.643, 2.3686, 0.0, 2.588, 7.8868, 5.4507, 8.2162],
[1.2814, 2.9406, 6.0083, 7.1007, 8.7011, 5.6294, 3.9564, 1.2795, 0.23, 2.588, 0.0, 5.3013, 2.8908, 6.0002],
[5.0757, 5.1793, 3.3686, 2.3844, 4.0604, 0.6648, 1.7741, 4.3763, 5.5184, 7.8868, 5.3013, 0.0, 2.6122, 3.3595],
[3.1152, 3.9849, 4.6401, 4.7977, 5.821, 2.8062, 1.0657, 2.3643, 3.12, 5.4507, 2.8908, 2.6122, 0.0, 4.6302],
[5.0702, 4.0837, 0.01, 2.4418, 6.9581, 3.9872, 4.4861, 4.7265, 6.1395, 8.2162, 6.0002, 3.3595, 4.6302, 0.0]
]
init_group_num = 1000
all_epoches = 200
GA_TSP(city_pos_list, distance_matrix, init_group_num, all_epoches)
3.3代码说明
3.3.1代码整体说明
- 在主函数中定义了城市位置坐标,以及距离矩阵,该矩阵是通过代码计算得出,这里直接列在程序中,读者可进行验证;
- 定义了遗传算法的初始种群数为1000,迭代轮数为200,即遗传代数,算法主体通过GA_TSP函数实现;
- GA_TSP函数,首先通过init_group_func函数生成初始化种群,然后进行循环,每次循环中依次计算适值函数,选择其中最小距离值为此次最优解并记录,再通过cross_vary_group函数进行交叉变异后,进行下次循环;
- cross_vary_group函数功能是对种群进行交叉变异后产生新一代种群,但是并不是所有个体都进行交叉,而是采用某种选择策略,本代码中使用了精英保留策略进行交叉,同时设置一定的比例进行变异。
3.3.2代码注意事项
- 本代码中使用了精英保留策略进行选择要交叉的个体,同时还有其他策略,例如旋轮法、锦标赛法等,具体效果读者可进行对比;
- 由于使用随机方法初始化种群,因此每次代码执行结果不同是正常的。
3.4求解结果展示
代码得出的最短路线距离为30.2471,TSP路线图如下:
