遗传算法求解TSP问题python实现

文章目录

      • 1 遗传算法总体设计
      • 2 算子设计
        • 2.1 选择算子
        • 2.2 交叉算子
        • 2.3 变异算子
        • 2.4 将种群的个体按照里程排序,并返回当前种群中的最优个体及其里程
        • 2.5 设置种群数,变异概率及其进化次数
        • 2.6 主函数
      • 3 实验结果分析
      • 4 附python源码(完整版)

1 遗传算法总体设计

  • Step1: 初始化参数(种群数、进化次数、变异概率,此次实验并未用到交叉概率,交叉由保留的父代随机进行);

  • Step2: 初始种群由贪婪算法求得的个体以及其他随机生成的个体构成,评价每一个个体的适配值(路径里程决定);

  • Step3: 判断算法的收敛准则是否满足(此处为迭代次数)。若满足收敛准则,则输出搜索结果,否则执行Step(4~8);

  • Step4: 执行选择操作根据个体适配值随机选择作为父代;

  • Step5: 父代两两个体随机进行交叉操作生成子代;

  • Step6: 父代变异,子代按概率变异,只有变异后的个体优于变异前的个体才接受该个体变异;

  • Step7: 更新种群个体(变异后的父代+子代)对应的适配值,更新当前最优解;

  • Step8: 返回Step3 判断是否进行下一次迭代;

:以下算法需要用到的数据均和禁忌搜索算法中的处理一致,因此这里不再阐述。

2 算子设计

2.1 选择算子

将种群个体按照里程由小到大排列,排列后的种群个体的适应度: f i t n e s s = 1 − 位 次 种 群 规 模 fitness = 1-\frac{位次}{种群规模} fitness=1

采用random库中的random方法,随机生成0~1·之间的数p,若p

  • 代码实现
def selection(population):
    '''
    选出父代个体
    '''
    M = population_size
    parents = []
    for i in range(M):
        if random.random() < (1 - i/M):
            parents.append(population[i])
    return parents

2.2 交叉算子

POSITION BASED CROSSOVER(PX)与CYCLE CROSSOVER(CX)的混合。

  • POSITION BASED CROSSOVER

Step1: 从parent1中随机选择N个点

Step2: 在child1中的相同位置复制这N个点,其余位置为空。

Step3:将在parent2但未在child1中的点按照在parent2中的相对顺序填充到child1中的空位中。

Step4: parent1与parent2交换角色,重复以上步骤,得出child2。

:路径parent1=【1、2、3、4、5、6、7、8、9】,parent2=【5、4、6、3、1、9、2、7、8】

从parent1中随机选取4个点如【2、5、6、9】

parent1:

复制选中的点到child1:

parent2:

将在parent2但未在child1中的点【4、3、1、7、6】按照在parent2中的相对顺序填充到child1中的空位中。

  • CYCLE CROSSOVER

CX交叉方法与PX交叉方法的主要不同点就是第一步选点方式的不同,因此这里只介绍CX的选点方式,交叉方式同PX。

Step1: P1中的第一个点为起点start,P2中的第一个点为end,将start添加到cross_points中。

Step2:找寻P1中end的位置position,令P2中position对应的点为end

Step3:将end添加到cross_points中

Step4:重复step2,3直到end=start

  • 交叉算法流程图:
  • 代码实现
def CPX(parent1,parent2):
    '''
    交叉繁殖:CX与PX的混合双亲产生两个子代
    '''
    cycle = [] #交叉点集
    start = parent1[0]
    cycle.append(start)
    end = parent2[0]
    while end != start:
        cycle.append(end)
        end = parent2[parent1.index(end)]
    child = parent1[:]
    cross_points = cycle[:]
    if len(cross_points) < 2 :
        cross_points = random.sample(parent1,2)
    k = 0
    for i in range(len(parent1)):
        if child[i] in cross_points:
            continue
        else:
            for j in range(k,len(parent2)):
                if parent2[j] in cross_points:
                    continue
                else:
                    child[i] = parent2[j]
                    k = j + 1
                    break   
    return child

2.3 变异算子

采用inversion方法:随机选择两个索引为变异位点,将两个位点之间的数据倒置。

