遗传-粒子群算法&遗传-禁忌搜索算法求解TSP问题

  • Post author:
  • Post category:其他




1. 前言

上一篇博文【

五种常见启发式算法求解TSP问题-总结篇

】中,总结了五种常见启发式算法在求解TSP问题上的效果,其中遗传算法的求解质量最差,而粒子群算法和禁忌搜索算法的求解效果最佳,因此本文计划引入粒子群思想和禁忌搜索思想对遗传算法进行优化。



2. 算法设计



2.1 遗传-禁忌搜索算法



2.1.1 改进思路

禁忌搜索算法的核心思想是不重复已经搜索过的解,以提高搜索的效率。在改进遗传算法上,可以对交叉算子和变异算法做出禁忌操作,具体改进想法如下:

①建立全局禁忌表,存储每一代中的最优解,在所有交叉和变异操作中作为禁忌对象;

②建立局部禁忌表,在交叉算子中,局部禁忌表为上一代种群和当前已交叉生成的子代种群,在变异算子中,局部禁忌表为上一代种群、交叉后待变异的种群和当前已变异生成子代种群;



2.1.2 程序设计

①基于禁忌搜索算法改进的交叉算子

def crossover(popsize,parent1_pops,parent2_pops,pc,tabu_list,par_pops):
    '''
    #顺序交叉
    输入:popsize-种群大小,parent1_pops-父代,parent2_pops-母代,pc-交叉概率,tabu_list-全局禁忌表,par_pops-上一代种群;
    输出:交叉后的种群-child_pops
    '''
    child_pops = []
    
    for i in range(popsize):
        flag = True
        while flag:
            
            #初始化
            child = [None]*len(parent1_pops[i])
            parent1 = parent1_pops[i]
            parent2 = parent2_pops[i]
            
            if random.random() >= pc:
                child = parent1.copy()#随机生成一个
                random.shuffle(child)
                
            else:
                #parent1 -> child
                start_pos = random.randint(0,len(parent1)-1)
                end_pos = random.randint(0,len(parent1)-1)
                if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
                child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
                # parent2 -> child
                list1 = list(range(end_pos+1,len(parent2)))
                list2 = list(range(0,start_pos))
                list_index = list1+list2
                j = -1
                for i in list_index:
                    for j in range(j+1,len(parent2)):
                        if parent2[j] not in child:
                            child[i] = parent2[j]
                            break
            
            if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#判断是否满足禁忌表
                child_pops.append(child)
                flag = False
                
    return child_pops
    

②基于禁忌搜索算法改进的变异算子

def mutate(pops,pm,tabu_list,par_pops):
    '''
    #基本位变异,成对变异
    输入:pops-交叉后种群,pm-变异概率,tabu_list-群居禁忌表,par_pops-上一代种群;
    输出:变异后种群-pops_mutate
    '''
    pops_mutate = []
    for i in range(len(pops)):
        flag = True
        while flag:
            pop = pops[i].copy()
            t = random.randint(1,10)#随机多次成对变异
            count = 0
            while count < t:
                if random.random() < pm: 
                        mut_pos1 = random.randint(0,len(pop)-1)  
                        mut_pos2 = random.randint(0,len(pop)-1)
                        if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
                count +=1
            
            if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、交叉后种群禁忌,上一代种群禁忌
                pops_mutate.append(pop)
                flag = False

    return pops_mutate
    

③完整程序

# -*- coding: utf-8 -*-
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文


def calFitness(line,dis_matrix):
    '''
    计算路径距离,即评价函数
    输入:line-路径,dis_matrix-城市间距离矩阵;
    输出:路径距离-dis_sum
    '''
    dis_sum = 0
    dis = 0
    for i in range(len(line)-1):
        dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
        dis_sum = dis_sum+dis
    dis = dis_matrix.loc[line[-1],line[0]]
    dis_sum = dis_sum+dis
    return round(dis_sum,1)


