本文中的课件来自清华大学深圳国际研究生院,物流与交通学部戚铭尧教授《物流地理信息系统》课程。
SPPRC问题
带资源约束的最短路径问题(shortest path problem with resource constraints)是一个众所周知的NP-Hard问题。除了作为网络问题直接应用外,SPPRC还用作列生成解决方案方法的基础,用于解决车辆路径规划问题和人员排班问题等。
考虑一个有向图,表示节点的集合,并且表示弧的集合。对于每一段弧都有一个非负的权重(vrptw问题中可能为负,需要作特殊处理),和,表示通过这段弧的成本和资源消耗。
SPPRC问题包括找到从起始节点到结束节点的一条路径,使该路径的总成本最小化,但不超过最大资源消耗。即使只存在一种资源,SPPRC也是一个NP-Hard。
Labeling算法
直接求解SPPRC问题是比较困难的,故通常采用一种动态规划的算法: correction 算法。通常我们考虑有种资源,可能包括时间、最大装载重量、最大装载体积等。
对于每一条从起始节点扩展到点的路径都有一个标签与之相关联。是路径使用的第L个资源的数量;是。
此外,我们还需要设置一些优超规则,称为,以帮助减少不必要的扩展。令和是从扩展到的两条不同的路径,与之关联的标签分别为和。路径可以路径当且仅当:
下面介绍一个简单的小例子:一个有向图如上图所示,我们可以看到每一段弧上都有一个非负的权重,分别表示通过该弧的与。
首先初始化出发节点的标签,设置为,随后令其向可达节点和进行扩展,则分别设置一个新的标签。再由向和进行扩展,则得到一个新的标签,得到一个新的标签,同理继续扩展到,生成一个新的标签。
然后回过头由扩展到,得到一个新的标签。我们可以发现没有继续向扩展了,因为其生成的标签为,与已有的标签相比满足了的要求,不必再扩展。
算法伪代码
图片来源:运小筹主编即将出版的《运筹优化常用模型、算法及案例实战》第13章
Python编程实现
考虑这样一个有向图
首先在python中对该图进行定义:
import numpy as np
import networkx as nx
from time import *
# 点中包含时间窗属性
Nodes = {'s': (0, 0)
, '1': (6, 14)
, '2': (9, 12)
, '3': (8, 12)
, 't': (9, 15)
}
# 弧的属性包括travel_time与distance
Arcs = {('s','1'): (8, 3)
,('s','2'): (5, 5)
,('s','3'): (12, 2)
,('1','t'): (4, 7)
,('2','t'): (2, 6)
,('3','t'): (4, 3)
}
# create the directed Graph
Graph = nx.DiGraph()
cnt = 0
# add nodes into the graph
for name in Nodes.keys():
cnt += 1
Graph.add_node(name
, time_window = (Nodes[name][0], Nodes[name][1])
, min_dis = 0
)
# add edges into the graph
for key in Arcs.keys():
Graph.add_edge(key[0], key[1]
, travel_time = Arcs[key][0]
, length = Arcs[key][1]
)
创建Label类
class Label:
path = []
travel_time = 0
distance = 0
# dominance rule
def dominate(Q, Path_set):
QCopy = copy.deepcopy(Q)
PathsCopy = copy.deepcopy(Path_set)
# dominate Q
for label in QCopy:
for another_label in Q:
if (label.path[-1] == another_label.path[
-1] and label.time and
label.dis Q.remove(another_label)
print( 'dominated path (Q) : ', another_label.path) # dominate Paths for key_1 in PathsCopy.keys(): for key_2 in PathsCopy.keys(): if (PathsCopy[key_1].path[ -1] == PathsCopy[key_2].path[ -1] and PathsCopy[key_1].travel_time and PathsCopy[key_1].length and (key_2 in Path_set.keys())):
Path_set.pop(key_2)
print( 'dominated path (P) : ', PathsCopy[key_1].path) return Q, Path_set # labeling algorithm def Labeling_SPPRC(Graph, org, des): # initial Q
Q = []
Path_set = {} # creat initial label
label = Label()
label.path = [org]
label.travel_time = 0
label.distance = 0
Q.append(label)
count = 0 while(len(Q) > 0):
count += 1
current_path = Q.pop() # extend the current label
last_node = current_path.path[ -1] for child in Graph.successors(last_node):
extended_path = copy.deepcopy(current_path)
arc = (last_node, child) # check the feasibility
arrive_time = current_path.travel_time + Graph.edges[arc][ 'travel_time']
time_window = Graph.nodes[child][ 'time_window'] if(arrive_time >= time_window[ 0] and arrive_time <= time_window[ 1] and last_node != des):
extended_path.path.append(child)
extended_path.travel_time += Graph.edges[arc][ 'travel_time']
extended_path.distance += Graph.edges[arc][ 'length']
Q.append(extended_path)
Path_set[count] = current_path # 调用dominance rule
Q, Path_set = dominance(Q, Path_set) # filtering Paths, only keep feasible solutions
Path_set_copy = copy.deepcopy(Path_set) for key in Path_set_copy.keys(): if(Path_set[key].path[ -1] != des):
Path_set.pop(key) # choose optimal solution
opt_path = {}
min_dis = 1e6 for key in Path_set.keys(): if(Path_set[key].distance min_dis = Path_set[key].distance
opt_path[ 1] = Path_set[key] return Graph, Q, Path_set, opt_path
调用labeling算法计算
org = 's'
des = 't'
begin_time = time()
Graph, Q, Path_set, opt_path = Labeling_SPPRC(Graph, org, des)
end_time = time()
print("计算时间: ", end_time - begin_time)
print('optimal path : ', opt_path[1].path )
print('optimal path (distance): ', opt_path[1].distance)
print('optimal path (time): ', opt_path[1].travel_time)
最终运行结果如图所示:
我们也可以尝试计算更多的点,并且加入的约束,对算例的计算结果为:
可视化结果:
对算例取300个客户点的计算结果为:可视化结果:
至于500个点往上,由于电脑性能的原因,我的电脑就跑不出来啦,大家可以多尝试。感谢大家的阅读!
作者:
夏旸,清华大学,工业工程系/深圳国际研究生院 (硕士在读)
王基光,清华大学,清华伯克利深圳学院(硕士在读)
刘兴禄,清华大学,清华伯克利深圳学院(博士在读)
往期精彩文章:
最新!运小筹月刊首次发布!
优化| 手把手教你用Python实现Dijkstra算法(附Python代码和可视化)
优化|手把手教你用Python调用Gurobi求解最短路问题
优化| 手把手教你用Java实现单纯形法
优化| 手把手教你用Python调用Gurobi求解VRPTW
优化 | 两阶段启发式算法求解带时间窗车辆路径问题(附Java代码)
编辑:
排版 | 徐璐,清华大学,清华-伯克利深圳学院(硕士在读)
审校 | 周鹏翔,清华大学,清华-伯克利深圳学院(博士在读)
感谢阅读!欢迎大家关注我们的公众号“运小筹”以及粉丝群!
微信群:
QQ群: