原始数据
计算从城市1出发,通过每个城市,最终回到城市1的最短距离,及路径
city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
[5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
[9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
[13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
[17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
[21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
[25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
[29, (360.0, 1980.0)]]
路径开头和结尾都是1,只要全排列2,…,29看怎么排列路径最短即可。
遗传算法
遗传算法_geatpy
写好代价函数和参数,直接调用geatpy包。
代码
# -*- coding: utf-8 -*-
"""MyProblem.py"""
import geatpy as ea
import numpy as np
city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
[5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
[9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
[13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
[17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
[21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
[25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
[29, (360.0, 1980.0)]]
x_point_list = [i[1][0] for i in city_list] # x坐标
y_point_list = [i[1][1] for i in city_list] # y坐标
# 计算两城市间的距离
def city_distance(city1, city2):
distance = ((float(x_point_list[city1] - x_point_list[city2])) ** 2 + (
float(y_point_list[city1] - y_point_list[city2])) ** 2) ** 0.5
return distance
# 计算线路路径长度
def aim_func(road_list):
current_point = 0
dist = 0
for i in road_list:
next_point = i - 1
dist += city_distance(current_point, next_point)
current_point = i - 1
dist += city_distance(current_point, 0)
return dist
class MyProblem(ea.Problem): # 继承Problem父类
def __init__(self):
name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
M = 1 # 初始化M(目标维数)
maxormins = [1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)
Dim = len(city_list) - 1 # 初始化Dim(决策变量维数)
varTypes = [1] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的实数;1表示是离散的整数)
lb = [2] * Dim # 决策变量下界
ub = [Dim + 1] * Dim # 决策变量上界
lbin = [1] * Dim # 决策变量下边界,1表示包含上边界;0表示不包含上边界
ubin = [1] * Dim # 决策变量上边界
# 调用父类构造方法完成实例化
ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
def aimFunc(self, pop): # 目标函数
X = pop.Phen
f = np.array([aim_func(road_list) for road_list in X]).reshape(X.shape[0], 1)
pop.ObjV = f # 计算目标函数值,赋值给pop种群对象的ObjV属性
# -*- coding: utf-8 -*-
"""main.py"""
import geatpy as ea
from MyProblem import * # 导入自定义问题接口
# 画线路图
def draw_line_pic(route, cost):
import matplotlib.pyplot as plt
x = []
y = []
route = list(route)
route.append(1)
route.insert(0,1)
for item in route:
x.append(x_point_list[item-1])
y.append(y_point_list[item-1])
plt.plot(x, y)
plt.scatter(x_point_list, y_point_list, marker="o", c='g')
plt.scatter(x_point_list[0], y_point_list[0], marker="o", c="r")
for i in range(len(city_list)):
plt.text(x_point_list[i], y_point_list[i], str(i+1), ha='center', va='bottom', fontsize=10)
plt.title("GA_Search: " + str(cost))
plt.plot([1,2],[1,2])
plt.show()
for i in range(1):
print(str(i)+"次")
"""===============================实例化问题对象================================"""
problem = MyProblem() # 生成问题对象
"""==================================种群设置=================================="""
Encoding = 'P' # 编码方式
NIND = 500 # 种群规模
Field = ea.crtfld(Encoding=Encoding, varTypes=problem.varTypes, ranges=problem.ranges, borders=problem.borders) # 创建区域描述器
population = ea.Population(Encoding, Field, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
"""================================算法参数设置================================="""
myAlgorithm = ea.soea_SEGA_templet(problem, population) # 实例化一个算法模板对象_精英保留的遗传算法
myAlgorithm.MAXGEN = 500 # 最大进化代数
myAlgorithm.mutOper.Pm = 0.8 # 变异概率
myAlgorithm.logTras = 10 # 设置每隔多少代记录日志,若设置成0则表示不记录日志
myAlgorithm.verbose = True # 设置是否打印输出日志信息
myAlgorithm.drawing = 1 # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画)
"""===========================调用算法模板进行种群进化==============--==========="""
[BestIndi, population] = myAlgorithm.run() # 执行算法模板,得到最优个体以及最后一代种群
BestIndi.save() # 把最优个体的信息保存到文件中
"""==================================输出结果=================================="""
print('用时:%f 秒' % myAlgorithm.passTime)
print('评价次数:%d 次' % myAlgorithm.evalsNum)
if BestIndi.sizes != 0:
print('最优的目标函数值为:%s' % BestIndi.ObjV[0][0])
print('最优的控制变量值为:')
road_list=BestIndi.Phen[::]
print("最优路径:",road_list)
draw_line_pic(road_list[0], BestIndi.ObjV[0][0])
finally_route_list = []
for i in road_list[0]:
finally_route_list.append([i, (x_point_list[i - 1], y_point_list[i - 1])]) # 最终路径
else:
print('没找到可行解。')
结果
用时:10.537014 秒
评价次数:250000 次
最优的目标函数值为:9076.982920396536
最优的控制变量值为:
最优路径: [1 28 6 12 9 26 3 29 5 21 2 20 10 13 4 15 18 14 17 22 11 19 25 7 23
8 27 16 24 1]
禁忌搜索
TS算法受禁忌表长度、邻域选择方式、以及初始值的影响比较 严重,且经检验极易陷入局部最优值(不知道是不是参数设置原因),且耗时较长。
代码
用python给出TS解TSP问题的代码
# -*- coding: utf-8 -*-
# @Time : 2022/2/17 15:01
# @Author : Orange
# @File : ts1.py.py
import copy, random, datetime
import matplotlib.pyplot as plt
import time
city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
[5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
[9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
[13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
[17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
[21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
[25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
[29, (360.0, 1980.0)]
]
class Taboo_search:
def __init__(self, city_list, candidate_count, taboo_list_length, iteration_count, is_random=True):
self.city_list = city_list # 城市列表
self.candidate_count = candidate_count # 候选集合长度
self.taboo_list_length = taboo_list_length # 禁忌长度
self.iteration_count = iteration_count # 迭代次数
self.min_route, self.min_cost = self.random_first_full_road() if is_random else self.greedy_first_full_road() # 最小解;最小目标值
self.taboo_list = [] # 禁忌表
# 计算两城市间的距离
def city_distance(self, city1, city2):
distance = ((float(city1[1][0] - city2[1][0])) ** 2 + (float(city1[1][1] - city2[1][1])) ** 2) ** 0.5
return distance
# 获取当前城市邻居城市中距离最短的一个
def next_shotest_road(self, city1, other_cities):
tmp_min = 999999
tmp_next = None
for i in range(0, len(other_cities)):
distance = self.city_distance(city1, other_cities[i])
# print(distance)
if distance < tmp_min:
tmp_min = distance
tmp_next = other_cities[i]
return tmp_next, tmp_min
# 随机生成初始线路
def random_first_full_road(self):
cities = copy.deepcopy(self.city_list)
cities.remove(cities[0])
route = copy.deepcopy(cities)
random.shuffle(route)
# route = [[6, (1030.0, 2070.0)], [5, (750.0, 2030.0)], [29, (360.0, 1980.0)], [3, (40.0, 2090.0)],
# [26, (490.0, 2130.0)], [9, (790.0, 2260.0)], [12, (1170.0, 2300.0)], [28, (1260.0, 1910.0)],
# [8, (1490.0, 1630.0)], [27, (1460.0, 1420.0)], [24, (1260.0, 1500.0)], [13, (970.0, 1340.0)],
# [16, (1280.0, 1200.0)], [23, (1840.0, 1240.0)], [7, (1650.0, 650.0)], [25, (1280.0, 790.0)],
# [19, (1040.0, 950.0)], [11, (840.0, 550.0)], [22, (490.0, 500.0)], [17, (230.0, 590.0)],
# [14, (510.0, 700.0)], [18, (460.0, 860.0)], [15, (750.0, 900.0)], [4, (750.0, 1100.0)],
# [10, (710.0, 1310.0)], [20, (590.0, 1390.0)], [2, (630.0, 1660.0)],
# [21, (830.0, 1770.0)]] # 将初始值设置为遗传算法的最优值
cost = self.route_cost(route)
return route, cost
# 根据贪婪算法获取初始线路
def greedy_first_full_road(self):
remain_city = copy.deepcopy(self.city_list)
current_city = remain_city[0]
road_list = []
remain_city.remove(current_city)
all_distance = 0
while len(remain_city) > 0:
next_city, distance = self.next_shotest_road(current_city, remain_city)
all_distance += distance
road_list.append(next_city)
remain_city.remove(next_city)
current_city = next_city
all_distance += self.city_distance(self.city_list[0], road_list[-1])
return road_list, round(all_distance, 2)
# 随机交换2个城市位置
def random_swap_2_city(self, route):
# print(route)
road_list = copy.deepcopy(route)
two_rand_city = random.sample(road_list, 2)
# print(two_rand_city)
index_a = road_list.index(two_rand_city[0])
index_b = road_list.index(two_rand_city[1])
road_list[index_a] = two_rand_city[1]
road_list[index_b] = two_rand_city[0]
return road_list, sorted(two_rand_city)
# 计算线路路径长度
def route_cost(self, route):
road_list = copy.deepcopy(route)
current_city = self.city_list[0]
while current_city in road_list:
road_list.remove(current_city)
all_distance = 0
while len(road_list) > 0:
distance = self.city_distance(current_city, road_list[0])
all_distance += distance
current_city = road_list[0]
road_list.remove(current_city)
all_distance += self.city_distance(current_city, self.city_list[0])
return round(all_distance, 2)
# 获取下一条线路
def single_search(self, route):
# 生成候选集合列表和其对应的移动列表
candidate_list = []
candidate_move_list = []
while len(candidate_list) < self.candidate_count: # 在候选集合里放candidate_count条不重复路径
tmp_route, tmp_move = self.random_swap_2_city(route)
# print("tmp_route:",tmp_route)
if tmp_route not in candidate_list:
candidate_list.append(tmp_route)
candidate_move_list.append(tmp_move)
# 计算候选集合各路径的长度
candidate_cost_list = []
for candidate in candidate_list:
candidate_cost_list.append(self.route_cost(candidate))
# print(candidate_list)
min_candidate_cost = min(candidate_cost_list) # 候选集合中最短路径
min_candidate_index = candidate_cost_list.index(min_candidate_cost)
min_candidate = candidate_list[min_candidate_index] # 候选集合中最短路径对应的线路
move_city = candidate_move_list[min_candidate_index]
if min_candidate_cost < self.min_cost:
# 若满足这个条件不管禁忌对象是否在禁忌表内,都直接更新禁忌表
self.min_cost = min_candidate_cost
self.min_route = min_candidate
if move_city in self.taboo_list: # 破禁法则,当此移动导致的值更优,则无视该禁忌列表
self.taboo_list.remove(move_city)
if len(self.taboo_list) >= self.taboo_list_length: # 判断该禁忌列表长度是否以达到限制,是的话移除最初始的move
self.taboo_list.remove(self.taboo_list[0])
self.taboo_list.append(move_city) # 将该move加入到禁忌列表
return min_candidate
else:
# 当未找到更优路径时,选择次优路线,如果该次优路线在禁忌表里,则更次一层,依次类推,找到一条次优路线
if move_city in self.taboo_list:
tmp_min_candidate = min_candidate
tmp_move_city = move_city
while move_city in self.taboo_list: # 若候选最优禁忌对象已经在T表,寻找次优禁忌对象,若已在T表,.....
candidate_list.remove(min_candidate)
candidate_cost_list.remove(min_candidate_cost)
candidate_move_list.remove(move_city)
min_candidate_cost = min(candidate_cost_list) # 候选集合中次优路径
min_candidate_index = candidate_cost_list.index(min_candidate_cost)
min_candidate = candidate_list[min_candidate_index] # 候选集合中最短路径对应的线路
move_city = candidate_move_list[min_candidate_index]
if len(candidate_list) < 10: # 防止陷入死循环,在候选集个数小于10的时候跳出
min_candidate = tmp_min_candidate
move_city = tmp_move_city
if len(self.taboo_list) >= self.taboo_list_length: # 判断该禁忌列表长度是否以达到限制,是的话移除最初始的move
self.taboo_list.remove(self.taboo_list[0])
self.taboo_list.append(move_city)
return min_candidate
# 进行taboo_search直到达到终止条件:循环100次
def taboo_search(self):
route = copy.deepcopy(self.min_route)
for i in range(self.iteration_count):
route = self.single_search(route)
new_route = [self.city_list[0]]
new_route.extend(self.min_route)
new_route.append(self.city_list[0]) # 前后插入首个城市信息
return new_route, self.min_cost
# 画线路图
def draw_line_pic(route, cost, duration, desc):
x = []
y = []
for item in route:
x.append(item[1][0])
y.append(item[1][1])
x_org = []
y_org = []
point_org = []
for item in city_list:
x_org.append(item[1][0])
y_org.append(item[1][1])
point_org.append(item[0])
x0 = [x[0], ]
y0 = [y[0], ]
plt.plot(x, y)
plt.scatter(x_org, y_org, marker="o", c='g')
plt.scatter(x0, y0, marker="o", c="r")
for i in range(len(city_list)):
plt.text(x_org[i], y_org[i], point_org[i], ha='center', va='bottom', fontsize=10)
plt.title("Taboo_Search(" + desc + ": " + str(cost) + ")")
plt.show()
if __name__ == "__main__":
ts_random = Taboo_search(city_list=city_list, candidate_count=40, taboo_list_length=3, iteration_count=4000)
# ts_greedy = Taboo_search(city_list, candidate_count=40, taboo_list_length=3, iteration_count=4000,
# is_random=False)
start_time1 = time.time()
route_random, cost_random = ts_random.taboo_search()
end_time1 = time.time()
duration1 = (end_time1 - start_time1)
# route_greedy, cost_greedy = ts_greedy.taboo_search()
draw_line_pic(route_random, cost_random, duration1, "random")
print("最优路径:", [i[0] for i in route_random])
print("最短距离:", cost_random)
print("随机TS耗时:",end_time1-start_time1)
# draw_line_pic(route_greedy, cost_greedy, None, "greedy")
结果
最优路径: [1, 28, 12, 6, 9, 26, 3, 29, 5, 21, 2, 20, 10, 13, 4, 15, 18, 14, 17, 22, 11, 19, 25, 7, 23, 27, 16, 24, 8, 1]
最短距离: 9151.27
随机TS耗时: 43.56393384933472
注:受初始解和候选表长度的影响,TS结果随机性也比较严重,上面是多次中表现最好的一次了。