def tournament_select(pops,popsize,fits,tournament_size):
	''' 
	#锦标赛选择
	'''
    new_pops = []
    while len(new_pops)<len(pops):
        tournament_list = random.sample(range(0,popsize),tournament_size)
        tournament_fit = [fits[i] for i in tournament_list]
        #转化为df方便索引
        tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
        #选出获胜者
        pop = pops[int(tournament_df.iloc[0,0])]
        new_pops.append(pop)

    return new_pops


def crossover(popsize,parent1_pops,parent2_pops,pc,tabu_list,par_pops):
    '''
    #顺序交叉
    输入:popsize-种群大小,parent1_pops-父代,parent2_pops-母代,pc-交叉概率,tabu_list-全局禁忌表,par_pops-上一代种群;
    输出:交叉后的种群-child_pops
    '''
    child_pops = []
    
    for i in range(popsize):
        flag = True
        while flag:
            
            #初始化
            child = [None]*len(parent1_pops[i])
            parent1 = parent1_pops[i]
            parent2 = parent2_pops[i]
            
            if random.random() >= pc:
                child = parent1.copy()#随机生成一个
                random.shuffle(child)
                
            else:
                #parent1 -> child
                start_pos = random.randint(0,len(parent1)-1)
                end_pos = random.randint(0,len(parent1)-1)
                if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
                child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
                # parent2 -> child
                list1 = list(range(end_pos+1,len(parent2)))
                list2 = list(range(0,start_pos))
                list_index = list1+list2
                j = -1
                for i in list_index:
                    for j in range(j+1,len(parent2)):
                        if parent2[j] not in child:
                            child[i] = parent2[j]
                            break
            
            if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#判断是否满足禁忌表
                child_pops.append(child)
                flag = False
                
    return child_pops


def mutate(pops,pm,tabu_list,par_pops):
    '''
    #基本位变异,成对变异
    输入:pops-交叉后种群,pm-变异概率,tabu_list-群居禁忌表,par_pops-上一代种群;
    输出:变异后种群-pops_mutate
    '''
    pops_mutate = []
    for i in range(len(pops)):
        flag = True
        while flag:
            pop = pops[i].copy()
            t = random.randint(1,10)#随机多次成对变异
            count = 0
            while count < t:
                if random.random() < pm: 
                        mut_pos1 = random.randint(0,len(pop)-1)  
                        mut_pos2 = random.randint(0,len(pop)-1)
                        if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
                count +=1
            
            if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、交叉后种群禁忌,上一代种群禁忌
                pops_mutate.append(pop)
                flag = False

    return pops_mutate


#画路径图
def draw_path(line,CityCoordinates):
    x,y= [],[]
    for i in line:
        Coordinate = CityCoordinates[i]
        x.append(Coordinate[0])
        y.append(Coordinate[1])
    x.append(x[0])
    y.append(y[0])
    
    plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    

