Python 遗传算法 求解tsp问题

将排列作为基因,进行变异和交叉操作

[1,2,3,4,5]

变异,任意调换两个位置的数字

交叉

[1,2,3,4,5]

[5,4,3,2,1]

由于排列中每个数字只能且必须出现一次,所以交叉操作略微有点麻烦。。。

先进行普通的交叉处理,之后使用set去除重复,并且记录没有使用的数字,在下一个重复的地方放入没有使用的数字即可

import math
import random

dis = [
    [0.00, 24.04, 68.37, 37.66, 58.81, 75.77, 65.20, 57.44, 59.37, 18.61],
    [24.04, 0.00, 89.58, 57.41, 82.04, 95.54, 59.86, 78.53, 73.57, 16.23],
    [68.37, 89.58, 0.00, 69.97, 18.91, 11.62, 86.73, 11.05, 34.42, 75.40],
    [37.66, 57.41, 69.97, 0.00, 52.75, 80.83, 101.03, 61.86, 78.96, 56.26],
    [58.81, 82.04, 18.91, 52.75, 0.00, 30.52, 92.05, 16.56, 45.24, 69.97],
    [75.77, 95.54, 11.62, 80.83, 30.52, 0.00, 85.08, 19.42, 31.47, 80.50],
    [65.20, 59.86, 86.73, 101.03, 92.05, 85.08, 0.00, 78.57, 53.61, 48.83],
    [57.44, 78.53, 11.05, 61.86, 16.56, 19.42, 78.57, 0.00, 28.99, 64.41],
    [59.37, 73.57, 34.42, 78.96, 45.24, 31.47, 53.61, 28.99, 0.00, 57.41],
    [18.61, 16.23, 75.40, 56.26, 69.97, 80.50, 48.83, 64.41, 57.41, 0.00],
]
dis = []
with open('dis17.txt', encoding='utf8', mode='r')  as f:
    for line in f.readlines():
        row = []
        for i in line.strip().split(' '):
            if i.strip() != '':
                row.append(float(i.strip()))
        dis.append(row)


def getDis(path):
    d = sum([dis[path[i]][path[i + 1]]
             for i in range(len(path) - 1)] + [dis[path[-1]][path[0]]])
    return d


