目录
一、问题描述
某物流企业有一个车场, 车场坐标位置是(10, 22), 企业的每个车的容量是 110。企业共有 30个客户需要被服务,每个客户最多被一辆车访问一次。
存在一个特殊客户的集合: (1, 3, 5, 7, 9, 11, 12, 13, 20, 21, 26, 27, 28, 29, 30),其中:此集合中至少有 10个客户被服务,且子集 (1, 3, 5, 7, 9, 11, 12) 中必须被服务至少有 4个客户; (13, 20, 21, 26, 27, 28, 29, 30) 中必须被服务至少 4个;以上特殊集合 (1, 3, 5, 7, 9, 11, 12, 13, 20, 21, 26, 27, 28, 29, 30) 以外的全部其他客户点,必须被服务(一个车辆访问) 且只被服务一次;
假设任意两点之间都有路线相连,路线距离就是其直线连接距离,保留 2 位小数(后续小数直接截断,例如, 如果长度为 12.08731, 使用数据为 12.08)
运输成本定义为全部车辆的总行驶路径距离,求最小成本的车辆路径
二、问题特点
1、只有一个车场(配送中心)
2、存在特殊客户集合,并不是所有的客户都需要被访问
3、使用的车辆数目不限制
4、每个客户最多被一辆车访问一次
5、车辆存在容量限制
三、数学模型
为了在本模型中加入MTZ约束以消除子环路,避免模型出现infeasible,将车场的初始点设置为point 0,增加虚拟点作为车场的终点,设置为point N+1。(在本题中就是增加了点31)
关于消除子环路可参考这篇文章:《运筹学修炼日记:TSP中两种不同消除子环路的方法及callback实现(Python调用Gurobi求解,附以王者荣耀视角解读callback的工作逻辑)》
1、参数变量设置
2、目标:最小成本的车辆路径(总行驶路径距离)
3、约束
约束部分可参考文章:《CVRP建模与求解-基于粒子群算法(python实现》,并在此文章基础上针对本文的问题做修改和添加约束。
Tips:
- 由于本题中30个点并不是都会被访问,所以在约束中,使用了很多大于等于或小于等于,这点对于考虑特殊集合非常重要。
- 决策变量之间的关系是为了保证,只有当两个客户点都被访问且该车被使用时,决策变量X才有可能为1。(通常我们在解数学模型的时候会遇到求解不可行或解不正确的情况,很大概率可能是决策变量之间缺乏相互约束的问题)
四、Gurobi求解数学模型
- 如何安装下载Gurobi,可参考文章《Gurobi+Python平台的搭建安装》
- 如何将利用Gurobi求解数学模型,可参考文章《Gurobi +Python 高效数学规划及建模实例》
- 如何看懂Gurobi输出的结果,可参考文章《Gurobi教程-从入门到入土-一篇顶万篇》
话不多说,直接上代码。
import gurobipy as gp
from gurobipy import GRB
import numpy as np
import pandas as pd
import copy
#读取数据
df=pd.read_excel('题目3数据.xlsx')#改成自己的文件名
n=df['customer No'].tolist()#节点集合,0到31共32个节点
customer_demand=df['demand'].tolist()#读取列名为“damand”的一列数据,下同
location_x=df['location x'].tolist()
location_y=df['location y'].tolist()
#构建数据结构
N=len(n)-2#客户点数目 30
M=110#车辆容量限制
K=np.arange(1,8)#车辆数目,按所有点都配送的容量(700)看最多需要7辆车
set=[1, 3, 5, 7, 9, 11, 12, 13, 20, 21, 26, 27, 28, 29, 30]
sub_set1=[1, 3, 5, 7, 9, 11, 12]
sub_set2=[13, 20, 21, 26, 27, 28, 29, 30]
set_left=[2,4,6,8,10,14,15,16,17,18,19,22,23,24,25]
#两个节点的距离
d_ij= {}
for i in n:
for j in n:
d_ij[i,j]=np.sqrt(pow(location_x[i]-location_x[j], 2)+pow(location_y[i]-location_y[j], 2))
#某个顾客的需求
m_j={}
for j in n:
m_j[j]=customer_demand[j]
#车辆k从i行驶到j
X_ijk=[]
for i in n[:-1]:
for j in n[1:]:
for k in K:
X_ijk.append((i, j, k))
X_ijk = gp.tuplelist(X_ijk)
#客户点是否被服务
Y_i=copy.deepcopy(n)#0-31
Y_i = gp.tuplelist(Y_i)
#车辆是否被使用
A_k=copy.deepcopy(K)
A_k = gp.tuplelist(A_k)
#消除子路径
u_i=copy.deepcopy(n)#0-31
u_i = gp.tuplelist(u_i)
#模型建立
Model=gp.Model("VRP_gurobi")
#定义变量
X_ijk=Model.addVars(X_ijk,vtype=GRB.BINARY,name="X_ijk")
Y_i=Model.addVars(Y_i,vtype=GRB.BINARY,name="Y_i")
A_k=Model.addVars(A_k,vtype=GRB.BINARY,name="A_k")
u_i=Model.addVars(u_i,vtype=GRB.INTEGER,name="u_i",lb=0)
#更新变量
Model.update()
#目标函数
Model.setObjective(sum(d_ij[i,j] * X_ijk.sum(i,j,"*") for i in n[:-1] for j in n[1:] ),gp.GRB.MINIMIZE)
#客户点服务约束
Model.addConstr(sum(Y_i[i] for i in set )>=10)
Model.addConstr(sum(Y_i[i] for i in sub_set1 )>=4)
Model.addConstr(sum(Y_i[i] for i in sub_set2 )>=4)
Model.addConstr(sum(Y_i[i] for i in set_left )==15)
#配送中心约束:所有车辆均由配送中心出发,完成所有配送任务后返回配送中心
Model.addConstr(sum(X_ijk[(0,j,k)] for j in n[1:] for k in K)==sum(X_ijk[(i,n[-1],k)] for i in n[:-1] for k in K ))
Model.addConstr(sum(X_ijk[(0,j,k)] for j in n[1:] for k in K)== sum(A_k[k] for k in K))
Model.addConstr(sum(X_ijk[(i,n[-1],k)] for i in n[:-1] for k in K )==sum(A_k[k] for k in K))
#车场约束,每辆车每次只能访问一个点
for k in K:
Model.addConstr(sum(X_ijk[(0,j,k)] for j in n[1:])==sum(X_ijk[(i,n[-1],k)] for i in n[:-1]))
Model.addConstr(sum(X_ijk[(0,j,k)] for j in n[1:])<=1)
Model.addConstr(sum(X_ijk[(i,n[-1],k)] for i in n[:-1])<=1)
#客户点服务约束,每个客户只能被服务一次
for j in n[1:-1]:#对于每个客户点来说
Model.addConstr(sum(X_ijk[i,j,k]for i in n[:-1] for k in K )<=1)
Model.addConstr(sum(X_ijk[j,i,k] for i in n[1:] for k in K) <= 1)
Model.addConstr(sum(X_ijk[j, i, k] for i in n[1:] for k in K) == sum(X_ijk[i,j,k]for i in n[:-1] for k in K ))
#客户点流量平衡,进出的车辆数相等
for j in n[1:-1]:
for k in K:
Model.addConstr(sum(X_ijk[i, j, k] for i in n[:-1]) == sum(X_ijk[j, i, k] for i in n[1:]))
Model.addConstr(sum(X_ijk[i, j, k] for i in n[:-1]) <=1)
Model.addConstr(sum(X_ijk[i, j, k] for i in n[:-1]) <=1)
#消除子路径
for i in n[:-1]:
for j in n[1:]:
for k in K:
Model.addConstr(u_i[i]-u_i[j]+N*X_ijk[i,j,k]<=N-1)
Model.addConstr(u_i[i]>=0)
Model.addConstr(u_i[j]>= 0)
#重量约束,每辆车的装载量不能超过最大载重量限制
for k in K:
Model.addConstr(sum((sum(X_ijk[(i,j,k)] for i in n[:-1]) * m_j[j] )for j in n[1:])<=M)
#所需车辆的约束
Model.addConstr(sum(A_k[k] for k in K) >= sum(m_j[j]*Y_i[j] for j in n[1:])/M)
#决策变量约束
for i in n[:-1]:
for j in n[1:]:
for k in K:
if i==n[0] and j==n[-1] :
Model.addConstr(X_ijk[(i, j, k)] == 0)
elif i==j:
Model.addConstr(X_ijk[(i, j, k)] == 0)
else:
Model.addConstr(X_ijk[i, j, k] <= Y_i[i])
Model.addConstr(X_ijk[i, j, k] <= Y_i[j])
Model.addConstr(X_ijk[i, j, k] <= A_k[k])
for i in n[1:-1]:
Model.addConstr(sum(X_ijk[(i, j, k)] for j in n[1:] for k in K) == Y_i[i])
Model.addConstr(sum(X_ijk[(j, i, k)] for j in n[:-1] for k in K) == Y_i[i])
for k in K:
Model.addConstr(sum(X_ijk[(i, j, k)] for i in n[:-1] for j in n[1:]) >=A_k[k])
Model.Params.LogToConsole = True#显示求解过程
Model.Params.TimeLimit = 7200
Model.Params.MIPFocus=3#1侧重快速找到最优解 2侧重证明最优 3侧重界的提升
Model.Params.Heuristics=0.2
# 显示冲突的约束(当模型infeasible时可用这段代码查看冲突约束)
# Model.computeIIS()
# Model.write('model.ilp')
Model.optimize()
print("Optimal Objective Value", Model.objVal)
# 查看所有变量取值
for var in Model.getVars():
if var.X==1:
print(f"{var.varName}: {round(var.X, 3)}")
五,遗传算法求解
1、算法流程图
2、直接上代码,基本都有注释。
import copy
import itertools
import math
import random
import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 添加这条可以让图形显示中文
def calDistance(CityCoordinates):
'''
计算城市间距离
输入:CityCoordinates-城市坐标;
输出:城市间距离矩阵-dis_matrix
'''
dis_matrix = pd.DataFrame(data=None, columns=range(len(CityCoordinates)), index=range(len(CityCoordinates)))
for i in range(len(CityCoordinates)):
xi, yi = CityCoordinates[i][0], CityCoordinates[i][1]
for j in range(len(CityCoordinates)):
xj, yj = CityCoordinates[j][0], CityCoordinates[j][1]
dis_matrix.iloc[i, j] = round(math.sqrt((xi - xj) ** 2 + (yi - yj) ** 2), 2)
return dis_matrix
def choosetwentyfive(setall,set_sub,sub_set1,sub_set2):#选择25个点
new_25 = copy.deepcopy(setall)
set_sub=set(set_sub)
set_1 = set(random.sample(sub_set1, 4))
set_2 = set(random.sample(sub_set2, 4))
set_union = set_1.union(set_2)
set_left = set_sub - set_union
set_3 = set(random.sample(list(set_left), 2))
for i in (set_left - set_3):
new_25.remove(i)
return new_25
def greedy(new_25,Customer, dis_matrix):
'''
贪婪策略构造初始解,初始化时将VRP简化为TSP进行构造。
输入:CityCoordinates-节点坐标,dis_matrix-距离矩阵
输出:初始解-line
'''
# 修改dis_matrix以适应求解需要
dis_matrix = dis_matrix.astype('float64')
for i in range(len(Customer)): dis_matrix.loc[i, i] = math.pow(10, 10)
dis_matrix.loc[:, 0] = math.pow(10, 10) # 0不在编码内
birdPop=[]
for i in range(len(new_25)):
new_matrix=copy.deepcopy(dis_matrix)
line = [] # 初始化
now_city = random.choice(new_25[i]) # 随机生成出发城市
line.append(now_city) # 添加当前城市到路径
new_matrix.loc[:, now_city] = math.pow(10, 10) # 更新距离矩阵,已经过城市不再被取出
left_5=list(set(Customer)-set(new_25[i]))
for m in left_5:#将不能走的五个点设置为无穷大
new_matrix.loc[:, m] = math.pow(10, 10)
new_matrix.loc[m, :] = math.pow(10, 10)
for j in range(len(new_25[i])-1):
next_city = new_matrix.loc[now_city, :].idxmin() # 距离最近的城市
line.append(next_city) # 添加进路径
new_matrix.loc[:, next_city] = math.pow(10, 10) # 更新距离矩阵
now_city = next_city # 更新当前城市
birdPop.append(line)
return birdPop
def calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1):
'''
贪婪策略分配车辆(解码),计算路径距离(评价函数)
输入:birdPop-路径,Demand-客户需求,dis_matrix-城市间距离矩阵,CAPACITY-车辆最大载重,DISTABCE-车辆最大行驶距离,C0-车辆启动成本,C1-车辆单位距离行驶成本;
输出:birdPop_car-分车后路径,fits-适应度
'''
birdPop_car, fits = [], [] # 初始化
for j in range(len(birdPop)):
bird = birdPop[j]
lines = [] # 存储线路分车
line = [0] # 每辆车服务客户点
dis_sum = 0 # 线路距离
dis, d = 0, 0 # 当前客户距离前一个客户的距离、当前客户需求量
i = 0 # 指向配送中心
while i < len(bird):
if line == [0]: # 车辆未分配客户点
dis += dis_matrix.loc[0, bird[i]] # 记录距离
line.append(bird[i]) # 为客户点分车
d += Demand[bird[i]] # 记录需求量
i += 1 # 指向下一个客户点
else: # 已分配客户点则需判断车辆载重和行驶距离
if (dis_matrix.loc[line[-1], bird[i]] + dis_matrix.loc[bird[i], 0] + dis <= DISTABCE) & (
d + Demand[bird[i]] <= CAPACITY):
dis += dis_matrix.loc[line[-1], bird[i]]
line.append(bird[i])
d += Demand[bird[i]]
i += 1
else:
dis += dis_matrix.loc[line[-1], 0] # 当前车辆装满
line.append(0)
dis_sum += dis
lines.append(line)
# 下一辆车
dis, d = 0, 0
line = [0]
# 最后一辆车
dis += dis_matrix.loc[line[-1], 0]
# line.append(0)
dis_sum += dis
lines.append(line)
#返回参数
birdPop_car.append(lines)
fits.append(round(C1 * dis_sum + C0 * len(lines), 2))
return birdPop_car, fits
def Fitness(bird, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1):
birdPop_car, fits = [], [] # 初始化
lines = [] # 存储线路分车
line = [0] # 每辆车服务客户点
dis_sum = 0 # 线路距离
dis, d = 0, 0 # 当前客户距离前一个客户的距离、当前客户需求量
i = 0 # 指向配送中心
while i < len(bird):
if line == [0]: # 车辆未分配客户点
dis += dis_matrix.loc[0, bird[i]] # 记录距离
line.append(bird[i]) # 为客户点分车
d += Demand[bird[i]] # 记录需求量
i += 1 # 指向下一个客户点
else: # 已分配客户点则需判断车辆载重和行驶距离
if (dis_matrix.loc[line[-1], bird[i]] + dis_matrix.loc[bird[i], 0] + dis <= DISTABCE) & (
d + Demand[bird[i]] <= CAPACITY):
dis += dis_matrix.loc[line[-1], bird[i]]
line.append(bird[i])
d += Demand[bird[i]]
i += 1
else:
dis += dis_matrix.loc[line[-1], 0] # 当前车辆装满
line.append(0)
dis_sum += dis
lines.append(line)
# 下一辆车
dis, d = 0, 0
line = [0]
# 最后一辆车
dis += dis_matrix.loc[line[-1], 0]
# line.append(0)
dis_sum += dis
lines.append(line)
#返回参数
birdPop_car=lines
fits=round(C1 * dis_sum + C0 * len(lines), 2)
return birdPop_car, fits
def crossover(bird, pLine, gLine, w, c1, c2, c3,Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1):
randNum = random.uniform(0, sum([w, c1, c2,c3]))
if randNum <= w:
new_cro=order_cross(bird,pLine)
elif randNum <= w + c1:
new_cro = order_cross(bird, gLine)
elif randNum <= w + c1+c2:
list_all = list(np.arange(1, 31))
choice_bird = list(random.sample(list_all, len(bird)))
new_cro = order_cross(bird, choice_bird)
else:
removed,bird1=max3Destroy(bird,dis_matrix)
new_cro=greedyInsert(bird1,removed,Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)
return new_cro
def order_cross(parent1,parent2):#顺序交叉
croBird = [None] * len(parent1) # 初始化
# parent1-> croBird
start_pos = random.randint(0, len(parent1) - 1)
end_pos = random.randint(0, len(parent1) - 1)
if start_pos > end_pos: start_pos, end_pos = end_pos, start_pos
croBird[start_pos:end_pos + 1] = parent1[start_pos:end_pos + 1].copy()
# parent2 -> croBird
list2 = list(range(0, start_pos))
list1 = list(range(end_pos + 1, len(parent1)))
list_index = list1 + list2 # croBird从后往前填充
j = -1
for i in list_index:
for j in range(j + 1, len(parent2) + 1):
if parent2[j] not in croBird:
croBird[i] = parent2[j]
break
return croBird
def max3Destroy(parent1,dis_matrix):#移除最大的三段距离
'''
len(disSort)-i-1得到排在最后面即距离最长的路段。len-1,len-2,...
disSort[]得到最大值,dis.index得到最大值在距离列表中的索引
solNew得到最大值路段所在起点的索引,+1删除路段终点
如solNew = 【1,3,2】,dis = 【d13,d32,d21】=【9,7,8】
disSort = 【7,8,9】,先移除最后面的9,再移除8,...
9对应的dis索引为0,solnew对应索引0上的城市为1,移除1-3段,去掉3
'''
solNew = copy.deepcopy(parent1)
removed = []
dis = []
for i in range(len(parent1) - 1):
dis.append(dis_matrix.loc[parent1[i],parent1[i + 1]]) #在距离矩阵中选取路段的长度,如【1,2,3】,循环两次求1-2,2-3的距离,
dis.append(dis_matrix.loc[parent1[-1],parent1[0]]) #选取首尾的距离3-1,放入dis列表
disdict = {} # 构造一个值和索引一一对应的字典,方便在排序后依然获得的是初始的index
for a in range(len(dis)):
disdict[a] = dis[a]
fsort = dict(sorted(disdict.items(), key=lambda x: x[1], reverse=True)) # 排序
sort_index = list(fsort.keys())#最大三个的原始索引
for i in range(3): #判断最长的3个路段并移除
if sort_index[i]== len(dis) - 1:
#如果是距离列表的最后一个,就是城市首尾相连的距离,则去掉列表第一个城市
removed.append(solNew[0])
parent1.remove(solNew[0])
else:
remove_one=solNew[sort_index[i]+1]
removed.append(remove_one)
parent1.remove(remove_one) #更新移除后的城市列表
return removed,parent1
def greedyInsert(parent1, removed,Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1):
dis = float('inf') #初始化距离
insertIndex = -1 #初始化插入索引
for i in range(len(removed)): #移除列表里有几个城市就循环几次
for j in range(len(parent1) + 1): #对于可行解中的每一个索引位置
solNew = copy.deepcopy(parent1)
solNew.insert(j, removed[i]) #将移除列表中的元素插入新解列表索引j的位置中,生成新的城市访问顺序
ditance=Fitness(solNew,Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)[1]
if ditance < dis: #插入后得到新解的距离是否<原解距离。solNew和当前解sol不一样,solNew只是作为中间过程插入哪里的判断条件
dis = ditance #更新距离,将原解换成新解
insertIndex = j #确定移除列表中每一个城市插入的索引
parent1.insert(insertIndex, removed[i]) #完成每一个城市的插入操作后,更新当前解
dis = float('inf') #完成每一个城市的插入操作后距离重新初始化
return parent1
def draw_path(car_routes, CityCoordinates):
'''
#画路径图
输入:line-路径,CityCoordinates-城市坐标;
输出:路径图
'''
for route in car_routes:
x, y = [], []
for i in route:
Coordinate = CityCoordinates[i]
x.append(Coordinate[0])
y.append(Coordinate[1])
plt.plot(x, y, 'o-', alpha=0.8, linewidth=0.8)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
#绘制收敛曲线
def draw_convergence(bestfit,iterI):
plt.figure()
X_labels=[]
for i in range(1,iterI+1):
X_labels.append(i)
plt.plot(X_labels, bestfit, color='black', marker='', linestyle="-")
# plt.xticks(X_labels,X_labels,rotation=0)
plt.legend()
plt.xlabel("iterations")
plt.ylabel("distance")
plt.show()
old_time = time.time()#记录运行开始时间
if __name__ == '__main__':
# 车辆参数
CAPACITY = 110 # 车辆最大容量
DISTABCE = 99999 # 车辆最大行驶距离
C0 = 0 # 车辆启动成本
C1 = 1 # 车辆单位距离行驶成本
# PSO参数
birdNum = 100 # 粒子数量
w = 0.25 # pline
c1 = 0.25 # gline
c2 = 0.15 # random
c3= 0.35 #destroy_restore
pBest, pLine = 0, [] # 当前最优值、当前最优解,(自我认知部分)
gBest, gLine = 0, [] # 全局最优值、全局最优解,(社会认知部分)
# 其他参数
iterMax = 1000 # 迭代次数
same_limit=iterMax*0.6
iterI = 1 # 当前迭代次数
bestfit = [] # 记录每代最优值
#特殊集合
set_sub = [1, 3, 5, 7, 9, 11, 12, 13, 20, 21, 26, 27, 28, 29, 30]
sub_set1 = [1, 3, 5, 7, 9, 11, 12]
sub_set2 = [13, 20, 21, 26, 27, 28, 29, 30]
df = pd.read_excel('题目3数据.xlsx')
Customer=df['customer No'].tolist()
Demand = df['demand'].tolist()
location_x = df['location x'].tolist()
location_y = df['location y'].tolist()
Customer_axes=[]
for i in range(len(location_x)):
Customer_axes.append((location_x[i],location_y[i]))
dis_matrix = calDistance(Customer_axes) # 计算城市间距离
#选择25个点
new_25=[choosetwentyfive(Customer, set_sub,sub_set1,sub_set2) for i in range(birdNum)]
#对这25个点的组进行贪婪的初始解
birdPop = greedy(new_25, Customer,dis_matrix)
# 分配车辆,计算种群适应度
birdPop_car, fits = calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)
gBest = pBest = min(fits) # 全局最优值、当前最优值
gLine = pLine = birdPop[fits.index(min(fits))] # 全局最优解、当前最优解
gLine_car = pLine_car = birdPop_car[fits.index(min(fits))]
bestfit.append(gBest)
# 迭代开始
same_num=0
while iterI <= iterMax and same_num <= same_limit:
same_num = max([len(list(v)) for k, v in itertools.groupby(bestfit)]) # 连续generation_num*same_num代的最优值不发生变化
for i in range(birdNum):
birdPop[i]= crossover(birdPop[i], pLine, gLine, w, c1, c2, c3,Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1)#交叉
# new_ran=Random_bird(new_cro,w, c1, c2)#变异
birdPop_car, fits= calFitness(birdPop, Demand, dis_matrix, CAPACITY, DISTABCE, C0, C1) # 分配车辆,计算种群适应度
pBest, pLine, pLine_car = min(fits), birdPop[fits.index(min(fits))], birdPop_car[fits.index(min(fits))]
if min(fits) <= gBest:
gBest, gLine, gLine_car = min(fits), birdPop[fits.index(min(fits))], birdPop_car[fits.index(min(fits))]
bestfit.append(gBest)
print(iterI, gBest) # 打印当前代数和最佳适应度值
iterI += 1 # 迭代计数加一
print(gLine_car) # 路径顺序
draw_path(gLine_car, Customer_axes) # 画路径图
draw_convergence(bestfit,iterI) #画收敛曲线图
current_time = time.time()
print("运行时间为" + str(current_time - old_time) + "s")
3、运行结果
六、原始数据
customer No | demand | location x | location y |
0 | 0 | 10 | 22 |
1 | 10 | 10 | 10 |
2 | 10 | 15 | 30 |
3 | 10 | 45 | 77 |
4 | 10 | 20 | 50 |
5 | 10 | 60 | 50 |
6 | 10 | 60 | 15 |
7 | 10 | 90 | 25 |
8 | 10 | 90 | 79 |
9 | 10 | 54 | 18 |
10 | 10 | 21 | 10 |
11 | 20 | 63 | 91 |
12 | 20 | 16 | 71 |
13 | 20 | 26 | 29 |
14 | 20 | 58 | 39 |
15 | 20 | 33 | 15 |
16 | 20 | 33 | 55 |
17 | 20 | 80 | 90 |
18 | 20 | 100 | 79 |
19 | 30 | 100 | 59 |
20 | 30 | 22 | 79 |
21 | 30 | 33 | 81 |
22 | 30 | 48 | 88 |
23 | 30 | 50 | 77 |
24 | 30 | 90 | 76 |
25 | 40 | 20 | 55 |
26 | 40 | 80 | 60 |
27 | 40 | 30 | 5 |
28 | 40 | 20 | 10 |
29 | 50 | 0 | 30 |
30 | 50 | 100 | 80 |
31 | 0 | 10 | 22 |
备注:gurobi中需要添加31号点,遗传中不需要。
欢迎大家一起交流,指出我算法中值得改进和提高的地方,也希望本篇文章能给大家提供解决问题的思路。