if __name__ == '__main__':
    #参数
    # CityNum = 50#城市数量
    # MinCoordinate = 0#二维坐标最小值
    # MaxCoordinate = 101#二维坐标最大值
    
    #GA参数
    generation = 1000  #迭代次数
    popsize = 100   #种群大小
    tournament_size = 5 #锦标赛小组大小
    pc = 0.95   #交叉概率
    pm = 0.1    #变异概率
   
    #TS参数
    tabu_limit = 100 #禁忌长度
    tabu_list = [] #禁忌表
    tabu_time = [] #禁忌次数
    
    #随机生成城市数据,城市序号为0,1,2,3...
    # CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
    # CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
    CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
    
    #计算城市之间的距离
    dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
    for i in range(len(CityCoordinates)):
        xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
        for j in range(len(CityCoordinates)):
            xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
            dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
    
    iteration = 0
    #初始化,随机构造
    pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
    
    #计算适应度
    fits = [None]*popsize
    for i in range(popsize):
        fits[i] = calFitness(pops[i],dis_matrix)
    
    #保留当前最优
    best_fit = min(fits)
    best_pop = pops[fits.index(best_fit)]
    
    #禁忌
    tabu_list.append(best_pop)
    tabu_time.append(tabu_limit)
    
    print('初代最优值 %.1f' % (best_fit))
    best_fit_list = []
    best_fit_list.append(best_fit)
    
    while iteration <= generation:
        #锦标赛赛选择
        pop1 = tournament_select(pops,popsize,fits,tournament_size)
        pop2 = tournament_select(pops,popsize,fits,tournament_size)
        child_pops = crossover(popsize,pop1,pop2,pc,tabu_list,pops)#交叉
        child_pops = mutate(child_pops,pm,tabu_list,pops)#变异
        
        child_fits = [None]*popsize
        for i in range(popsize):
            child_fits[i] = calFitness(child_pops[i],dis_matrix)
            
        #一对一生存者竞争
        for i in range(popsize):
            if fits[i] > child_fits[i]:
                fits[i] = child_fits[i]
                pops[i] = child_pops[i]
        
        #更新禁忌表
        tabu_time = [x-1 for x in tabu_time]
        if 0 in tabu_time:
            tabu_list.remove(tabu_list[tabu_time.index(0)])
            tabu_time.remove(0)
    
        if best_fit>min(fits):
            best_fit = min(fits)
            best_pop = pops[fits.index(best_fit)]
        #添加禁忌
        tabu_list.append(best_pop)
        tabu_time.append(tabu_limit)
            
        best_fit_list.append(best_fit)
        print('第%d代最优值 %.1f' % (iteration, best_fit))
        iteration += 1
    
    #路径顺序
    print(best_pop)
    # #画图
    draw_path(best_pop,CityCoordinates)
    #迭代图
    iters = list(range(len(best_fit_list)))
    plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('迭代次数')
    plt.ylabel('最优解')
    plt.show()

最优解为[27, 25, 23, 49, 45, 29, 43, 40, 35, 9, 31, 26, 39, 34, 14, 6, 19, 41, 20, 24, 33, 1, 0, 22, 48, 44, 13, 21, 30, 16, 37, 10, 46, 36, 18, 15, 8, 28, 42, 38, 3, 11, 47, 32, 17, 7, 2, 4, 12, 5],其适应度值为617.0,路径图和迭代效果如下。

在这里插入图片描述

在这里插入图片描述



讨论

】经过测试,多次求解质量的平均水平相比经典遗传算法有了一定的改进。



2.2 遗传-粒子群算法



2.2.1改进思路

粒子群算法的核心思想是不断向当前最优解和历史最优解的方向移动,以达到最优解,在改进遗传算法上的思路是在交叉操作上,以一定的概率(轮盘赌操作)与当前最优解或历史最优解进行交叉操作。



2.2.2 程序设计

①基于粒子群算法改进的交叉算子

def crossover(popsize,parent1_pops,pc,c1,c2,pLine,best_pop):
    '''
    #顺序交叉
    输入:popsize-种群大小,parent1_pops-选择后父代,pc-变异系数,c1-自我认知因子,c2-社会认知因子,pLine-当前最优解,best_pop-历史最优解
    输出:交叉后子代种群-child_pops
    '''
    child_pops = []
    for i in range(popsize):
        #初始化
        child = [None]*len(parent1_pops[i])
        parent1 = parent1_pops[i]
        #求parent2
        randNum = random.uniform(0, sum([c1,c2]))
        if randNum <= c1:
            parent2 = pLine
        else:
            parent2 = best_pop
        
        if random.random() >= pc:
            child = parent1.copy()#随机生成一个
            random.shuffle(child)
            
        else:
            #parent1-> child
            start_pos = random.randint(0,len(parent1)-1)
            end_pos = random.randint(0,len(parent1)-1)
            if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
            
            child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
            # parent2 -> child
            list1 = list(range(end_pos+1,len(parent2)))
            list2 = list(range(0,start_pos))
            list_index = list1+list2
            j = -1
            for i in list_index:
                for j in range(j+1,len(parent2)):
                    if parent2[j] not in child:
                        child[i] = parent2[j]
                        break
        child_pops.append(child)
    return child_pops

②完整程序

# -*- coding: utf-8 -*-
"""
遗传算法求解TSP问题
随机在(0,100)二维平面生成20个点
距离最小化
"""
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文


