旅行商问题是一个经典的组合优化问题,它的提出和研究有着悠久的历史。最早的描述是1759年欧拉研究的骑士环游问题,即对于国际象棋棋盘中的64个方格,走访64个方格一次且仅一次,并且最终返回到起始点。1954年,Dantzig等人用线性规划的方法取得了旅行商问题的历史性的突破——解决了美国49个城市的巡回问题。
旅行商问题在现实生活中有很多应用场景,如飞机航线的安排、公路网络的建设、物流货物配送等。由于这些场景中涉及到大量城市或地点之间的距离或成本,如果要求出精确解需要枚举所有可能路径并比较其总和,这样计算量会随着城市数量呈指数级增长,非常耗时甚至无法完成,它是组合优化中的NP困难问题。因此,人们寻找各种近似算法或启发式算法来求解该问题。蚁群算法(Ant Colony Algorithm)是一种仿生算法,最开始由意大利学者通过观察蚂蚁觅食,得到的2。它的基本思想是模拟蚂蚁在寻找食物时所留下的信息素,并根据信息素浓度来指导自己行走方向的过程。
下面的程序按照标准的蚂蚁算法编写,共人工智能专业的同学们对照、参考。
import numpy as np
import matplotlib.pyplot as plt
cities = [] # 为代码简单,使用全局变量。cities 储存城市的列表
city_dis_matrix = [] # 储存城市之间距离的二维列表
tau_mat = [] # 储存信息素的列表。不必单独定义启发因子矩阵
def read_cities(dat_file): # 读入dat_file 中的数据,即数据本地化。将数据读成列表格式,来自教材程序。最好是直接读成np.array格式。
with open(dat_file, 'rt') as f:
temp_list = list(line.split() for line in f)
return [list(map(int,k)) for k in temp_list] # 测试成功读入,读成二维列表。形式如[[1,2],[3,4],...] 还有方法 np.genfromtxt,没有试
def dist_between(i,j): # 两个城市距离,城市形式是i ,j 是[2,3],这样的列表。不是,i, j 就是 2,3这样的整数,指的是cities的第 i j个城市
# 对城市的指称不统一,以后说那个城市,就是指相对于城市列表 cities 的第几个,所以
t_vector = np.array(cities[i]) - np.array(cities[j])
return np.linalg.norm(t_vector)
def init_dis_hpero_mat(): # list_city的格式是:[[1,2],[5,2],...],初始化城市之间的距离和信息素矩阵
cities_num = len(cities) # 1. 获取城市个数
city_dist_mat = np.ones([cities_num, cities_num]) # 先生成一个1 阵,然后修改成距离,构建城市距离矩阵
tau_mat = np.ones([cities_num, cities_num]) # 初始化信息素矩阵,全是 1。
for i in range(cities_num):
for j in range(i+1, cities_num):
city_dist_mat[i][j] = city_dist_mat[j][i] = dist_between(i,j)
tau_mat[i][j] = tau_mat[j][i] = 1/dist_between(i,j)**2 # 初始化信息矩阵,距离倒数
# tau_mat = np.ones([cities_num, cities_num]) # 初始化信息素矩阵,全是 1。
return city_dist_mat, tau_mat
class ant():
def __init__(self,startcity): #初始化蚂蚁,主要是指定蚂蚁的起始位置 startpint 的格式是 1, 2 这样的整数,是指cities的第 1,2个城市
self.start = startcity # 蚂蚁出发的城市,是整数
self.current = startcity # 蚂蚁移动,当前所在的城市
self.allowed = [] # 蚂蚁可运行访问的城市
self.path = [startcity] # 记录蚂蚁走过的路线 path 是 [3,1,4]这样的列表,表示从走过第3,1,4个城市
self.length = 0 # 记录蚂蚁走过的路长。
def reset(self):
self.allowed = [i for i in range(len(cities))] # 蚂蚁周游一圈后需要重置allowed,路程和路径都要归 0 和空
self.length = 0
self.path = []
def deal_chosed_city(self,chosed_city): # 选择一个城市后,要做的处理。选择城市即移动到选定的城市。chosed_city是整数
self.length += dist_between(self.current,chosed_city) # 累加这段路长。
self.path.append(chosed_city) # 并将其放入蚂蚁走过的路径。
self.current = chosed_city # 蚂蚁的当前城市
return None
def choose_city(self,alp = 1,beta = 3): # 在当前城市current,按概率选择下一个城市。要访问距离矩阵和信息矩阵
self.allowed.remove(self.current)
s = 0
list_of_j = [] # 记录城市
list_of_s = [] # 记录,和那个公式有关,但是没有单位化。
for j in self.allowed:
s = s + 10**8*tau_mat[self.current][j]**alp*city_dis_matrix[self.current][j]**(-beta) # 一段段计算公式的那个数,没有分母。
list_of_s.append(s)
list_of_j.append(j)
point = np.random.uniform(0, list_of_s[-1]) # 生成一个随机数
k = 0
while k < len(list_of_j): # 判断随机数落在那个范围,就选哪个 j。
if point <= list_of_s[k]:
return list_of_j[k]
else:
k += 1
def update_tau_mat(self,pre,cur):
# 此处更新信息素,蚂蚁走完从 pre 到 cur 的路径,更新路径信息素。
delta = 1/city_dis_matrix[pre][cur] # 此处相当 Q = 1。
return delta
# 主程序 记住城市用 i 整数表示,表示的是cities的第 i 个城市。
if __name__ == "__main__":
print('begin!')
ant_num = 100 # 蚂蚁数
allants = [] # 记录全部生成的蚂蚁的列表
Max_inter = 1000 #5 # 周游圈数。
rho = 0.1 # 信息素蒸发率
filename = r'city_coordinates.dat'
cities = read_cities(filename) # 读入城市数据,存在列表cities中。
city_dis_matrix, tau_mat = init_dis_hpero_mat() # 根据城市列表初始化 距离矩阵,信息素矩阵
city_num = len(cities)
min_length = float('inf')
min_path = []
for k in range(ant_num): # 实例化蚂蚁,并随机放在各个城市。
random_city = np.random.randint(0,city_num) # 随机指定一个城市
the_ant = ant(random_city) # 初始化蚂蚁,把蚂蚁放在这个城市
the_ant.reset() # 设置每个蚂蚁的可允许访问的城市。
allants.append(the_ant) # 示例化的蚂蚁全部放到列表 allants 中
i_th = 0 # 第一层循环的初始化
while i_th < Max_inter: # 蚂蚁周游圈数的循环
city_th = 0 # 第二层循环的初始化
while city_th <= city_num - 1: #遍历全部城市的循环
delta_mat = np.zeros([city_num,city_num]) # 第三层循环初始化,delta_mat 记录蚂蚁本次移动信息素增量的矩阵,每走一个城市需要置 0。
for i_ant in allants:
if city_th == city_num - 1: # 到了最后一个城市,需要回到起点。周游一圈的事情在这里处理。
nextcity = i_ant.start # 直接选择初始城市为下一个城市。
delta_mat[currentcity][nextcity] = delta_mat[currentcity][nextcity] + i_ant.update_tau_mat(currentcity, nextcity)
delta_mat[nextcity][currentcity] = delta_mat[nextcity][currentcity] + i_ant.update_tau_mat(currentcity, nextcity)
i_ant.deal_chosed_city(nextcity) # 把next_city设为蚂蚁的当前城市,即蚂蚁回到原来的出发城市。
if i_ant.length < min_length:
min_length = i_ant.length
min_path = i_ant.path
i_ant.reset()
else:
currentcity = i_ant.current # 蚂蚁当前位置在蚂蚁内部的变量记录,取出
nextcity = i_ant.choose_city(currentcity) # 在当前位置选择下一个城市,
delta_mat[currentcity][nextcity] = delta_mat[currentcity][nextcity] + i_ant.update_tau_mat(currentcity, nextcity)
delta_mat[nextcity][currentcity] = delta_mat[nextcity][currentcity] + i_ant.update_tau_mat(currentcity, nextcity)
# 选中的next_city的意思就是移动到next_city,计算路径信息素的增量并累加在delta_mat 中
i_ant.deal_chosed_city(nextcity) # 把next_city设为蚂蚁的当前城市。
tau_mat = (1-rho)*tau_mat + delta_mat # 用累加的增量矩阵更新信息素矩阵。这里是矩阵运算,验证可行否?
city_th += 1
i_th += 1
print('最短路径是{},最短路径长度是{}'.format(min_path,min_length))
# 图示结果
xc, yc = [x[0] for x in cities], [x[1] for x in cities]
plt.plot(xc, yc, '.', color='blue')
geo_path = [cities[k] for k in min_path] # 路径的坐标
pxc,pyc = [x[0] for x in geo_path], [x[1] for x in geo_path]
plt.plot(pxc, pyc, color='red')
plt.show()
程序应用的数据文件如下,将其复制,在一个文本编辑器中粘贴,并保存为 city_coordinates.dat。
1304 2312
3639 1315
4177 2244
3712 1399
3488 1535
3326 1556
3238 1229
4196 1004
4312 790
4386 570
3007 1970
2562 1756
2788 1491
2381 1676
1332 695
3715 1678
3918 2179
4061 2370
3780 2212
3676 2578
4029 2838
4263 2931
3429 1908
3507 2367
3394 2643
3439 3201
2935 3240
3140 3550
2545 2357
2778 2826
2370 2975