TSP problem solving based on genetic algorithm (python implementation)

Problem Description

Assuming that a traveling businessman wants to visit N cities, he must choose the path he wants to take. The limit of the path is that each city can only be visited once, and in the end he must return to the original starting city. The goal of path selection is that the required path distance is the minimum value among all paths

Requirements: Refer to the core code of the genetic algorithm, and take the TSP problem as an example to realize the solution program of the genetic algorithm

In this problem example, we randomly generate 20 cities, and use the coordinates (x, y) in the plane Cartesian coordinate system to represent the location of the city

Solutions

The genetic algorithm is used to solve the TSP problem, and it uses the characteristics that it is more likely to find the global optimal solution; through the continuous reproduction of the population, it simulates the process of iteratively finding the minimum value of the path. The specific implementation steps are as follows:

  1. Initialize the population: the population is composed of individuals, and an individual corresponds to an effective candidate solution in the problem; in the genetic algorithm, the chromosome is used to represent the individual, and when mapped to the TSP problem, an effective path represents an individual; therefore We can make sure that, in the following description, the chromosome and represent a valid path

  1. Calculate population fitness: fitness reflects the probability of individuals surviving in the population and producing offspring. In the TSP problem, because the purpose of each iteration is to find a shorter path, the shorter the path length of the individual, the It is easier to retain in the iterative process, and the fitness is higher

  1. Natural selection: Selecting advantageous individuals from the current population; I use the tournament selection method:

  1. Each time three individuals are randomly selected from the population (i.e. triple tournament selection method) as a sample

  1. Select the one with the best fitness (that is, the scheme with the shortest path) in the sample and enter the next generation population

  1. Repeat steps ab until the new population is the same size as the original population

  1. Crossover: select individuals in the new population generated by natural selection, and create offspring. In the genetic algorithm, it is realized by exchanging chromosomes between two selected individuals;

The "single-point crossover" is selected in the crossover operation of this question:

  1. set the probability of a crossover

  1. Select an individual from each of the two new populations as the parent of the offspring individual

  1. If there is no crossover, one of the parents is randomly selected as the offspring individual

  1. If a crossover occurs, perform the following steps:

  1. Choose a father (or mother), whose chromosomes are copied for the offspring

  1. Randomly intercept a piece of chromosome from the mother (or father) to replace the chromosome at the corresponding position of the offspring

  1. Generate new offspring from this

  1. Repeat steps abcd until the population of offspring individuals is the same size as the population of the previous generation

  1. Mutation: Change some genes of each newly created individual (since chromosomes are made up of genes, what changes is a certain position on the chromosome); the probability of mutation is usually low

The mutation operation for this problem is "exchange mutation": randomly select two genes on a chromosome, exchange their positions, and form a new chromosome

  1. Elitist strategy: It can ensure that the best individuals in the population enter the next generation; through selection, crossover and mutation, the offspring generated from the previous generation population are not directly used to form the offspring population, but are compared with those in the previous generation population. Individuals are compared, and more excellent individuals are selected to enter the offspring population, so as to ensure that the offspring population generated by each iteration is composed of the best individuals, and the optimal solution can be obtained faster

  1. Selection of termination conditions: set the number of iterations, the algorithm will terminate when the number of iterations is reached, and the best solution will be output

Algorithm source code

import math
import random
import time
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
import numpy as np


# 计算适应度
def compute_fitness(pop_line,distance):
    sum_dis = 0
    temp_dis = 0
    nums = len(pop_line)
    for i in range(nums):
        if i < nums - 1:
            temp_dis = distance[pop_line[i],pop_line[i+1]]
            sum_dis += temp_dis
        else:
            temp_dis = distance[pop_line[i],pop_line[0]]
            sum_dis += temp_dis
    return sum_dis

# 轮盘赌选择(不太好实现,先不用了)
# def select(pops,fitness):
#     fitness = np.array(fitness)
#     index = np.random.choice(np.arange(POP_SIZE), size=1, replace=True,p=( fitness / fitness.sum()))[0]
#     print(fitness[index])
#     return pops[index]

# 自然选择,采用锦标赛选择
def tournament_select(pops,pop_size,fitness,tournament_size):
    new_pops = []
    new_fitness = []
    # 直到新的种群规模到达当前种群规模
    while len(new_pops) < len(pops):
        # 从原始样本中选出的样本
        checked_list_pop = [random.randrange(0,pop_size) for i in range(0,tournament_size)]
        checked_list_fitness = np.array([fitness[i] for i in checked_list_pop])
        min_fitness = checked_list_fitness.min() # 最小适应度
        idx = np.where(checked_list_fitness == min_fitness)[0][0] # 获取索引
        min_pop = pops[idx] #获取对应的个体
        new_pops.append(min_pop) #放入新的种群中
        new_fitness.append(min_fitness)
    # print(new_pops)
    return new_pops #返回新的种群

# 交叉算子 选择单点交叉
def crossover(pop_size,pops_parent1,pops_parent2,trans_rate):
    pops_children = []
    # 双亲交叉一次得到一个子代
    for i in range(0,pop_size):
        child = [None] * len(pops_parent1[i])
        parent1 = pops_parent1[i]
        parent2 = pops_parent2[i]
        # print("双亲为:")
        # print(parent1)
        # print(parent2)
        if random.random() > trans_rate:
            # 不发生交叉,则随机保留父代中的一个
            if random.random() > 0.5:
                child = parent1.copy()
            else:
                child = parent2.copy()
        else:
            #发生交叉
            #确定交叉的位置
            begin = random.randrange(0,len(parent1))
            end = random.randrange(0,len(parent1))
            if begin > end:
                temp = begin
                begin = end
                end = temp
            child[begin:end+1] = parent1[begin:end+1].copy()
            list1 = list(range(end+1,len(parent2)))
            list2 = list(range(0,begin))
            list_idx = list1 + list2
            j = -1
            for i in list_idx:
                for j in range(j+1,len(parent2)):
                    if parent2[j] not in child:
                        child[i] = parent2[j]
                        break
            #print("子代为:",child)
        pops_children.append(child)
    return pops_children

