前言
遗传算法是典型的智能算法,其本质也是通过不断搜索从而得到最好的解决方案,但是与随机搜索不同,遗传算法的搜索具有一定的智能性,在搜索时它更倾向于选择适应度更优的解决方案,以下是遗传算法的一个基本流程图:
在上述基本遗传算法的基础上,可以在初始种群、个体选择、交叉方式、变异方式以及局部优化等方面作进一步调整,从而达到更好的效果。搜索类算法改进的本质基本都是让搜索具有全局性或者使得每次搜索的解更优质。
构造
使用遗传算法求解旅行商问题最主要的问题是编码和交叉,要保证交叉过程中尽量不产生坏解且交叉能够带来足够大的随机性,这具有一定的难度。
编码:[x1,x2…xn],xi代表第i个访问的城市,其中x1始终等于0
选择:使用轮盘赌策略选择两个交叉的个体
交叉:交叉最重要的是要保证交叉过后产生的个体为可行解。在进行交叉时,首先生成一个长度为n的0-1数组,然后根据数组中元素的值判断是否在此位置进行交叉,例如假设有4个城市,个体1为[0,1,2,3],个体2为[0,3,1,2],交叉数组为[0,0,1,0],这就代表只对第3个城市的位置进行交叉操作,交换两个个体第3个城市的位置,从而产生两个新为[0,1,3,2]和[0,3,2,1]。
局部优化:使用爬山法对每个个体进行优化。
代码
值得注意的点是,当子代发展到一定代数时,个体会趋向一致,这时候就会产生大量重复的个体,为了处理这种情况,对每一个重复的个体做一次随机扰动,保证每一代的个体中的没有重复个体。
import numpy as np
import pandas as pd
import random
import copy
# 计算距离
def calculate_distance(record):
total_distance = 0
if record[len(record) - 1]:
record.append(0)
for i in range(city_num):
total_distance += distance[record[i]][record[i + 1]]
return total_distance
# 交换位置
def swap(record, i, j):
temp = record[i]
record[i] = record[j]
record[j] = temp
# 优化个体
def improve(record):
for i in range(city_num):
for j in range(city_num):
if i == j:
continue
if record[i] * record[j] == 0:
continue
pre_distance = calculate_distance(record)
swap(record, i, j)
cur_distance = calculate_distance(record)
if cur_distance > pre_distance:
swap(record, i, j)
return record
# 数据
distance = pd.read_table('distance.txt', header=None, index_col=None)
distance = distance.values.tolist()
city_num = len(distance)
# 参数
iter_num = 100 # 迭代次数
gen_num = 100 # 每代个体数
min_distance = 10000 # 最短距离
min_record = [-1 for i in range(city_num)] # 最短距离对应遍历顺序
# 初始种群
records = [[i for i in range(city_num)] for j in range(gen_num)]
for i in range(gen_num):
random.shuffle(records[i])
for j in range(gen_num):
if records[i][j] == 0:
swap(records[i], j, 0)
break
if min_distance > calculate_distance(records[i]):
min_distance = calculate_distance(records[i])
min_record = copy.deepcopy(records[i])
# 遗传算法
for i in range(iter_num):
new_records = []
pro = [] # 选中每个个体的可能性
sum_pro = 0 # 个体可能性之和
for j in range(gen_num):
pro.append(1 / calculate_distance(records[j]))
sum_pro += pro[j]
# 轮盘赌选择两个个体进行交叉
for j in range(gen_num):
idx = [] # 记录两个个体
for k in range(2):
rand_num = sum_pro * random.random()
for jj in range(gen_num):
if sum(pro[:jj + 1]) >= rand_num:
idx.append(jj)
break
# 交叉择优得到一个新个体
cross = [random.randint(0, 1) for ii in range(city_num)] # 选择交叉位置,1交叉,0不交叉
for jj in range(city_num):
if not cross[jj]:
continue
swap_index = [0, 0]
for k in range(city_num):
if records[idx[0]][k] == jj:
swap_index[0] = k
if records[idx[1]][k] == jj:
swap_index[1] = k
swap(records[idx[0]], swap_index[0], swap_index[1])
swap(records[idx[1]], swap_index[0], swap_index[1])
if calculate_distance(records[idx[0]]) < calculate_distance(records[idx[1]]):
new_records.append(records[idx[0]])
else:
new_records.append(records[idx[1]])
records = copy.deepcopy(new_records)
# 去重
for j in range(gen_num):
for jj in range(j + 1, gen_num):
if records[jj] == records[j]:
random.shuffle(records[jj])
for jjj in range(1, len(records[jj])):
if records[jj][jjj] == 0:
if records[jj][0]:
swap(records[jj], 0, jjj)
else:
swap(records[jj], city_num, jjj)
# 优化
for j in range(gen_num):
records[j] = copy.deepcopy(improve(records[j]))
# 记录最优解
for j in range(gen_num):
if min_distance > calculate_distance(records[j]):
min_distance = calculate_distance(records[j])
min_record = copy.deepcopy(records[j])
print(min_record, min_distance)
结果
每一代100个个体,经过100次迭代,找到了TSP问题的最优解,与贪心等启发算法相比,智能算法的全局性更强。另外,若是不加局部优化,那么求解效果会非常差,局部优化对于种群进化非常关键,如果不加局部优化那么每个个体的值都很差,子代的表现大概率也不会太好。
[0, 11, 21, 23, 22, 24, 17, 19, 18, 15, 12, 9, 8, 6, 7, 1, 2, 3, 5, 4, 10, 16, 25, 20, 13, 14, 0] 1390
[0, 14, 11, 12, 16, 21, 20, 7, 5, 3, 4, 6, 9, 13, 10, 8, 15, 18, 19, 17, 25, 23, 22, 24, 2, 1, 0] 1310
[0, 11, 21, 22, 23, 24, 25, 17, 18, 19, 16, 20, 15, 4, 5, 3, 2, 13, 10, 12, 14, 9, 8, 7, 6, 1, 0] 1239
[0, 14, 8, 15, 18, 17, 16, 9, 4, 5, 3, 6, 7, 19, 25, 22, 23, 24, 21, 20, 11, 10, 12, 13, 2, 1, 0] 1200
[0, 1, 6, 7, 15, 9, 10, 12, 14, 13, 2, 4, 3, 5, 8, 16, 20, 21, 24, 23, 22, 25, 17, 19, 18, 11, 0] 1192
[0, 1, 3, 5, 4, 2, 9, 16, 17, 19, 18, 15, 25, 22, 23, 24, 21, 20, 11, 12, 10, 8, 7, 6, 13, 14, 0] 1139
[0, 14, 2, 1, 3, 5, 4, 6, 7, 15, 18, 19, 17, 16, 20, 21, 25, 22, 23, 24, 11, 12, 10, 8, 9, 13, 0] 1018
[0, 14, 13, 9, 10, 12, 11, 20, 21, 24, 23, 22, 25, 17, 16, 19, 18, 15, 8, 7, 6, 4, 5, 3, 2, 1, 0] 983
[0, 24, 23, 22, 21, 25, 20, 16, 17, 19, 18, 15, 10, 12, 11, 14, 13, 9, 8, 7, 6, 4, 5, 3, 2, 1, 0] 940
[0, 24, 23, 22, 25, 21, 20, 16, 17, 19, 18, 15, 10, 12, 11, 14, 13, 9, 8, 7, 6, 4, 5, 3, 2, 1, 0] 937