def calFitness(line,dis_matrix):
    dis_sum = 0
    dis = 0
    for i in range(len(line)):
        if i<len(line)-1:
            dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
            dis_sum = dis_sum+dis
        else:
            dis = dis_matrix.loc[line[i],line[0]]
            dis_sum = dis_sum+dis
    return round(dis_sum,1)


#锦标赛选择
def tournament_select(pops,popsize,fits,tournament_size):
    new_pops = []
    while len(new_pops)<len(pops):
        tournament_list = random.sample(range(0,popsize),tournament_size)
        tournament_fit = [fits[i] for i in tournament_list]
        #转化为df方便索引
        tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
        #选出获胜者
        pop = pops[int(tournament_df.iloc[0,0])]
        new_pops.append(pop)

    return new_pops


def crossover(popsize,parent1_pops,pc,c1,c2,pLine,best_pop):
    '''
    #顺序交叉
    输入:popsize-种群大小,parent1_pops-选择后父代,pc-变异系数,c1-自我认知因子,c2-社会认知因子,pLine-当前最优解,best_pop-历史最优解
    输出:交叉后子代种群-child_pops
    '''
    child_pops = []
    for i in range(popsize):
        #初始化
        child = [None]*len(parent1_pops[i])
        parent1 = parent1_pops[i]
        #求parent2
        randNum = random.uniform(0, sum([c1,c2]))
        if randNum <= c1:
            parent2 = pLine
        else:
            parent2 = best_pop
        
        if random.random() >= pc:
            child = parent1.copy()#随机生成一个
            random.shuffle(child)
            
        else:
            #parent1-> child
            start_pos = random.randint(0,len(parent1)-1)
            end_pos = random.randint(0,len(parent1)-1)
            if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
            
            child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
            # parent2 -> child
            list1 = list(range(end_pos+1,len(parent2)))
            list2 = list(range(0,start_pos))
            list_index = list1+list2
            j = -1
            for i in list_index:
                for j in range(j+1,len(parent2)):
                    if parent2[j] not in child:
                        child[i] = parent2[j]
                        break
        child_pops.append(child)
    return child_pops


def mutate(pops,pm):
    '''
    #基本位变异,成对变异
    '''
    pops_mutate = []
    for i in range(len(pops)):
        pop = pops[i].copy()
        t = random.randint(1,10)#随机多次成对变异
        count = 0
        while count < t:
            if random.random() < pm: 
                    mut_pos1 = random.randint(0,len(pop)-1)  
                    mut_pos2 = random.randint(0,len(pop)-1)
                    if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
            pops_mutate.append(pop)
            count +=1
    return pops_mutate


#画路径图
def draw_path(line,CityCoordinates):
    x,y= [],[]
    for i in line:
        Coordinate = CityCoordinates[i]
        x.append(Coordinate[0])
        y.append(Coordinate[1])
    x.append(x[0])
    y.append(y[0])
    plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    