如:child = 【1、2、3、4、5、6、7、8、9】

选择变异位点为【2,5】

变异后:

算法思想:对子代的每一个个体进行概率性变异。若生成的随机数小于设定的变异率,则进行变异。若变异后的个体劣于变异前的个体,则拒绝该个体的变异,保持原状(接受法则)。

代码实现:

def mutation(children,mutation_rate):
    '''
    子代变异
    '''
    for i in range(len(children)):
        if random.random() < mutation_rate:
            child = children[i]
            new_child = child[:]
            index = sorted(random.sample(indexs,2))
            L = index[1] - index[0] + 1
            for j in range(L):
                new_child[index[0]+j] = child[index[1]-j]
            path = [origin] + child + [origin]
            a,b = index[0] + 1,index[1] + 1
            d1 = dis[path[a-1]-1][path[a]-1] + dis[path[b]-1][path[b+1]-1]
            d2 = dis[path[a-1]-1][path[b]-1] + dis[path[a]-1][path[b+1]-1]
            if d2 < d1:#只接受优良变异
                children[i] = new_child

    return children

此外,在程序中父代将进行整体变异(mutation_rate=1),变异仍然遵守接受法则。

2.4 将种群的个体按照里程排序,并返回当前种群中的最优个体及其里程

def get_best_current(population):
    '''
    将种群的个体按照里程排序,并返回当前种群中的最优个体及其里程
    '''
    graded = [[route_mile_cost(x),x] for x in population]
    graded = sorted(graded)
    population = [x[1] for x in graded]
    return graded[0][0],graded[0][1],population

2.5 设置种群数,变异概率及其进化次数

population_size = 100 #种群数
mutation_rate = 0.2 #变异概率
iter_count = 1000 #进化次数

2.6 主函数

  • 流程图
  • 代码实现
def main(iter_count):
    #初始化种群
    population = [greedy_initial_route(remain_cities)]
    # population = []
    for i in range(population_size-1):
        #随机生成个体
        individual  = remain_cities[:]
        random.shuffle(individual)
        population.append(individual)
    mile_cost,result,population = get_best_current(population)
    record = [mile_cost] #记录每一次繁殖的最优值
    i = 0
    while i < iter_count:
        #选择繁殖个体群
        parents = selection(population)
        #交叉繁殖
        target_count = population_size - len(parents) #需要繁殖的数量(保持种群的规模)
        children = []
        while len(children) < target_count:
            parent1,parent2 = random.sample(parents,2)
            child1 = CPX(parent1,parent2)
            child2 = CPX(parent2,parent1)
            children.append(child1)
            children.append(child2)
        #父代变异
        parents = mutation(parents,1)
        #子代变异
        children = mutation(children,mutation_rate)
        #更新种群
        population = parents + children
        #更新繁殖结果
        mile_cost,result,population = get_best_current(population) 
        record.append(mile_cost) #记录每次繁殖后的最优解
        i += 1
    route = [origin] + result + [origin]
    return route,mile_cost,record
  • 绘制进化过程图
def fig():
    time_start = time.time()
    N = 1000 #进化次数
    satisfactory_solution,mile_cost,record = main(N)
    time_end = time.time()
    time_cost = time_end - time_start
    print('time cost:',time_cost)
    print("优化里程成本:%d" %(int(mile_cost)))
    print("优化路径:\n",satisfactory_solution)
    #绘制路线图
    X = []
    Y = []
    for i in satisfactory_solution:
        x = city_location[i-1][0]
        y = city_location[i-1][1]
        X.append(x)
        Y.append(y)
    plt.plot(X,Y,'-o')
    plt.title("satisfactory solution of TS:%d"%(int(mile_cost)))
    plt.show()
    #绘制迭代过程图
    A = [i for i in range(N+1)]#横坐标
    B = record[:] #纵坐标
    plt.xlim(0,N)
    plt.xlabel('进化次数',fontproperties="SimSun")
    plt.ylabel('路径里程',fontproperties="SimSun")
    plt.title("solution of GA changed with evolution")
    plt.plot(A,B,'-')
    plt.show()

