# -*- coding: UTF-8 -*-
"""
问题:
从某物流中心用多台配送车辆向多个客户送货,每个客户的位置和货物需求量一定,每台配送车辆的载重量一定,其一次配送的最大行驶距离一定,要求合理安排车辆配送路线,使目标函数得到优化,并满足以下条件:
(1) 每条配送路径上各客户的需求量之和不超过配送车辆的载重量;
(2) 每条配送路径的长度不超过配送车辆一次配送的最大行驶距离;
(3) 每个客户的需求必须满足,且只能由一台配送车辆送货。
以配送总里程最短为目标函数
"""
"""
一个实例:
某物流中心有2 台配送车辆,其载重量均为8t ,车辆每次配送的最大行驶距离为50km ,配送中心(其编号为0) 与8 个客户之间及8 个客户相互之间的距离dij 、8 个客户的货物需求量qj (i 、j = 1 ,2 , ⋯,8) 均见表1 。要求合理安排车辆配送路线,使配送总里程最短。
采用以下参数:群体规模取20 ,进化代数取25 ,交叉概率取0.9 ,变异概率取0.09 ,变异时基因换位次数取5 , 对不可行路径的惩罚权重取100km ,实施爬山操作时爬山次数取20 。对实例随机求解10 次。
"""
import random
#遗传算法
class GeneticAlgorithm:
#-----------初始数据定义---------------------
#定义一个9 * 9的二维数组表示配送中心(编号为0)与8个客户之间,以及8个客户相互之间的距离d[i][j]
d = [[0, 4, 6, 7.5, 9, 20, 10, 16, 8], #配送中心(编号为0)到8个客户送货点的距离
[4, 0, 6.5, 4, 10, 5, 7.5, 11, 10], #第1个客户到配送中心和其他8个客户送货点的距离
[6, 6.5, 0, 7.5, 10, 10, 7.5, 7.5, 7.5], #第2个客户到配送中心和其他8个客户送货点的距离
[7.5, 4, 7.5, 0, 10, 5, 9, 9, 15],
[9, 10, 10, 10, 0, 10, 7.5, 7.5, 10 ],
[20, 5, 10, 5, 10, 0, 7, 9, 7.5],
[10, 7.5, 7.5, 9, 7.5, 7, 0, 7, 10],
[16, 11, 7.5, 9, 7.5, 9, 7, 0, 10],
[8, 10, 7.5, 15, 10, 7.5, 10, 10, 0]];
# 8个客户分布需要的货物的需求量,第0位为配送中心自己
q = [0, 1, 2, 1, 2, 1, 4, 2, 2];
#定义一些遗传算法需要的参数
JCL = 0.9 #遗传时的交叉率
BYL = 0.09 #遗传时的变异率
JYHW = 5 #变异时的基因换位次数
PSCS = 20 #爬山算法时的迭代次数
def __init__(self, rows, times, mans, cars, tons, distance, PW):
self.rows = rows #排列个数
self.times = times #迭代次数
self.mans = mans #客户数量
self.cars = cars #车辆总数
self.tons = tons #车辆载重
self.distance = distance #车辆一次行驶的最大距离
self.PW = PW #当生成一个不可行路线时的惩罚因子
#-------------遗传函数开始执行---------------------
def run(self):
print ("开始迭代")
#路线数组
lines = [[0 for i in range(self.mans)] for i in range(self.rows)]
#适应度
fit = [0 for i in range(self.rows)]
# print "初始输入获取rows个随机排列,并且计算适应度"
#初始输入获取rows个随机排列,并且计算适应度
j = 0
for i in range(0, self.rows):
j = 0
while j < self.mans:
num = int(random.uniform(0, self.mans)) + 1
if self.isHas(lines[i], num) == False:
lines[i][j] = num
j += 1
#计算每个线路的适应度
# print "计算每个线路的适应度 i = %d" % (i)
fit[i] = self.calFitness(lines[i], False)
#迭代次数
t = 0
while t < self.times:
#适应度
newLines = [[0 for i in range(self.mans)] for i in range(self.rows)]
nextFit = [0 for i in range(self.rows)]
randomFit = [0 for i in range(self.rows)]
totalFit = 0
tmpFit = 0
# print "计算总的适应度"
#计算总的适应度
for i in range(self.rows):
totalFit += fit[i]
# print "通过适应度占总适应度的比例生成随机适应度"
#通过适应度占总适应度的比例生成随机适应度
for i in range(self.rows):
randomFit[i] = tmpFit + fit[i] / totalFit
tmpFit += randomFit[i]
# print "上一代中的最优直接遗传到下一代"
#上一代中的最优直接遗传到下一代
m = fit[0]
ml = 0
for i in range(self.rows):
if m < fit[i]:
m = fit[i]
ml = i
for i in range(self.mans):
newLines[0][i] = lines[ml][i]
nextFit[0] = fit[ml]
# print "对最优解使用爬山算法促使其自我进化"
#对最优解使用爬山算法促使其自我进化
self.clMountain(newLines[0])
# print "开始遗传"
#开始遗传
nl = 1
while nl < self.rows:
#根据概率选取排列
r = int(self.randomSelect(randomFit))
#判断是否需要交叉,不能越界
if random.random() < self.JCL and nl + 1 < self.rows:
fline = [0 for x in range(self.mans)]
nline = [0 for x in range(self.mans)]
#获取交叉排列
rn = int(self.randomSelect(randomFit))
f = int(random.uniform(0, self.mans))
l = int(random.uniform(0, self.mans))
min = 0
max = 0
fpo = 0
npo = 0
if f < l:
min = f
max = l
else:
min = l
max = f
# print "将截取的段加入新生成的基因"
#将截取的段加入新生成的基因
"""
除排在第一位的最优个体外,另N - 1 个个体要按交叉概率Pc 进行配对交叉重组。
采用类OX法实施交叉操作,现举例说明其操作方法:
①随机在父代个体中选择一个交配区域,如两父代个体及交配区域选定为:A = 47| 8563| 921 ,B = 83| 4691|257 ;
②将B 的交配区域加到A 的前面,A 的交配区域加到B 的前面,得:A’= 4691| 478563921 ,B’=8563| 834691257 ;
③在A’、B’中自交配区域后依次删除与交配区相同的自然数,得到最终的两个体为:A”= 469178532 ,B”= 856349127
"""
while min < max:
fline[fpo] = lines[rn][min]
nline[npo] = lines[r][min]
min += 1
fpo += 1
npo += 1
for i in range(self.mans):
if self.isHas(fline, lines[r][i]) == False:
fline[fpo] = lines[r][i]
fpo += 1
if self.isHas(nline, lines[rn][i]) == False:
nline[npo] = lines[rn][i]
npo += 1
#基因变异
self.change(fline)
self.change(nline)
# print "交叉并且变异后的结果加入下一代"
#交叉并且变异后的结果加入下一代
for i in range(self.mans):
newLines[nl][i] = fline[i]
newLines[nl + 1][i] = nline[i]
nextFit[nl] = self.calFitness(fline, False)
nextFit[nl + 1] = self.calFitness(nline, False)
nl += 2
else:
# print "不需要交叉的,直接变异,然后遗传到下一代"
#不需要交叉的,直接变异,然后遗传到下一代
line = [0 for i in range(self.mans)]
i = 0
while i < self.mans:
line[i] = lines[r][i]
i += 1
#基因变异
self.change(line)
#加入下一代
i = 0
while i < self.mans:
newLines[nl][i] = line[i]
i += 1
nextFit[nl] = self.calFitness(line, False)
nl += 1
# print "新的一代覆盖上一代 当前是第 %d 代" %(t)
#新的一代覆盖上一代
for i in range(self.rows):
for h in range(self.mans):
lines[i][h] = newLines[i][h]
fit[i] = nextFit[i]
t += 1
#上代中最优的为适应函数最小的
m = fit[0]
ml = 0
for i in range(self.rows):
if m < fit[i]:
m = fit[i]
ml = i
print ("迭代完成")
#输出结果:
self.calFitness(lines[ml], True)
print ("最优权值为: %f" %(m))
print ("最优结果为:")
for i in range(self.mans):
print ("%d," %(lines[ml][i]),)
#-----------------遗传函数执行完成--------------------
#-----------------各种辅助计算函数--------------------
#线路中是否包含当前的客户
def isHas(self, line, num):
for i in range(0, self.mans):
if line[i] == num:
return True
return False
#计算适应度,适应度计算的规则为每条配送路径要满足题设条件,并且目标函数即 车辆行驶的总里程越小,适应度越高
def calFitness(self, line, isShow):
carTon = 0 #当前车辆的载重
carDis = 0 #当前车辆行驶的总距离
newTon = 0
newDis = 0
totalDis = 0
# ll = []
r = 0 #表示当前需要车辆数
# l = 0
fore = 0 #表示正在运送的客户编号
M = 0 #表示当前的路径规划所需要的总车辆和总共拥有的车辆之间的差,如果大于0,表示是一个失败的规划,乘以一个很大的惩罚因子用来降低适应度
#遍历每个客户点
for i in range(0, self.mans):
#行驶的距离
newDis = carDis + self.d[fore][line[i]]
#当前车辆的载重
newTon = carTon + self.q[line[i]]
#如果已经超过最大行驶距离或者超过车辆的最大载重,切换到下一辆车
if newDis + self.d[line[i]][0] > self.distance or newTon > self.tons:
#下一辆车
totalDis += carDis + self.d[fore][0] #后面加这个d[fore][0]表示需要从当前客户处返程的距离
r += 1
fore = 0
i -= 1 #表示当前这个点的配送还没有完成
carTon = 0
carDis = 0
else:
carDis = newDis
carTon = newTon
fore = line[i]
#加上最后一辆车的距离和返程的距离
totalDis += carDis + self.d[fore][0]
if isShow:
print ("总行驶里程为: %.1fkm" %(totalDis))
else:
# print "中间过程尝试规划的总行驶里程为: %.1fkm" %(totalDis)
pass
#判断路径是否可用,所使用的车辆数量不能大于总车辆数量
if r - self.cars + 1 > 0:
M = r - self.cars + 1
#目标函数,表示一个路径规划行驶的总距离的倒数越小越好
result = 1 / (totalDis + M * self.PW)
return result
#爬山算法
def clMountain(self, line):
oldFit = self.calFitness(line, False)
i = 0
while i < self.PSCS:
f = random.uniform(0, self.mans)
n = random.uniform(0, self.mans)
self.doChange(line, f, n)
newFit = self.calFitness(line, False)
if newFit < oldFit:
self.doChange(line, f, n)
i += 1
#基因变异
#变异的意思是当满足变异率的条件下,随机的两个因子发生多次交换,交换次数为变异迭代次数规定的次数
def change(self, line):
if random.random() < self.BYL:
i = 0
while i < self.JYHW:
f = random.uniform(0, self.mans)
n = random.uniform(0, self.mans)
self.doChange(line, f, n)
i += 1
#将线路中的两个因子执行交换
def doChange(self, line, f, n):
tmp = line[int(f)]
line[int(f)] = line[int(n)]
line[int(n)] = tmp
#根据概率随机选择的序列
def randomSelect(self, ranFit):
ran = random.random()
for i in range(self.rows):
if ran < ranFit[i]:
return i
#-------------入口函数,开始执行-----------------------------
"""
输入参数的的意义依次为
self.rows = rows #排列个数
self.times = times #迭代次数
self.mans = mans #客户数量
self.cars = cars #车辆总数
self.tons = tons #车辆载重
self.distance = distance #车辆一次行驶的最大距离
self.PW = PW #当生成一个不可行路线时的惩罚因子
"""
ga = GeneticAlgorithm(rows=20, times=25, mans=8, cars=3, tons=8, distance=50, PW=100)
for i in range(20):
print ("第 %d 次:" %(i + 1))
ga.run()