#变异算子 采用交换突变
def mutate(pop_size,pops,mutation_rate):
    pops_mutated = []
    for i in range(pop_size):
        child = pops[i].copy()
        if random.random() < mutation_rate:
            # 发生变异
            pos_first = random.randrange(0,len(child))
            pos_second = random.randrange(0,len(child))
            if pos_first != pos_second:
                temp = child[pos_first]
                child[pos_first] = child[pos_second]
                child[pos_second] = temp
        pops_mutated.append(child)
    return pops_mutated

# elitism策略处理(精英注意)
def elitism(pop_size,pops,children_pops,pop_fitness,children_fitness):
    #如果父代适应度更高的话,则不会被淘汰
    for i in range(0,pop_size):
        if pop_fitness[i] > children_fitness[i]:
            pops[i] = children_pops[i]
            pop_fitness[i] = children_fitness[i]
    return pops,pop_fitness

# 画路径图
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)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()


if __name__ == '__main__':

    CITY_NUM = 20  # 城市数量
    ITERATIONS = 1000  # 终止条件的选择 种群繁衍1000代终止
    POP_SIZE = 100  # 种群大小
    TOURNAMENT_SIZE = 2  # 锦标赛采样大小
    TRANS_RATE = 0.7  # 交叉概率
    MUTATION_RATE = 0.1  # 变异概率

    # 获取城市坐标(可以读取文件获取或者随机生成)
    coordinates = [] # 城市坐标
    with open("data.txt","r") as f:
        lines = f.readlines()
    for line in lines:
        line = line.strip()
        coordinates.append(list(map(int,line.split(" "))))
    # coordinates = 100 * np.random.rand(20, 2)  # 随机产生20个城市
    coordinates = np.array(coordinates)
    row,col = coordinates.shape


    #计算各个城市之间的距离
    distance = np.zeros((row,row))
    for i in range(row):
        for j in range(row):
            distance[i,j] = distance[j,i] = math.sqrt((coordinates[i][0] - coordinates[j][0]) ** 2 + (coordinates[i][1] - coordinates[j][1]) ** 2 )
    #print(distance)

    iteration = 0 #迭代次数为0

    #生成种群 100个 每一种路径的选择相当于一条染色体
    pops = [random.sample([i for i in list(range(len(coordinates)))],len(coordinates)) for j in range(POP_SIZE)]
    #print(pops)

    #计算适应度
    pop_fitness = [0] * POP_SIZE
    for i in range(POP_SIZE):
        pop_fitness[i] = compute_fitness(pops[i],distance)
    # print(pop_fitness)

    # 找到初代最优解
    min_pop_fitness = min(pop_fitness)
    optimal_pop = pops[pop_fitness.index(min_pop_fitness)]
    print("第1代的最短距离为:",min_pop_fitness)
    optimal_pops = [optimal_pop]

    start_time = time.perf_counter()
    x = []
    y = []

    # 开始迭代
    while iteration <ITERATIONS:
        pops_parent1 = tournament_select(pops,POP_SIZE,pop_fitness,TOURNAMENT_SIZE)
        pops_parent2 = tournament_select(pops,POP_SIZE,pop_fitness,TOURNAMENT_SIZE)

        # 双亲交叉
        pops_children = crossover(POP_SIZE,pops_parent1,pops_parent2,TRANS_RATE)
        # print(pops_children)

        # 变异
        pops_mutated = mutate(POP_SIZE,pops_children,MUTATION_RATE)
        # print(pops_mutated)

        # 统计发生变异的个体
        # print( "发生变异的个体有%d个"  %(len(np.where(np.array(pops_children) != np.array(pops_mutated))[0]) / 2) )

        # 获取子代种群
        children_fitness = []
        for i in range(POP_SIZE):
            children_fitness.append(compute_fitness(pops_mutated[i],distance))

        # 应用精英策略
        pops,pop_fitness = elitism(POP_SIZE,pops,pops_children,pop_fitness,children_fitness)

        # 找到当代最优解
        if min_pop_fitness > min(pop_fitness):
            min_pop_fitness = min(pop_fitness)
            optimal_pop = pops[pop_fitness.index(min_pop_fitness)]

        optimal_pops.append(optimal_pop)
        print("第%d代的最短距离为:" % (iteration+2),min_pop_fitness)
        # XY是趋势图的横轴和纵轴
        x.append(iteration)
        y.append(min_pop_fitness)
        iteration += 1

    print("最佳路径为:",optimal_pop)
    end_time = time.perf_counter()

    # 根据绘制每一代最优解的走向图
    plt.figure()
    plt.rcParams['font.sans-serif'] = ['SimHei']
    title = "迭代次数:"+str(ITERATIONS)+"种群大小:"+ str(POP_SIZE)+"\n"+"交叉概率:"+  str(TRANS_RATE) +"变异概率:" + str(MUTATION_RATE)
    plt.title(
        label=title,
        fontdict={
            "fontsize": 20,  # 字体大小
            "color": "black",  # 字体颜色
        }
    )
    plt.plot(x, y)
    plt.show()

    #画路径图
    # draw_path(optimal_pop,coordinates)
    print("迭代所需时间为:%f ms" % ((end_time-start_time)*1000))

Guess you like

Origin blog.csdn.net/qq_51235856/article/details/129737332