if __name__ == '__main__':
    # #参数
    # CityNum = 50#城市数量
    # MinCoordinate = 0#二维坐标最小值
    # MaxCoordinate = 101#二维坐标最大值
    
    #GA参数
    generation = 1000  #迭代次数
    popsize = 100   #种群大小
    tournament_size = 5 #锦标赛小组大小
    pc = 0.95   #交叉概率
    pm = 0.1    #变异概率
    # n = 1#灾变系数
    
    #PSO参数
    c1 = 0.5#自我认知因子
    c2 = 0.5#社会认知因子
    pBest,pLine =0,[]#当前最优值、当前最优解,(自我认知部分)
    
    #随机生成城市数据,城市序号为0,1,2,3...
    # CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
    # CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
    CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
    
    #计算城市之间的距离
    dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
    for i in range(len(CityCoordinates)):
        xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
        for j in range(len(CityCoordinates)):
            xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
            dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
    
    iteration = 0
    #初始化,随机构造
    pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
    
    #计算适应度
    fits = [None]*popsize
    for i in range(popsize):
        fits[i] = calFitness(pops[i],dis_matrix)
    
    #保留当前最优
    best_fit = min(fits)
    best_pop = pops[fits.index(best_fit)]
    pBest,pLine = min(fits),pops[fits.index(best_fit)]
    
    
    print('初代最优值 %.1f' % (best_fit))
    best_fit_list = []
    best_fit_list.append(best_fit)
    
    while iteration <= generation:
        
        pop1 = tournament_select(pops,popsize,fits,tournament_size)#锦标赛赛选择
        child_pops = crossover(popsize,pop1,pc,c1,c2,pLine,best_pop)#交叉
        child_pops = mutate(child_pops,pm)#变异
        
        child_fits = [None]*popsize
        for i in range(popsize):
            child_fits[i] = calFitness(child_pops[i],dis_matrix)
            
        #一对一生存者竞争
        for i in range(popsize):
            if fits[i] > child_fits[i]:
                fits[i] = child_fits[i]
                pops[i] = child_pops[i]
        
        if best_fit>min(fits):
            best_fit = min(fits)
            best_pop = pops[fits.index(best_fit)]
            pBest,pLine = min(fits),pops[fits.index(best_fit)]
            # n = 1#灾变系数
        
        elif pBest>min(fits):
            pBest,pLine = min(fits),pops[fits.index(best_fit)]
        #     n += 1
        # else:
        #     n += 1

        # if n == 20:#20代历史最优值没变化就发生灾变
        #     for i in range(int(len(pops)/2)):
        #         pops[i] = random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates))
        
        best_fit_list.append(best_fit)
        print('第%d代最优值 %.1f' % (iteration, best_fit))
        iteration += 1
    
    #路径顺序
    print(best_pop)
    # #画图
    draw_path(best_pop,CityCoordinates)
    #迭代图
    iters = list(range(len(best_fit_list)))
    plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('迭代次数')
    plt.ylabel('最优解')
    plt.show()
    

最优解为[42, 11, 28, 8, 15, 18, 36, 46, 48, 0, 1, 22, 44, 10, 33, 13, 16, 30, 37, 21, 24, 20, 40, 35, 45, 9, 41, 19, 6, 34, 14, 39, 7, 4, 2, 12, 5, 27, 25, 23, 49, 43, 29, 31, 26, 17, 32, 47, 3, 38],其适应度值为732.6,路径图和迭代效果如下。

在这里插入图片描述

在这里插入图片描述



讨论

】粒子群算法改进遗传算法的表现与经典遗传算法相比,表现更差了,深入分析后也可以理解这一结果:在我设计的算法中,只按一定的概率与当前最优解或者历史最优解进行交叉,一定程度上使得种群极易陷入局部解,且缺乏多样性的种群很难跳出局部最优解。尝试引入“灾变”技术进行改进(代码中有保留,灾变系数n注释掉的部分),但是效果也不佳,归根到底,种群多样性少会使得种群搜索效率变得较低。



2.3 遗传-禁忌搜索-粒子群算法



2.3.1改进思路

粒子群算法改进的效果不佳,一定部分原因是收敛太快导致使得搜索效率低下,为了提高搜索效率,这里再引入禁忌搜索算法的思想尝试进行改进,改进思路是前面两种算法的结合,即在交叉算子和变异算子中引入禁忌搜索的思想,在交叉算子中叠加粒子群算法的思想。



2.3.2 程序设计

# -*- coding: utf-8 -*-
"""
遗传算法求解TSP问题
随机在(0,100)二维平面生成50个点
距离最小化
"""
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文


def calFitness(line,dis_matrix):
    dis_sum = 0
    dis = 0
    for i in range(len(line)-1):
        dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
        dis_sum = dis_sum+dis
    dis = dis_matrix.loc[line[-1],line[0]]
    dis_sum = dis_sum+dis
    return round(dis_sum,1)


