编制旅行商路径优化问题的遗传算法程序

通过python语言,按照以下过程一步步实现遗传算法,测试用的数据为十个城市的数据集。
在这里插入图片描述

#把城市按照0到9进行编号,十个城市之间的距离矩阵如下:
distance=np.array([[0,100,120,130,140,150,160,170,180,190],
                   [100,0,200,210,220,230,240,250,260,270],
                   [120,200,0,300,310,320,330,340,350,360],
                   [130,210,300,0,400,410,420,430,440,450],
                   [140,220,310,400,0,500,510,520,530,540],
                   [150,230,320,410,500,0,600,610,620,630],
                   [160,240,330,420,510,600,0,700,710,720],
                   [170,250,340,430,520,610,700,0,800,810],
                   [180,260,350,440,530,620,710,800,0,900],
                   [190,270,360,450,540,630,720,810,900,0]])

为了探究算法的性能如何,我设置了不同迭代次数,每运行五次,并把数据记录下来生成散点图。
不设概率(种群规模为60,交叉概率为0.9,变异概率为0.005)的情况下得到的收敛图像如图1所示,横坐标是迭代次数,纵坐标是求出的最短路径的长度,可以看到随着迭代次数的增加,不断收敛,也可以看出最优解就是3030,但是收敛过程中并不稳定,会出现小幅度波动,当迭代次数为200及以上时波动幅度明显减小,这时算法是可以满足求解十城市TSP问题的需求的,因为可以将迭代次数设置在200-300之间(因为从图中可以看出这个范围内的收敛情况比较稳定),然后运行若干次(比如5次),得到若干个结果,然后从这些结果中选出最小的结果,便是最优解。
最终求得的最优路径有(9. 1. 2. 4. 3. 7. 6. 0. 5. 8. 9)(8. 1. 4. 2. 7. 0. 3. 5. 6. 9. 8)(8. 3. 7. 1. 9. 6. 2. 0. 4. 5. 8)
(9. 6. 7. 2. 5. 0. 1. 3. 4. 8. 9)(9. 2. 7. 0. 1. 6. 4. 5. 3. 8. 9)(9. 3. 7. 0. 6. 2. 4. 1. 5. 8. 9)(4. 8. 1. 2. 7. 5. 3. 0. 6. 9. 4)
(5. 3. 6. 2. 4. 0. 8. 7. 1. 9. 5)(7. 0. 6. 1. 3. 5. 2. 9. 4. 8. 7)(9. 3. 6. 8. 5. 1. 2. 4. 0. 7. 9)(4. 2. 9. 1. 6. 0. 7. 5. 3. 8. 4)
(9. 2. 7. 1. 5. 0. 6. 3. 4. 8. 9)(8. 2. 4. 3. 6. 7. 0. 5. 1. 9. 8)(5. 4. 0. 9. 1. 7. 3. 6. 2. 8. 5)(2. 9. 7. 3. 6. 0. 1. 4. 8. 5. 2)
(9. 2. 7. 3. 5. 1. 4. 6. 0. 8. 9)(5. 6. 3. 4. 7. 8. 2. 1. 0. 9. 5)路径长度均为3030
接下来我们把图1的数据作为参照组,保持这一组的参数不变,采用控制变量的方法每次仅改变一个参数来做对照。
图一
控制种群规模和变异概率不变,改变交叉操作的概率(种群规模为60,交叉操作的概率为0.6,变异的概率0.005)的情况下得到的收敛图像如图2,可以看到,与图1相比,图2收敛的速度较慢,在迭代次数为100-300的范围内的收敛状况几乎没什么变化,结合图1数据来看,可以认为交叉操作有效地保留和产生了更加优良的基因,对种群总体实现了优化。
图二
控制种群规模和交叉概率不变,仅改变变异的概率(种群规模为60,交叉概率为0.9,变异概率为0.001),得到图像如图3,与图1作比较可以发现图3体现出来的收敛速度更慢,并且发生收敛的区域也更宽,不易得到最优解,可以认为变异操作在群体的适应值趋于一致时促使某些个体发生变化,同时防止了适应值大的个体发生突变,从而避免进化停止,在非最优解的附近收敛。
图三
改变种群规模,保持交叉和变异的概率不变(种群规模为100,交叉概率为0.9,变异概率为0.005)时的图像如下图所示,与图1比较,图4在迭代早期求得的解更快地趋向了最优解(图4早期的数据分布比图1更加密集,所得最优路径的长度也更短),在这之后的收敛情况则与图1相似(图1和图4在后期的分布密集程度相似),说明更大规模的种群更易于得到优秀的个体,但是由于种群规模更大了,运算耗费的时间也更多。图四
受到计算机算力的限制(迭代次数为300是需要用时90秒),我不便进行更大规模的运算,比如增加迭代次数、尝试不同规模的初始种群、探索不同变异概率(或交叉概率)下的收敛情况或是将不同的迭代次数、初始种群规模、变异概率、交叉概率进行排列组合来寻找最佳的模型参数,未来可以进行这方面的探索;另外,除了python以外,Java、C以及其他编程语言也可以用来实现这一算法,可以尝试不同的语言,体会哪种语言能更高效地实现算法。最后,自己对遗传算法的理解还存在不足的地方,此文章中如有不准确的地方感谢朋友们指正~
👇这里放上全部代码:

