遗传算法求解TSP旅行商问题python代码实战

遗传算法求解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的遗传和变异,以及生物接受自然选择,适者生存的这一自然过程而借鉴提出。

算法的基本过程为:

  1. 在搜索空间内产生一个初始种群,种群中的每个个体都是待求解问题的一个解;
  2. 根据问题构造适值函数,该函数衡量个体的适应度,即解的优劣,适值函数越大说明解越优秀;
  3. 根据某种选择策略选择种群中的个体进行繁殖,产生下一代,繁殖的过程就包括基因互换和变异,基因即为解的某种编码方式;
  4. 若干次繁衍后,得到的种群中适值函数最大的个体即为该算法得出的最优解。

二、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代码整体说明

  1. 在主函数中定义了城市位置坐标,以及距离矩阵,该矩阵是通过代码计算得出,这里直接列在程序中,读者可进行验证;
  2. 定义了遗传算法的初始种群数为1000,迭代轮数为200,即遗传代数,算法主体通过GA_TSP函数实现;
  3. GA_TSP函数,首先通过init_group_func函数生成初始化种群,然后进行循环,每次循环中依次计算适值函数,选择其中最小距离值为此次最优解并记录,再通过cross_vary_group函数进行交叉变异后,进行下次循环;
  4. cross_vary_group函数功能是对种群进行交叉变异后产生新一代种群,但是并不是所有个体都进行交叉,而是采用某种选择策略,本代码中使用了精英保留策略进行交叉,同时设置一定的比例进行变异。

3.3.2代码注意事项

  1. 本代码中使用了精英保留策略进行选择要交叉的个体,同时还有其他策略,例如旋轮法、锦标赛法等,具体效果读者可进行对比;
  2. 由于使用随机方法初始化种群,因此每次代码执行结果不同是正常的。

3.4求解结果展示

代码得出的最短路线距离为30.2471,TSP路线图如下:

相关推荐
Nina_7172 小时前
pytorch核心组件以及流程
人工智能·pytorch·python
FMRbpm2 小时前
链表5--------删除
数据结构·c++·算法·链表·新手入门
程序员buddha2 小时前
C语言操作符详解
java·c语言·算法
Highcharts.js2 小时前
在Python中配置高度交互的数据可视化:Highcharts完全指南
开发语言·python·信息可视化·highcharts
John_Rey2 小时前
API 设计哲学:构建健壮、易用且符合惯用语的 Rust 库
网络·算法·rust
愿没error的x2 小时前
动态规划、贪心算法与分治算法:深入解析与比较
算法·贪心算法·动态规划
Ace_31750887762 小时前
京东关键字搜索接口逆向:从动态签名破解到分布式请求调度
分布式·python
NONE-C3 小时前
动手学强化学习 第6章 Dyna-Q 算法
算法
yachuan_qiao3 小时前
专业的建筑设备监控管理系统选哪家
大数据·运维·python