class GA():
    def __init__(self, length, count):
        # 染色体长度
        self.length = length
        # 种群中的染色体数量
        self.count = count
        # 随机生成初始种群
        self.population = self.gen_population(length, count)

    def evolve(self, retain_rate=0.1, random_select_rate=0.5, mutation_rate=0.05):
        """
        进化
        对当前一代种群依次进行选择、交叉并生成新一代种群,
        然后对新一代种群进行变异
        """
        parents = self.selection(retain_rate, random_select_rate)
        self.crossover(parents)
        self.mutation(mutation_rate)

    def gen_chromosome(self, length):
        """
        随机生成长度为length的染色体,每个基因的取值是0或1
        这里用一个bit表示一个基因
        """
        chromosome = list(range(length))
        random.shuffle(chromosome)
        return chromosome

    def gen_population(self, length, count):
        """
        获取初始种群(一个含有count个长度为length的染色体的列表)
        """
        return [self.gen_chromosome(length) for i in range(count)]

    def fitness(self, chromosome):
        """
        计算适应度,将染色体解码为0~9之间数字,代入函数计算
        因为是求最大值,所以数值越大,适应度越高
        """
        return -getDis(chromosome)

    def selection(self, retain_rate, random_select_rate):
        """
        选择
        先对适应度从大到小排序,选出存活的染色体
        再进行随机选择,选出适应度虽然小,但是幸存下来的个体
        """
        # 对适应度从大到小进行排序
        graded = [(self.fitness(chromosome), chromosome)
                  for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        # 选出适应性强的染色体
        retain_length = int(len(graded) * retain_rate)
        parents = graded[:retain_length]
        # 选出适应性不强,但是幸存的染色体
        for chromosome in graded[retain_length:]:
            if random.random() < random_select_rate:
                parents.append(chromosome)
        return parents

    def gen(self, fa, ma):
        cross_x = random.randint(0, self.length)
        c = fa[:cross_x]
        d = ma[cross_x:]
        ret = c + d
        used = set()
        for i in range(self.length):
            if ret[i] in used:
                unused = set(range(self.length)) - used
                ret[i] = list(unused)[0]
                used.add(ret[i])
            else:
                used.add(ret[i])
        return ret

    def gen2(self, fa, ma):
        cross_x = random.randint(0, len(fa))
        cross_y = random.randint(0, len(fa))
        cross_x = min(cross_x, cross_y)
        cross_y = max(cross_x, cross_y)
        c = fa[:cross_x]
        d = ma[cross_y:]
        ret = c + fa[cross_x:cross_y] + d
        # print(cross_x, cross_y)
        # print(ret)
        used = set()
        for i in range(len(fa)):
            if ret[i] in used:
                unused = set(range(len(fa))) - used
                ret[i] = list(unused)[random.randint(0, len(unused) - 1)]
                used.add(ret[i])
            else:
                used.add(ret[i])
        return ret

    def crossover(self, parents):
        """
        染色体的交叉、繁殖,生成新一代的种群
        """
        # 新出生的孩子,最终会被加入存活下来的父母之中,形成新一代的种群。
        children = []
        # 需要繁殖的孩子的量
        target_count = len(self.population) - len(parents)
        # 开始根据需要的量进行繁殖
        while len(children) < target_count:
            male = random.randint(0, len(parents) - 1)
            female = random.randint(0, len(parents) - 1)
            if male != female:
                # 随机选取交叉点
                male = parents[male]
                female = parents[female]

                children.append(self.gen2(male, female))
                children.append(self.gen2(female, male))
        # 经过繁殖后,孩子和父母的数量与原始种群数量相等,在这里可以更新种群。
        self.population = parents + children

    def mutation(self, rate):
        """
        变异
        对种群中的所有个体,随机改变某个个体中的某个基因
        """
        for i in range(len(self.population)):
            if random.random() < rate:
                m, n = random.randint(0, self.length - 1), random.randint(0, self.length - 1)
                t = self.population[i][m]
                self.population[i][m] = self.population[i][n]
                self.population[i][n] = t

    def result(self):
        """
        获得当前代的最优值,这里取的是函数取最大值时x的值。
        """
        graded = [(self.fitness(chromosome), chromosome)
                  for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        return self.fitness(graded[0])


if __name__ == '__main__':
    ga = GA(len(dis), 1024)

    #  进化次数
    for i in range(200000):
        ga.evolve()
        if (i + 1) % 100 == 0:
            print(ga.result())

可以使用实数数组保存基因,更加简单

[0.1,0.2,0.3,0.4]

安照对应位置的数字在数组中的大小决定是第几次遍历,上面转化后 为【1,2,3,4】

变异

随机挑选一个位置修改数字即可,比如修改下标为0的基因

[0.1,0.2,0.3,0.4] --> [0.5,0.2,0.3,0.4]

[1,2,3,4] --> [4,1,2,3]

交叉操作

普通交叉即可,不需要考虑每次数字出现一次,实现更简单,运行也更快,效果比排列稍微好一点

import math
import random

dis = []
with open('dis48.txt', encoding='utf8', mode='r')  as f:
    for line in f.readlines():
        row = []
        for i in line.strip().split(' '):
            if i.strip() != '':
                row.append(float(i.strip()))
        dis.append(row)


def getDis(path):
    d = sum([dis[path[i]][path[i + 1]]
             for i in range(len(path) - 1)] + [dis[path[-1]][path[0]]])
    return d


class GA():
    def __init__(self, length, count):
        # 染色体长度
        self.length = length
        # 种群中的染色体数量
        self.count = count
        # 随机生成初始种群
        self.population = self.gen_population(length, count)

    def evolve(self, retain_rate=0.1, random_select_rate=0.5, mutation_rate=0.05):
        """
        进化
        对当前一代种群依次进行选择、交叉并生成新一代种群,
        然后对新一代种群进行变异
        """
        parents = self.selection(retain_rate, random_select_rate)
        self.crossover(parents)
        self.mutation(mutation_rate)

    def gen_chromosome(self, length):
        """
        随机生成长度为length的染色体,每个基因的取值是0或1
        这里用一个bit表示一个基因
        """
        chromosome = [random.random() for _ in range(self.length)]
        return chromosome

    def gen_population(self, length, count):
        """
        获取初始种群(一个含有count个长度为length的染色体的列表)
        """
        return [self.gen_chromosome(length) for i in range(count)]

    def fitness(self, chromosome):
        """
        计算适应度,将染色体解码为0~9之间数字,代入函数计算
        因为是求最大值,所以数值越大,适应度越高
        """
        b = sorted([(i, index) for index, i in enumerate(chromosome)])
        path = [i[1] for i in b]
        return -getDis(path)

    def selection(self, retain_rate, random_select_rate):
        """
        选择
        先对适应度从大到小排序,选出存活的染色体
        再进行随机选择,选出适应度虽然小,但是幸存下来的个体
        """
        # 对适应度从大到小进行排序
        graded = [(self.fitness(chromosome), chromosome)
                  for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        # 选出适应性强的染色体
        retain_length = int(len(graded) * retain_rate)
        parents = graded[:retain_length]
        # 选出适应性不强,但是幸存的染色体
        for chromosome in graded[retain_length:]:
            if random.random() < random_select_rate:
                parents.append(chromosome)
        return parents

    def gen(self, fa, ma):
        cross_x = random.randint(0, self.length)
        c = fa[:cross_x]
        d = ma[cross_x:]
        ret = c + d
        return ret

    def crossover(self, parents):
        """
        染色体的交叉、繁殖,生成新一代的种群
        """
        # 新出生的孩子,最终会被加入存活下来的父母之中,形成新一代的种群。
        children = []
        # 需要繁殖的孩子的量
        target_count = len(self.population) - len(parents)
        # 开始根据需要的量进行繁殖
        while len(children) < target_count:
            male = random.randint(0, len(parents) - 1)
            female = random.randint(0, len(parents) - 1)
            if male != female:
                # 随机选取交叉点
                male = parents[male]
                female = parents[female]

                children.append(self.gen(male, female))
                children.append(self.gen(female, male))
        # 经过繁殖后,孩子和父母的数量与原始种群数量相等,在这里可以更新种群。
        self.population = parents + children

    def mutation(self, rate):
        """
        变异
        对种群中的所有个体,随机改变某个个体中的某个基因
        """
        for i in range(len(self.population)):
            if random.random() < rate:
                m, n = random.randint(0, self.count - 1), random.randint(0, self.length - 1)
                self.population[m][n] = random.random()

    def result(self):
        """
        获得当前代的最优值,这里取的是函数取最大值时x的值。
        """
        graded = [(self.fitness(chromosome), chromosome)
                  for chromosome in self.population]
        graded = [x[1] for x in sorted(graded, reverse=True)]
        return self.fitness(graded[0])


if __name__ == '__main__':
    ga = GA(len(dis), 128)

    #  进化次数
    for i in range(200000):
        ga.evolve()
        if (i + 1) % 100 == 0:
            print(ga.result())

猜你喜欢

转载自my.oschina.net/ahaoboy/blog/1823325