import random
import math
import itertools
import numpy as np

distance=np.array([[0,100,120,130,140,150,160,170,180,190],
                   [100,0,200,210,220,230,240,250,260,270],
                   [120,200,0,300,310,320,330,340,350,360],
                   [130,210,300,0,400,410,420,430,440,450],
                   [140,220,310,400,0,500,510,520,530,540],
                   [150,230,320,410,500,0,600,610,620,630],
                   [160,240,330,420,510,600,0,700,710,720],
                   [170,250,340,430,520,610,700,0,800,810],
                   [180,260,350,440,530,620,710,800,0,900],
                   [190,270,360,450,540,630,720,810,900,0]])

'''
@funtion:
calcu_distance(route)函数用于计算某个个体的适应值,即路径总距离对数的倒数,route实际上为一个个体的基因组
'''
def calcu_distance(route):
    total_distance = 0
    for i in range(len(route)-1):
        row=int(route[i])
        column=int(route[i+1])
        total_distance = distance[row][column]+total_distance
    total_distance = total_distance + distance[9][0]   
    return 1/math.log(total_distance,10)

'''
@function:
calcu_distance_value(route)函数用于计算某个路径的总距离
'''
def calcu_distance_value(route):
    total_distance = 0
    for i in range(len(route)-1):
        row=int(route[i])
        column=int(route[i+1])
        total_distance = distance[row][column]+total_distance
    total_distance = total_distance + distance[9][0]
    return total_distance

'''
@function:
calcu_group_distance(population)函数用于返回群体中每个个体适应值组成的数组,population是一个群体
'''
def calcu_group_distance(population):
    group_size = len(population)
    group_dist = np.zeros(group_size)
    for i in range(group_size):
        group_dist[i] = calcu_distance(population[i])
    return group_dist


'''
@function:
calcu_group_distance_value(population)函数用于计算路径集合中每条路径的总距离
'''
def calcu_group_distance_value(population):
    group_size = len(population)
    group_dist_value = np.zeros(group_size)
    for i in range(group_size):
        group_dist_value[i] = calcu_distance_value(population[i])
    return group_dist_value

'''
@funtion:
generate_initial_pop(size)函数用于生成容量为size的初始种群
'''
def generate_initial_pop(size):
    initial_pop = np.zeros(shape=(size,10))
    sequence = list(itertools.permutations(array))
    #随机取得全排列中的50个组合,作为初始种群
    for i in range(50):
        for j in range(10):
            initial_pop[i][j] = sequence[i*70000][j]
    return initial_pop

