利用动态规划算法求解旅游预算问题_优化 | 手把手教你用Python实现动态规划Labeling算法求解SPPRC问题...

本文中的课件来自清华大学深圳国际研究生院,物流与交通学部戚铭尧教授《物流地理信息系统》课程。

SPPRC问题

        带资源约束的最短路径问题(shortest path problem with resource constraints)是一个众所周知的NP-Hard问题。除了作为网络问题直接应用外,SPPRC还用作列生成解决方案方法的基础,用于解决车辆路径规划问题和人员排班问题等。  

         考虑一个有向图,表示节点的集合,并且表示弧的集合。对于每一段弧都有一个非负的权重(vrptw问题中可能为负,需要作特殊处理),和,表示通过这段弧的成本和资源消耗。  

         SPPRC问题包括找到从起始节点到结束节点的一条路径,使该路径的总成本最小化,但不超过最大资源消耗。即使只存在一种资源,SPPRC也是一个NP-Hard。

Labeling算法

  直接求解SPPRC问题是比较困难的,故通常采用一种动态规划的算法: correction 算法。通常我们考虑有种资源,可能包括时间、最大装载重量、最大装载体积等。  

对于每一条从起始节点扩展到点的路径都有一个标签与之相关联。是路径使用的第L个资源的数量;是。  

此外,我们还需要设置一些优超规则,称为,以帮助减少不必要的扩展。令和是从扩展到的两条不同的路径,与之关联的标签分别为和。路径可以路径当且仅当:

下面介绍一个简单的小例子:6dc15f9c9d288de7e2de5e08367baf42.png一个有向图如上图所示,我们可以看到每一段弧上都有一个非负的权重,分别表示通过该弧的与。

首先初始化出发节点的标签,设置为,随后令其向可达节点和进行扩展,则分别设置一个新的标签。再由向和进行扩展,则得到一个新的标签,得到一个新的标签,同理继续扩展到,生成一个新的标签。

然后回过头由扩展到,得到一个新的标签。我们可以发现没有继续向扩展了,因为其生成的标签为,与已有的标签相比满足了的要求,不必再扩展。

算法伪代码

1181dcc9e05b9de49eceab7a56622f83.png

图片来源:运小筹主编即将出版的《运筹优化常用模型、算法及案例实战》第13章

Python编程实现

08ae7e1a53f9ef3ad51597c4968e5c68.png考虑这样一个有向图

首先在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)

最终运行结果如图所示:020567864e0d34677b1e55bf2a858c4b.png

我们也可以尝试计算更多的点,并且加入的约束,对算例的计算结果为:5f08bfe8ec670e6c4c9fa48276afe228.png

可视化结果:

f3fca7b00afc9252d9788fd0b2b67c80.png

4255351178f98f401c474e7d611d7177.png

对算例取300个客户点的计算结果为:71424ec68caf792ee59dcc75cb7b1309.png可视化结果:

38f642a4e63f17f61077c8b2d4f3e02f.png

17c55bea0a58138459222bd554c4dd4a.png

至于500个点往上,由于电脑性能的原因,我的电脑就跑不出来啦,大家可以多尝试。感谢大家的阅读!


作者:

夏旸,清华大学,工业工程系/深圳国际研究生院 (硕士在读)

王基光,清华大学,清华伯克利深圳学院(硕士在读)

刘兴禄,清华大学,清华伯克利深圳学院(博士在读)


往期精彩文章:

最新!运小筹月刊首次发布!

优化| 手把手教你用Python实现Dijkstra算法(附Python代码和可视化)

优化|手把手教你用Python调用Gurobi求解最短路问题

优化| 手把手教你用Java实现单纯形法

优化| 手把手教你用Python调用Gurobi求解VRPTW

优化 | 两阶段启发式算法求解带时间窗车辆路径问题(附Java代码)


编辑:

排版 | 徐璐,清华大学,清华-伯克利深圳学院(硕士在读)

审校 | 周鹏翔,清华大学,清华-伯克利深圳学院(博士在读)


感谢阅读!欢迎大家关注我们的公众号“运小筹”以及粉丝群!

微信群:

b8efc454bad30859470479007313247f.png

QQ群

b17129f84b3839d572dad86993d873a4.png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值