#锦标赛选择
def tournament_select(pops,popsize,fits,tournament_size):
    new_pops = []
    while len(new_pops)<len(pops):
        tournament_list = random.sample(range(0,popsize),tournament_size)
        tournament_fit = [fits[i] for i in tournament_list]
        #转化为df方便索引
        tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
        #选出获胜者
        pop = pops[int(tournament_df.iloc[0,0])]
        new_pops.append(pop)

    return new_pops


#顺序交叉
def crossover(popsize,parent1_pops,pc,tabu_list,par_pops,c1,c2,pLine,best_pop):
    child_pops = []
    for i in range(popsize):
        flag = True
        while flag: 
            #初始化
            child = [None]*len(parent1_pops[i])
            parent1 = parent1_pops[i]
            
            #求parent2
            randNum = random.uniform(0, sum([c1,c2]))
            if randNum <= c1:
                parent2 = pLine
            else:
                parent2 = best_pop
            
            if random.random() >= pc:
                child = parent1.copy()#随机生成一个
                random.shuffle(child)
                
            else:
                #parent1 -> child
                start_pos = random.randint(0,len(parent1)-1)
                end_pos = random.randint(0,len(parent1)-1)
                if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
                child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
                # parent2 -> child
                list1 = list(range(end_pos+1,len(parent2)))
                list2 = list(range(0,start_pos))
                list_index = list1+list2
                j = -1
                for i in list_index:
                    for j in range(j+1,len(parent2)):
                        if parent2[j] not in child:
                            child[i] = parent2[j]
                            break
            
            if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#全局禁忌、当前禁忌、上一代种群禁忌
                child_pops.append(child)
                flag = False
                     
    return child_pops


#基本位变异,成对变异
def mutate(pops,pm,tabu_list,par_pops):
    pops_mutate = []
    for i in range(len(pops)):
        flag = True
        while flag:
            pop = pops[i].copy()
            #随机多次成对变异
            t = random.randint(1,10)
            count = 0
            while count < t:
                if random.random() < pm: 
                        mut_pos1 = random.randint(0,len(pop)-1)  
                        mut_pos2 = random.randint(0,len(pop)-1)
                        if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
                count +=1
            
            if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、变异之前的群体禁忌
                pops_mutate.append(pop)
                flag = False

    return pops_mutate


#画路径图
def draw_path(line,CityCoordinates):
    x,y= [],[]
    for i in line:
        Coordinate = CityCoordinates[i]
        x.append(Coordinate[0])
        y.append(Coordinate[1])
    x.append(x[0])
    y.append(y[0])
    
    plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    