'''
@function:
selected_rate(distance,group_dist)函数用于计算每个个体的选择概率,distance为单个个体的适应值,group_dist为群体中每个个体适应值构成的数组
'''
def selected_rate(distance,group_dist):
    total_rate=0
    rate = 0
    for i in range(len(group_dist)):
        total_rate = total_rate+group_dist[i]
    rate = rate + distance/total_rate
    return rate

'''
@funcion:
select(population)函数用于实现轮盘赌,选择出幸存的个体,goup_rate是所有个体选择概率组成的数组,population是种群中所有个体组成的集合
'''
def select(population):
    random_num_group_size = len(population)
    random_num_group = np.zeros(random_num_group_size)
    for i in range(random_num_group_size):
        random_num_group[i] = random.random()
    group_rate = np.zeros(random_num_group_size)
    group_dist = calcu_group_distance(population)
    for i in range(len(population)):
        distance = calcu_distance(population[i])
        selected_rate_value = selected_rate(distance,group_dist)
        group_rate[i] = group_rate[i] + selected_rate_value
    selected_group = []
    for i in range(len(random_num_group)):
        temp_rate_2 = 0
        for j in range(len(group_rate)):
            temp_rate_2 = temp_rate_2+group_rate[j]
            if temp_rate_2>random_num_group[i]:
                selected_group.append(population[j].copy())
                break
    return selected_group

'''
@function:
cross(population)函数实现了sub exchange crossover交叉算子,把两个父代个体进行交叉生成两个子代,dad和mom分别是父代population中的个体,实现这个
算子的过程分两步,第一步,在某个父代上选择1组基因,在另一父代上找到这些基因所在的位置,第二步,保持未选中基因不变,按选中基因的出现顺序,交换
两父代染色体中基因的位置,一次生成两个子代
'''
def cross(population):
    new_population_size = 0
    new_population_capacity = len(population)+2
    new_population = np.zeros((new_population_capacity,10))
    indexes = range(0,len(population))
    index = random.sample(indexes,2)
    #两个子代都是在父代的基础上变化而来的
    index_father = index[0]
    index_mother = index[1]
    dad = population[index_father].copy()
    mom = population[index_mother].copy()
    offspring_A = population[index_father].copy()
    offspring_B = population[index_mother].copy()
    #将两个子代储存在一个二维数组中返回
    offsprings = np.zeros((2,10))
    #确定基因组中基因值的数目,其是1到10之间的整数
    segment_size = random.randint(1,10)
    dad_segment = np.zeros(segment_size)
    mom_segment = np.zeros(segment_size)
    #按照基因组中基因值的数目从父代中随机抽取基因值构成基因组,segment_location用于存储基因值在父代基
    #因中的位置信息
    dad_segment_location = np.zeros(segment_size)
    #为防止随机生成的基因值所在位置出现重复现象,使用random.sample()方法
    list = [2,1,0,6,9,8,4,3,7,5]
    random.shuffle(list)
    dad_segment_location=random.sample(list,segment_size)
    dad_segment_location.sort()
    mom_segment_location = np.zeros(segment_size)
    for i in range(len(dad_segment)):
        index = dad_segment_location[i]
        dad_segment[i] = dad_segment[i] + dad[index]
    for i in range(segment_size):
        for j in range(len(mom)):
            if mom[j] == dad_segment[i]:
                mom_segment_location[i] = mom_segment_location[i] + j
                break
    mom_segment_location.sort()
    for i in range(segment_size):
        index = int(mom_segment_location[i])
        mom_segment[i] = mom_segment[i] + mom[index]
    for i in range(segment_size):
        dad_index = dad_segment_location[i]
        offspring_A[dad_index] = mom_segment[i]
    for i in range(segment_size):
        mom_index = int(mom_segment_location[i])
        offspring_B[mom_index] = dad_segment[i]
    for i in range(10):
        offsprings[0][i] = offsprings[0][i] + offspring_A[i]
    for i in range(10):
        offsprings[1][i] = offsprings[1][i] + offspring_B[i]
    for i in range(len(population)):
        new_population[i] = population[i].copy()
        new_population_size = new_population_size + 1
    for i in range(len(offsprings)):
        new_population[new_population_size] = offsprings[i].copy()
        new_population_size = new_population_size + 1
    return new_population