3 实验结果分析

对berlin52,在不同的种群数和进化次数下每组做10次重复实验,得出组内平均gap值以及程序平均运行时间如下:

平均gap值
程序平均运行时间(秒)

结果分析:在进化次数相同的情况下,随着种群数的增加,GA的最终结果往往更趋近于最优解。种群数相同的情况下,进化次数的增加并不一定能够改善GA结果。这意味着在进化后期,算法陷入了局部最优。当然,无论种群数的增加还是进化次数的增加都会导致程序运行时间的增加。

以ch150为例,以下结果为GA运算中得出的ch150算例最好的满意解gap值为1.52%

GA搜索路径结果
GA进化过程

4 附python源码(完整版)

import math,random,time
import matplotlib.pyplot as plt
#读取坐标文件
filename = '算例\\berlin52.txt' 
city_num = [] #城市编号
city_location = [] #城市坐标
with open(filename, 'r') as f:
    datas = f.readlines()[6:-1]
for data in datas:
    data = data.split()
    city_num.append(int(data[0]))
    x = float(data[1])
    y = float(data[2])
    city_location.append((x,y))#城市坐标
city_count = len(city_num) #总的城市数
origin = 1 #设置起点和终点
remain_cities = city_num[:] 
remain_cities.remove(origin)#迭代过程中变动的城市
remain_count = city_count - 1 #迭代过程中变动的城市数
indexs = list(i for i in range(remain_count))
#计算邻接矩阵
dis =[[0]*city_count for i in range(city_count)] #初始化
for i in range(city_count):
    for j in range(city_count):
        if i != j:
            dis[i][j] = math.sqrt((city_location[i][0]-city_location[j][0])**2 + (city_location[i][1]-city_location[j][1])**2)
        else:
            dis[i][j] = 0

def route_mile_cost(route):
    '''
    计算路径的里程成本
    '''
    mile_cost = 0.0
    mile_cost += dis[0][route[origin-1]-1]#从起始点开始
    for i in range(remain_count-1):#路径的长度
        mile_cost += dis[route[i]-1][route[i+1]-1]
    mile_cost += dis[route[-1]-1][origin-1] #到终点结束
    return mile_cost

#获取当前邻居城市中距离最短的1个
def nearest_city(current_city,remain_cities):
    temp_min = float('inf')
    next_city = None
    for i in range(len(remain_cities)):
        distance = dis[current_city-1][remain_cities[i]-1]
        if distance < temp_min:
            temp_min = distance
            next_city = remain_cities[i]
    return next_city

def greedy_initial_route(remain_cities):
    '''
    采用贪婪算法生成初始解:从第一个城市出发找寻与其距离最短的城市并标记,
    然后继续找寻与第二个城市距离最短的城市并标记,直到所有城市被标记完。
    最后回到第一个城市(起点城市)
    '''
    cand_cities = remain_cities[:]
    current_city = origin
    initial_route = []
    while len(cand_cities) > 0:
        next_city = nearest_city(current_city,cand_cities) #找寻最近的城市及其距离
        initial_route.append(next_city) #将下一个城市添加到路径列表中
        current_city = next_city #更新当前城市
        cand_cities.remove(next_city) #更新未定序的城市
    return initial_route

#物竞天择,适者生存
def selection(population):
    '''
    选出父代个体
    '''
    M = population_size
    parents = []
    for i in range(M):
        if random.random() < (1 - i/M):
            parents.append(population[i])
    return parents
def CPX(parent1,parent2):
    '''
    交叉繁殖:CX与PX的混合双亲产生两个子代
    '''
    cycle = []
    start = parent1[0]
    cycle.append(start)
    end = parent2[0]
    while end != start:
        cycle.append(end)
        end = parent2[parent1.index(end)]
    child = parent1[:]
    cross_points = cycle[:]
    if len(cross_points) < 2 :
        cross_points = random.sample(parent1,2)
    k = 0
    for i in range(len(parent1)):
        if child[i] in cross_points:
            continue
        else:
            for j in range(k,len(parent2)):
                if parent2[j] in cross_points:
                    continue
                else:
                    child[i] = parent2[j]
                    k = j + 1
                    break   
    return child