if __name__ == '__main__':
    #参数
    CityNum = 50#城市数量
    MinCoordinate = 0#二维坐标最小值
    MaxCoordinate = 101#二维坐标最大值
    
    #GA参数
    generation = 1000  #迭代次数
    popsize = 100   #种群大小
    tournament_size = 5 #锦标赛小组大小
    pc = 0.95   #交叉概率
    pm = 0.1    #变异概率
    
    #TS参数
    tabu_limit = 200 #禁忌长度
    tabu_list = [] #禁忌表
    tabu_time = [] #禁忌次数
    
    #PSO参数
    c1 = 0.5#自我认知因子
    c2 = 0.5#社会认知因子
    pBest,pLine =0,[]#当前最优值、当前最优解,(自我认知部分)
    
    #随机生成城市数据,城市序号为0,1,2,3...
    # CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
    # CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
    CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
    
    #计算城市之间的距离
    dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
    for i in range(len(CityCoordinates)):
        xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
        for j in range(len(CityCoordinates)):
            xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
            dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
    
    iteration = 0
    #初始化,随机构造
    pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
    
    #计算适应度
    fits = [None]*popsize
    for i in range(popsize):
        fits[i] = calFitness(pops[i],dis_matrix)
   
    #保留当前最优
    best_fit = min(fits)
    best_pop = pops[fits.index(best_fit)]
    pBest,pLine = min(fits),pops[fits.index(best_fit)]
    
    #禁忌
    tabu_list.append(best_pop)
    tabu_time.append(tabu_limit)
    
    print('初代最优值 %.1f' % (best_fit))
    best_fit_list = []
    best_fit_list.append(best_fit)
    
    while iteration <= generation:
        pop1 = tournament_select(pops,popsize,fits,tournament_size)#锦标赛赛选择
        child_pops = crossover(popsize,pop1,pc,tabu_list,pops,c1,c2,pLine,best_pop)#交叉
        child_pops = mutate(child_pops,pm,tabu_list,pops)#变异
        
        child_fits = [None]*popsize
        for i in range(popsize):
            child_fits[i] = calFitness(child_pops[i],dis_matrix)
            
        # #一对一生存者竞争
        for i in range(popsize):
            if fits[i] > child_fits[i]:
                fits[i] = child_fits[i]
                pops[i] = child_pops[i]
        
        #更新禁忌表
        tabu_time = [x-1 for x in tabu_time]
        if 0 in tabu_time:
            tabu_list.remove(tabu_list[tabu_time.index(0)])
            tabu_time.remove(0)
    
        if best_fit>min(fits):
            best_fit = min(fits)
            best_pop = pops[fits.index(best_fit)]
            pBest,pLine = min(fits),pops[fits.index(best_fit)]
        elif pBest>min(fits):
            pBest,pLine = min(fits),pops[fits.index(best_fit)]
        #添加禁忌
        tabu_list.append(best_pop)
        tabu_time.append(tabu_limit)
              
        best_fit_list.append(best_fit)
        print('第%d代最优值 %.1f' % (iteration, best_fit))
        iteration += 1
    
    #路径顺序
    print(best_pop)
    # #画图
    draw_path(best_pop,CityCoordinates)
    #迭代图
    iters = list(range(len(best_fit_list)))
    plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
    plt.xlabel('迭代次数')
    plt.ylabel('最优解')
    plt.show()
    

最优解为[30, 16, 37, 13, 46, 36, 8, 28, 15, 18, 11, 42, 38, 3, 47, 32, 2, 4, 12, 5, 27, 25, 23, 7, 17, 14, 39, 19, 41, 6, 34, 26, 31, 43, 45, 29, 49, 40, 35, 9, 20, 0, 10, 48, 44, 22, 1, 33, 24, 21],其适应度值为663.0,路径图和迭代图如下:

在这里插入图片描述

在这里插入图片描述



讨论

】在粒子群改进的基础上加入禁忌搜索算法思想,整体效果比遗传-粒子群算法和经典遗传算法效果稍微稳定一些,但稍微逊色于遗传-禁忌搜索算法。



3. 总结

本文分别尝试了使用禁忌搜索算法、粒子群算法对遗传算法进行改进,并尝试结合三种算法的思想构建遗传-禁忌搜索-粒子群算法,在以上三种改进中,遗传-禁忌搜索算法的求解效果是比较好的,当然这也与算法设计有很大的关系,不同的算子设计可能带来不同的效果,而粒子群算法改进遗传算法的效果适得其反,对比单纯用【

粒子群算法求解TSP问题

】的设计,在遗传算法上可能是选择操作使得进行交叉的种群多样性较少(粒子群和遗传算法都有交叉操作),进而降低了对解空间的搜索效率。

学习完TSP问题求解系列,最大的感受就是

初始解的构造上对求解效率的影响非常大

,良好的初始解可以减少大量冗余的遍历过程,且极易获得较优解。但是,也存在某些问题上可能很难设计出较好的初始解(比如多约束的VRP问题),这种情况下问题的求解对算法依赖比较大,需要选中更加合适的算法或者结合多种算法思想进行求解。






TSP系列目录

智能优化算法类别 启发式算法求解TSP问题系列博文
进化算法
遗传算法求解TSP问题
仿人智能优化算法
禁忌搜索算法求解TSP问题
仿自然优化算法
模拟退火算法求解TSP问题
群智能优化算法
蚁群算法求解TSP问题
群智能优化算法
粒子群算法求解TSP问题
总结篇
五种常见启发式算法求解TSP问题
改进篇
遗传-粒子群算法&遗传-禁忌搜索算法求解TSP问题



记录学习过程,欢迎指正



版权声明:本文为tangshishe原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。