#计算出初始种群所有个体的适应值

'''
@function:
mutation(individual)函数实现了变异算子,在个体的基因组中随机选择一个片段,将这个片段逆转,得到新的基因组,individual为某个个体的基因组
'''
def mutation(individual):
    list = [9,1,0,3,4,5,8,2,7,6]
    random.shuffle(list)
    field = random.sample(list,2)
    field.sort()
    start=field[0]
    end=field[1]
    segment=individual[start:end+1].copy()
    for i in range(start,end+1):
        individual[i]=segment[len(segment)-1-i+start]
    return individual

'''
@function:
mutation_operator(population)函数实现了变异算子对整个群体进行操作,其在内部调用了mutation函数
'''
def mutation_operator(population):
    max_population_index = len(population)-1
    #index为发生变异的个体下标
    index = random.randint(0,max_population_index)
    individual = population[index].copy()
    new_individual = mutation(individual).copy()
    population[index] = new_individual
    return population
'''
@function:
SGATSP(initial_pop_capacity,cro_rate,mutation_rate,iteration)封装了以上所有方法,输入四个参数后实现遗传算法
@parameter:
initial_pop_capacity,初始种群规模,建议取值为20-100
cro_rate, 交叉算子执行概率,建议取值为0.4-0.9
mutation, 变异概率,建议取值为0.001-0.01
iteration,迭代次数,作为停止进化的条件
'''
def SGATSP(initial_pop_capacity,cro_rate,mutation_rate,iteration):
    array = [0,1,2,3,4,5,6,7,8,9]
    initial_pop = np.zeros((initial_pop_capacity,10))
    sequence = list(itertools.permutations(array))
    #随机取得全排列中的组合,构成初始种群
    for i in range(initial_pop_capacity):
        for j in range(10):
            initial_pop[i][j] = sequence[i*int(3600000/initial_pop_capacity)][j]
    old_distance_group = calcu_group_distance_value(initial_pop).copy()
    old_distance_group.sort()
    old_distance = old_distance_group[0]
    population_0 = initial_pop.copy()
#按照一定概率进行选择、交叉和变异操作,实现的原理是生成0-1之间的伪随机数,若该随机数落在一定长度的
#区间内,则执行对应概率的操作
    population_1 = select(population_0)
    rand_cross = random.random()
    if rand_cross <= cro_rate:
        population_2 = cross(population_1)
    else:
        population_2 = population_1.copy()
    rand_mutation = random.random()
    if rand_mutation <= mutation_rate:
        new_population = mutation_operator(population_2)
    else:
        new_population = population_2.copy()
    result_set = calcu_group_distance_value(new_population)
    result_route = []
    result_route_index = 0
    min_distance = result_set[0]
    for i in range(len(result_set)):
        if min_distance > result_set[i]:
            min_distance = result_set[i]
            result_route_index = i
    result_route = new_population[result_route_index].copy()
    new_distance = min_distance
    generation = 1
    while generation<iteration:
        old_distance = new_distance
        population_0 = new_population.copy()
        population_1 = select(population_0)
        rand_cross_2 = random.random()
        if rand_cross_2 <= cro_rate:
            population_2 = cross(population_1)
        else:
            population_2 = population_1.copy()
        rand_mutation_2 = random.random()
        if rand_mutation_2 <= mutation_rate:
            new_population = mutation_operator(population_2)
        else:
            new_population = population_2.copy()
        result_set = calcu_group_distance_value(new_population)
        for i in range(len(result_set)):
            if min_distance>result_set[i]:
                min_distance = result_set[i]
                result_route_index = i
        result_route = new_population[result_route_index].copy()
        generation = generation + 1
    print(result_route)
    print(min_distance)
for i in range(5):
    SGATSP(60,0.9,0.005,300)

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值