遗传算法求解TSP旅行商问题python代码实战
遗传算法求解TSP旅行商问题python代码实战
- 一、遗传算法简介
- 1.1智能优化算法
- 1.2遗传算法原理
- 二、TSP旅行商问题简介
- 2.1TSP描述
- 2.2求解算法
- 三、求解python代码
- 3.1共14个城市位置坐标
- 3.2求解代码
- 3.3代码说明
- 3.3.1代码整体说明
- 3.3.2代码注意事项
- 3.4求解结果展示
一、遗传算法简介
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求解代码
import random
import math
import sys
from tqdm import tqdm
import copy
import matplotlib.pyplot as plt
import mathdef 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)breakif code not in code_group:code_group.append(code)breakreturn code_groupdef calc_fitness(code_group, city_pos_list, distance_matrix):fitness_list = []dist_list = []for code in code_group:sum_dist = 0.0for i in range(-1, len(code) - 1):dist = distance_matrix[code[i]][code[i + 1]]sum_dist += distdist_list.append(round(sum_dist, 4))sum_dist = 1.0 / sum_distsum_dist = round(sum_dist, 4)fitness_list.append(sum_dist)return fitness_list, dist_listdef 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:breakif r1 > r2:r1, r2 = (r2, r1)code_len = len(new_group01)if r2 >= code_len:r2 = code_len - 1for 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_valtmp_val = new_group02[i]num = 0tmp_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 += 1if num > 10:print(tmp_list)print(tmp_indexlist)sys.exit(0)new_group02[i] = tmp_valfor 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_valtmp_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_valnew_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_group02def 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 - 1while True:r2 = int(random.random() * 10)if r2 >= code_len:r2 = code_len - 1if r2 != r1:breaknew_group03[r1], new_group03[r2] = (new_group03[r2], new_group03[r1])return new_group03def 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:breakif r1 > r2:r1, r2 = (r2, r1)if r2 >= code_len:r2 = code_len - 1tmp_list = new_group04[r1:r2]tmp_list.reverse()new_group04[r1:r2] = tmp_listreturn new_group04def 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 processfor _ in range(vary_num):index03 = int(random.random() * len(code_group))if index03 >= len(code_group):index03 = len(code_group) - 1new_group03 = code_group[index03]new_group03 = vary_func(new_group03)new_code_group.append(new_group03)#reverse processfor _ in range(reverse_num):index04 = int(random.random() * len(code_group))if index04 >= len(code_group):index04 = len(code_group) - 1new_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 = 0for i in range(len(cum_list)):if tmp_prob01 <= cum_list[i]:index01 = ibreakindex01 = fitness_list.index(sorted_fitness_list[k])new_group01 = code_group[index01]new_code_group.append(new_group01)return new_code_groupdef 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....')returnmin_distance = 100min_path = ''min_epoch = 0f = 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_minmin_path = tmp_min_pathmin_epoch = k + 1f.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:returnf.close()print('GA done')print(f"min_epoch is {min_epoch}, min_distance is {min_distance}, min_path is {min_path}")returnif __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 = 1000all_epoches = 200GA_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路线图如下:

