一种VRPTW初始解构造方法(含惩罚时间)
在使用智能算法求解带时间窗的车辆路径规划问题(VRPTW)时,面临的第一个问题就是初始解的构造,不同与旅行商问题(TSP)只要将所有客户打乱顺序即可得到一个初始解。VRPTW的初始解构造需要考虑两点:
- 每辆车的载重量上限;
- 客户点的开始服务时间约束。
在使用智能算法(本文以邻域搜索为例)求解VRPTW问题时,想要构造一个可行的满足所有约束的初始解较为复杂,为了构造方便以及避免后续需要对所有产生的新解进行可行性判断,一般引入两个惩罚因子,一个是对重叠时间进行惩罚,一个是对超载进行惩罚。
一种构造初始解的方法
构造过程如下
下面是python构造初始解的代码。
class CustomerType:
def __init__(self, c_id, x, y, demand, begin, end, service):
"""
:param c_id: 客户点id
:param x: x轴坐标
:param y: y轴坐标
:param demand: 需求量
:param begin: 早开始时间
:param end: 晚开始时间
:param service: 服务时长
"""
self.c_id = c_id
self.x = x
self.y = y
self.demand = demand
self.begin = begin
self.end = end
self.service = service
self.r = 0
class RouteType:
def __init__(self):
self.load = 0 # 单条路径载重
self.sub_t = 0 # 单条路径违反各节点时间窗约束时长总和
self.dis = 0 # 单挑路径总长度
self.v = [] # 单条路径顾客节点序列
# 计算距离
def distance(c1: CustomerType, c2: CustomerType):
return int(((c1.x - c2.x) ** 2 + (c1.y - c2.y) ** 2) ** 0.5)
# 读取数据
def read_in():
f = open('solomon-instances/c101.txt', 'r')
lines = f.readlines()
count = 0
for line in lines:
count += 1
if 10 <= count <= 10 + customerNumber:
str_ = line.strip().split()
str_ = [int(s) for s in str_]
node = CustomerType(*str_)
customers.append(node)
for i in range(vehicleNumber):
# 给每一条路径赋初始起点与虚拟终点
routes.append(RouteType())
routes[i].v.append(copy.deepcopy(customers[0]))
routes[i].v.append(copy.deepcopy(customers[0]))
routes[i].v[0].end = routes[i].v[0].begin
routes[i].v[1].begin = routes[i].v[1].end
# 算例中给出节点0有起始时间0和终止时间,所以如上赋值。
routes[i].load = 0
# 计算两点间的距离
for i in range(customerNumber + 1):
temp = []
for j in range(customerNumber + 1):
temp.append(distance(customers[i], customers[j]))
graph.append(temp)
# 构造初始解
def construction():
customers_set = [i for i in range(1, customerNumber + 1)]
rd.shuffle(customers_set)
current_route = 0
# 以满足容量约束为目的的随机初始化
# 即随机挑选一个节点插入到第m条路径中,若超过容量约束,则插入第m+1条路径
# 且插入路径的位置由该路径上已存在的各节点的最早时间决定
for c in customers_set:
# 随机提取出一个节点,类似产生乱序随机序列的代码
# 不满足容量约束,下一条车辆路线
if routes[current_route].load + customers[c].demand > capacity:
current_route += 1
# 对路径中每一个节点查找,看是否能插入新节点
for i in range(len(routes[current_route].v) - 1):
if routes[current_route].v[i].begin <= customers[c].begin <= routes[current_route].v[i + 1].begin:
routes[current_route].v.insert(i + 1, copy.deepcopy(customers[c]))
# 判断时间窗开始部分是否满足,则加入该节点
# 更新路径容量,节点类
routes[current_route].load += customers[c].demand
customers[c].r = current_route
break
# 初始化计算超过时间窗约束的总量
for i in range(vehicleNumber):
routes[i].sub_t = 0
for j in range(1, len(routes[i].v)):
routes[i].dis += graph[routes[i].v[j - 1].c_id][routes[i].v[j].c_id]
# 更新超过时间窗总量
update_sub_t(routes[i])
# 更新路径r对时间窗的违反量
def update_sub_t(r: RouteType):
at = 0
# 对每个节点分别计算超出时间窗的部分
for j in range(1, len(r.v)):
at = at + r.v[j - 1].service + graph[r.v[j - 1].c_id][r.v[j].c_id]
if at > r.v[j].end:
# 超过,记录
r.sub_t += at - r.v[j].end
else:
# 未到达,等待
at = r.v[j].begin
# 参数
customerNumber = 25
vehicleNumber = 5
capacity = 200
graph = [] # 记录距离
# 存储客户数据、当前解路线数据
customers = []
routes = []
if __name__ == '__main__':
# 读取数据
read_in()
# 构造初始解
construction()