#变异
def mutation(children,mutation_rate):
    '''
    子代变异
    '''
    for i in range(len(children)):
        if random.random() < mutation_rate:
            child = children[i]
            new_child = child[:]
            index = sorted(random.sample(indexs,2))
            L = index[1] - index[0] + 1
            for j in range(L):
                new_child[index[0]+j] = child[index[1]-j]
            path = [origin] + child + [origin]
            a,b = index[0] + 1,index[1] + 1
            d1 = dis[path[a-1]-1][path[a]-1] + dis[path[b]-1][path[b+1]-1]
            d2 = dis[path[a-1]-1][path[b]-1] + dis[path[a]-1][path[b+1]-1]
            if d2 < d1:
                children[i] = new_child

    return children

def get_best_current(population):
    '''
    将种群的个体按照里程排序,并返回当前种群中的最优个体及其里程
    '''
    graded = [[route_mile_cost(x),x] for x in population]
    graded = sorted(graded)
    population = [x[1] for x in graded]
    return graded[0][0],graded[0][1],population

population_size = 100 #种群数
mutation_rate = 0.2 #变异概率
def main(iter_count):
    #初始化种群
    population = [greedy_initial_route(remain_cities)]
    # population = []
    for i in range(population_size-1):
        #随机生成个体
        individual  = remain_cities[:]
        random.shuffle(individual)
        population.append(individual)
    mile_cost,result,population = get_best_current(population)
    record = [mile_cost] #记录每一次繁殖的最优值
    i = 0
    while i < iter_count:
        #选择繁殖个体群
        parents = selection(population)
        #交叉繁殖
        target_count = population_size - len(parents) #需要繁殖的数量(保持种群的规模)
        children = []
        while len(children) < target_count:
            parent1,parent2 = random.sample(parents,2)
            child1 = CPX(parent1,parent2)
            child2 = CPX(parent2,parent1)
            children.append(child1)
            children.append(child2)
        #父代变异
        parents = mutation(parents,1)
        #子代变异
        children = mutation(children,mutation_rate)
        #更新种群
        population = parents + children
        #更新繁殖结果
        mile_cost,result,population = get_best_current(population) 
        record.append(mile_cost) #记录每次繁殖后的最优解
        i += 1
    route = [origin] + result + [origin]
    return route,mile_cost,record

def fig():
    time_start = time.time()
    N = 1000 #进化次数
    satisfactory_solution,mile_cost,record = main(N)
    time_end = time.time()
    time_cost = time_end - time_start
    print('time cost:',time_cost)
    print("优化里程成本:%d" %(int(mile_cost)))
    print("优化路径:\n",satisfactory_solution)
    #绘制路线图
    X = []
    Y = []
    for i in satisfactory_solution:
        x = city_location[i-1][0]
        y = city_location[i-1][1]
        X.append(x)
        Y.append(y)
    plt.plot(X,Y,'-o')
    plt.title("satisfactory solution of TS:%d"%(int(mile_cost)))
    plt.show()
    #绘制迭代过程图
    A = [i for i in range(N+1)]#横坐标
    B = record[:] #纵坐标
    plt.xlim(0,N)
    plt.xlabel('进化次数',fontproperties="SimSun")
    plt.ylabel('路径里程',fontproperties="SimSun")
    plt.title("solution of GA changed with evolution")
    plt.plot(A,B,'-')
    plt.show()
    return mile_cost,time_cost

# fig()
   
# record1 = [0]*10
# record2 = [0]*10
# for i in range(10):
#     record1[i],record2[i] = fig()
# print(min(record1))
# print(sum(record1)/10)
# print(record1)
# R = 10
# Mcosts = [0]*R
# Tcosts = [0]*R
# for j in range(R):
#     Mcosts[j],Tcosts[j] = fig()
# AM = sum(Mcosts)/R #平均里程
# AT = sum(Tcosts)/R #平均时间
# print("最小里程:",min(Mcosts))
# print("平均里程:",AM)
# print('里程:\n',Mcosts)
# print("平均时间:",AT)

你可能感兴趣的:(遗传算法,TSP问题,python,算法)