1、带有条件约束的最短路径问题

最短路径问题是图论中求两个顶点之间的最短路径问题,通常是求最短加权路径。

条件最短路径,指带有约束条件、限制条件的最短路径。例如,顶点约束,包括必经点或禁止点的限制;边的约束,包括必经路段或禁止路段;还包括无权路径长度的限制,即经过几步到达终点。进一步地,还有双目标限制的最短路径问题,求最短距离中花费最小的路线;交通限制条件下的最短路径问题,需要考虑转向限制和延误的约束。

求解带有限制条件的最短路径问题,总体来说可以分为两类基本方法:一类是基于不带限制条件的最短路径算法,对求解过程中的每一条有效路径,都用限制条件进行判断,如果满足所有限制条件则继续,如果不满足限制条件则放弃该路径;另一类方法是基于具体问题和选择算法的特点,将问题转化为有约束的规划问题来处理。

但是,如果使用 NetworkX 求解带有限制条件的最短路径问题,采用这两类方法都会有一些困难。原因在于前文所介绍的 NetworkX 提供的 Dijkstra 算法、Bellman-Ford 算法、Floyd 算法和启发式算法 A* 都是封装函数,没有提供设置约束条件的选项和接口,因此用户不能把条件判断语句加入这些封装函数的程序内部。这种问题不仅存在于 Python 语言的 Network 工具包,对于其它计算机语言的工具包也是类似的:自己编程序费时费力,但可以根据需要修改和扩展;直接调用工具包的算法函数非常方便,但不能进行修改或扩展。

不过,NetworkX 可以生成两个顶点之间的所有简单路径,而且可以获得所有简单路径的边的列表。利用简单路径算法,可以通过对约束条件的判断来求解带有顶点约束和边约束的最短路径问题。

2、问题案例:蚂蚁的最优路径分析

蚁巢有若干个储藏间(图中圆圈表示),储藏间之间有路径相连(路径拓扑结构如图所示)。该图为无向图,路径通行的花费如图中线路上的数字所示,路径正反方向通行的花费相同。要求从起点 N0 到终点 N17 的最优路径,并需要满足限制条件:

  • 需要尽可能以最少的花费到达终点;
  • 必须经过图中的绿色节点;
  • 必须经过图中的两段绿色路段;
  • 必须避开图中的红色路段。

说明:本案例出自西安邮电大学第12届数学建模竞赛赛题,本文进行了改编。

关注 Youcans 原创系列(https://www.cnblogs.com/youcans/)

3、NetworkX 求解带有条件约束的最短路径问题

3.1 图的创建和可视化

Python 例程(NetworkX)

  1. # networkX_E3.py
  2. # Demo of shortest path with NetworkX
  3. # Copyright 2021 YouCans, XUPT
  4. # Crated:2021-05-20
  5. import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
  6. import networkx as nx # 导入 NetworkX 工具包
  7. # 问题 1:蚂蚁的最优路径分析(西安邮电大学第12届数学建模竞赛B题)
  8. gAnt = nx.Graph() # 创建:空的 无向图
  9. gAnt.add_weighted_edges_from([(0,1,3),(0,2,1),(0,3,1),
  10. (1,2,1),(1,4,1),(1,9,4),
  11. (2,3,1),(2,4,2),(2,5,1),
  12. (3,5,2),(3,6,2),(3,7,1),
  13. (4,5,1),(4,9,1),
  14. (5,6,1),(5,9,3),(5,10,1),(5,12,3),
  15. (6,7,1),(6,8,2),(6,12,2),(6,13,4),(6,14,3),
  16. (7,8,1),
  17. (8,14,1),(8,15,3),
  18. (9,10,1),(9,11,1),
  19. (10,11,1),(10,12,2),
  20. (11,12,1),(11,16,1),
  21. (12,13,2),(12,16,1),
  22. (13,14,1),(13,15,2),(13,16,2),(13,17,1),
  23. (14,15,1),
  24. (15,17,4),
  25. (16,17,1)]) # 向图中添加多条赋权边: (node1,node2,weight)
  26. pos={0:(1,8),1:(4,12),2:(4,9),3:(4,6),4:(8,11),5:(9,8), # 指定顶点位置
  27. 6:(11,6),7:(8,4),8:(12,2),9:(12,13),10:(15,11),11:(18,13),
  28. 12:(19,9),13:(22,6),14:(18,4),15:(21,2),16:(22,11),17:(28,8)}
  29. nx.draw(gAnt, pos, with_labels=True, alpha=0.8)
  30. labels = nx.get_edge_attributes(gAnt,'weight')
  31. nx.draw_networkx_edge_labels(gAnt,pos,edge_labels=labels, font_color='c') # 显示权值
  32. nx.draw_networkx_nodes(gAnt,pos,nodelist=[0,17],node_color='yellow') # 设置顶点颜色
  33. nx.draw_networkx_nodes(gAnt,pos,nodelist=[7,12],node_color='lime') # 设置顶点颜色
  34. nx.draw_networkx_edges(gAnt,pos,edgelist=[(2,4),(13,14)],edge_color='lime',width=2.5) # 设置边的颜色
  35. nx.draw_networkx_edges(gAnt,pos,edgelist=[(11,12)],edge_color='r',width=2.5) # 设置边的颜色
  36. plt.show()

运行结果

本段程序绘制网络图,包括顶点、边、边的权值,特殊顶点和特殊边的颜色设置。

程序说明

  1. 图的创建。本例使用 nx.Graph() 创建无向图,然后用 gAnt.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,weight) 表示。
  2. 图的绘制。使用nx.draw()绘图时,默认的节点位置并不理想,可以使用 pos 属性参数指定节点位置。pos 为字典数据类型,按 node:(x_pos,y_pos) 格式设置节点位置。
  3. 显示边的权值。使用 nx.draw_networkx_edge_labels() 可以绘制边的属性,本例中选择显示权值属性。
  4. 设置顶点属性。nx.draw_networkx_nodes() 可以设置顶点的属性,例如对 nodelist 列表中的节点设置颜色属性 node_color。
  5. 设置边的属性。nx.draw_networkx_edges() 可以设置边的属性,例如对 edgelist 列表中的边设置线宽属性 width 和颜色属性 edge_color。

3.2 无限制条件的最短路径

程序说明

  1. 对于无限制条件的最短路径问题,NetworkX 提供了 Dijkstra 算法、Bellman-Ford 算法、Floyd 算法和启发式算法 A* 的函数。
  2. 例程使用 nx.dijkstra_path() 和 nx.dijkstra_path_length() 调用 Dijkstra 算法求两个指定顶点之间的最短加权路径和最短加权路径长度。

Python 例程(NetworkX)

  1. # 两个指定顶点之间的最短加权路径
  2. minWPath1 = nx.dijkstra_path(gAnt, source=0, target=17) # 顶点 0 到 顶点 17 的最短加权路径
  3. # 两个指定顶点之间的最短加权路径的长度
  4. lMinWPath1 = nx.dijkstra_path_length(gAnt, source=0, target=17) #最短加权路径长度
  5. print("\n问题1: 无限制条件")
  6. print("S 到 E 的最短加权路径: ", minWPath1)
  7. print("S 到 E 的最短加权路径长度: ", lMinWPath1)

运行结果

  1. 问题1: 无限制条件
  2. S E 的最短加权路径: [0, 2, 5, 10, 11, 16, 17]
  3. S E 的最短加权路径长度: 6

3.3 限制条件:禁止点或禁止边

程序说明

  1. 禁止点或者禁止边的处理比较简单,从图中删除对应的禁止顶点或禁止边即可。当然,在创建图时就不添加这些顶点和边更简单,但这样在绘图时也无法反映这些顶点和边。
  2. 使用 remove_node(n) 删除指定顶点 n,remove_edge(u,v) 删除指定的边 (u,v)。
  3. 使用 remove_nodes_from([n1,...nk]) 删除多个顶点,remove_edges_from([(u1,v1),...(uk,vk)]) 删除多条边。
  4. 例程中删除的点和边与案例问题中的要求不一致,是为了示例删除函数的使用。下同。

Python 例程

  1. # 2. 限制条件:禁止点或禁止边
  2. # 解决方案:从图中删除禁止顶点或禁止边
  3. gAnt.remove_nodes_from([5]) # 通过顶点标签 5 删除顶点
  4. gAnt.remove_edge(13,17) # 删除边 (13,17)
  5. minWPath2 = nx.dijkstra_path(gAnt, source=0, target=17) # 顶点 0 到 顶点 17 的最短加权路径
  6. lMinWPath2 = nx.dijkstra_path_length(gAnt, source=0, target=17) #最短加权路径长度
  7. print("\n问题2: 禁止点或禁止边的约束")
  8. print("S 到 E 的最短加权路径: ", minWPath2)
  9. print("S 到 E 的最短加权路径长度: ", lMinWPath2)

运行结果

  1. 问题2: 禁止点或禁止边的约束
  2. S E 的最短加权路径: [0, 3, 6, 12, 16, 17]
  3. S E 的最短加权路径长度: 7

3.4 限制条件:一个必经点

程序说明

  1. 当限制条件为一个必经点时,可以把原问题分解为两个子问题:子问题 1 为起点至必经点,子问题 2 为必经点至终点。
  2. 对两个子问题分别用 Dijkstra 算法求最短加权路径和最短加权路径长度,然后进行合并,就得到经过必经点的原问题的最短加权路径和最短加权路径长度。

Python 例程

  1. # 3. 限制条件:一个必经点
  2. # 解决方案:分解为两个问题,问题 1 为起点N0至必经点N6,问题 2 为必经点N6至终点N17
  3. minWPath3a = nx.dijkstra_path(gAnt, source=0, target=6) # N0 到 N6 的最短加权路径
  4. lMinWPath3a = nx.dijkstra_path_length(gAnt, source=0, target=6) # 最短加权路径长度
  5. minWPath3b = nx.dijkstra_path(gAnt, source=6, target=17) # N6 到 N17 的最短加权路径
  6. lMinWPath3b = nx.dijkstra_path_length(gAnt, source=6, target=17) # 最短加权路径长度
  7. minWPath3a.extend(minWPath3b[1:]) # 拼接 minWPath3a、minWPath3b 并去重 N7
  8. print("\n问题3: 一个必经点的约束")
  9. print("S 到 E 的最短加权路径: ", minWPath3a)
  10. print("S 到 E 的最短加权路径长度: ", lMinWPath3a+lMinWPath3b)

运行结果

  1. 问题3: 一个必经点的约束
  2. S E 的最短加权路径: [0, 3, 6, 12, 16, 17]
  3. S E 的最短加权路径长度: 7

3.5 限制条件:多个必经点(方案一)

程序说明

  1. 当限制条件为两个或多个必经点时,起点、终点与各必经点的次序并不确定,即从起点出发就不知道应该先去哪一个必经点,因此不宜再用分段求最小路径的方法处理。
  2. NetworkX 提供了 all_simple_paths() 函数,可以生成两个顶点之间的所有简单路径。利用简单路径算法,可以通过对约束条件的判断来求解带有多个顶点约束的最短路径问题。
  3. 程序实现的步骤包括:(1)遍历起点为0、终点为17的简单路径;(2)检查路径是否满足包括顶点 N7、N15 的限制条件;(3)在满足限制条件的简单路径中找加权长度最短的路径;(4)求最短路径的加权路径长度。
  4. 本段例程非常简练,综合使用了几种 Python 语言循环、判断结构的简洁写法,需要逐步分析。

Python 例程

  1. # 4. 限制条件:多个必经点 (N7,N15)
  2. # 解决方案:遍历从起点到终点的简单路径,求满足必经点条件的最短路径
  3. minWPath4 = min([path # 返回 key 为最小值的 path
  4. for path in nx.all_simple_paths(gAnt, 0, 17) # gAnt 中所有起点为0、终点为17的简单路径
  5. if all(n in path for n in (7, 15))], # 满足路径中包括顶点 N7,N15
  6. key=lambda x: sum(gAnt.edges[edge]['weight'] for edge in nx.utils.pairwise(x))) # key 为加权路径长度
  7. lenPath = sum(gAnt.edges[edge]['weight'] for edge in nx.utils.pairwise(minWPath4)) # 求指定路径的加权路径长度
  8. print("\n问题4: 多个必经点的约束")
  9. print("S 到 E 的最短加权路径: ", minWPath4)
  10. print("S 到 E 的最短加权路径长度: ", lenPath)

运行结果

  1. 问题4: 多个必经点的约束
  2. S E 的最短加权路径: [0, 3, 7, 8, 14, 15, 13, 17]
  3. S E 的最短加权路径长度: 8

3.6 限制条件:多个必经点(方案二)

程序说明

  1. 本例与 3.5 的问题实际上是相同的。限制条件都是多个必经顶点 N7、N15,解决方案都是使用 all_simple_paths() 函数生成两个顶点间的所有简单路径,程序实现的步骤也是类似的。
  2. 本方案按照典型的循环、判断结构的写法,便于阅读和理解。此外,如果还有其它约束条件或子任务需要在循环中处理,这样的结构更容易实现。

Python 例程

  1. # 5. 限制条件:多个必经点 (N7,N15)
  2. # 解决方案:遍历从起点到终点的简单路径,求满足必经点条件的最短路径
  3. lMinWPath5 = minWPath5 = 1e9
  4. for path in nx.all_simple_paths(gAnt, 0, 17):
  5. if all(n in path for n in (7,15)): # 满足路径中包括顶点 N7,N15
  6. lenPath = sum(gAnt.edges[edge]['weight'] for edge in nx.utils.pairwise(path))
  7. if lenPath < lMinWPath5:
  8. lMinWPath5 = lenPath
  9. minWPath5 = path
  10. print("\n问题5: 多个必经点的约束")
  11. print("S 到 E 的最短加权路径: ", minWPath5)
  12. print("S 到 E 的最短加权路径长度: ", lMinWPath5)

运行结果

  1. 问题5: 多个必经点的约束
  2. S E 的最短加权路径: [0, 3, 7, 8, 14, 15, 13, 17]
  3. S E 的最短加权路径长度: 8

3.7 限制条件:必经边

程序说明

  1. 必经点的处理,实际上还可以有更好的方法,其思想是结合 Dijkstra 算法的实现过程, 将限制条件作为缩小搜索空间的条件,可以降低算法的复杂度。但对于多个必经边来说,很难以此来改进基础的无约束算法,通常的处理方法就是在算法中增加一个判断是否满足约束条件的过程。
  2. 本例仍然延续处理多个必经点的思路,利用简单路径算法,可以通过对约束条件的判断来求解带有多个必经边约束条件的最短路径问题,可以同时处理必经点约束条件。
  3. 本例程对应案例中的各项约束条件: 必须经过图中的绿色节点;必须经过图中的两段绿色路段;必须避开图中的红色路段;尽可能以最少的花费到达终点。
  4. 本例程的框架和步骤同 3.6,这是一个遍历简单路径、判断约束条件的通用框架。
  5. all(n in path for n in (2,4,7,12,13,14)) 的作用,一是判断路径中是否包括必经点 N7、N12;二是判断路径中是否包括必经边 (2,4)、(13,14) 的各顶点,这不仅可以减小计算量,而且能确保下面使用 index() 查找顶点位置时不会发生错误。

Python 例程

  1. # 6. 限制条件:必经边 (N2,N4), (N13,N14),必经点 N7,N12
  2. # 解决方案:遍历从起点到终点的简单路径,求满足必经边条件的最短路径
  3. gAnt.remove_edge(11,12) # 禁止边 (11,12)
  4. lMinWPath6 = minWPath6 = 1e9 # 置初值
  5. for path in nx.all_simple_paths(gAnt, 0, 17): # 所有起点为0、终点为17的简单路径
  6. if all(n in path for n in (2,4,7,12,13,14)): # 满足路径中包括顶点 N7,N12
  7. # 检查 (N2,N4)
  8. p1 = path.index(2) # N2 的位置
  9. if (path[p1-1]!=4 and path[p1+1]!=4): continue # 判断 N2~N4 是否相邻
  10. # 检查 (N13,N14)
  11. p2 = path.index(13) # # N13 的位置
  12. if (path[p2-1]!=14 and path[p2+1]!=14): continue # 判断 N13~N14 是否相邻
  13. lenPath = sum(gAnt.edges[edge]['weight'] for edge in nx.utils.pairwise(path))
  14. if lenPath < lMinWPath6:
  15. lMinWPath6 = lenPath
  16. minWPath6 = path
  17. print("\n问题6: 多个必经边、必经点的约束")
  18. print("S 到 E 的最短加权路径: ", minWPath6)
  19. print("S 到 E 的最短加权路径长度: ", lMinWPath6)
  20. edgeList = []
  21. for i in range(len(minWPath6)-1):
  22. edgeList.append((minWPath6[i],minWPath6[i+1]))
  23. nx.draw_networkx_edges(gAnt,pos,edgelist=edgeList,edge_color='m',width=4) # 设置边的颜色
  24. plt.show() # YouCans, XUPT

运行结果

  1. 问题6: 多个必经边、必经点的约束
  2. S E 的最短加权路径: [0, 2, 4, 5, 6, 7, 8, 14, 13, 12, 16, 17]
  3. S E 的最短加权路径长度: 13


欢迎关注 Youcans 原创系列(https://www.cnblogs.com/youcans/):

Python数模笔记-NetworkX(1)图的操作

https://www.cnblogs.com/youcans/p/14778010.html

Python数模笔记-NetworkX(2)最短路径

https://www.cnblogs.com/youcans/p/14782578.html

Python数模笔记-NetworkX(3)条件最短路径

https://www.cnblogs.com/youcans/p/14791415.html


版权说明:

本文内容及例程为作者原创,并非转载书籍或网络内容。

本文中案例问题来自:

YouCans 原创作品,如转载需标注原始链接。

Copyright 2021 YouCans, XUPT

Crated:2021-05-20

Python数模笔记-NetworkX(3)条件最短路径的更多相关文章

  1. Python数模笔记-NetworkX(2)最短路径

    1.最短路径问题的常用算法 最短路径问题是图论研究中的经典算法问题,用于计算图中一个顶点到另一个顶点的最短路径. 1.1 最短路径长度与最短加权路径长度 在日常生活中,最短路径长度与最短路径距离好像并 ...

  2. Python数模笔记-StatsModels 统计回归(4)可视化

    1.如何认识可视化? 图形总是比数据更加醒目.直观.解决统计回归问题,无论在分析问题的过程中,还是在结果的呈现和发表时,都需要可视化工具的帮助和支持. 需要指出的是,虽然不同绘图工具包的功能.效果会有 ...

  3. Python数模笔记-StatsModels 统计回归(1)简介

    1.关于 StatsModels statsmodels(http://www.statsmodels.org)是一个Python库,用于拟合多种统计模型,执行统计测试以及数据探索和可视化. 2.文档 ...

  4. Python数模笔记-Scipy库(1)线性规划问题

    1.最优化问题建模 最优化问题的三要素是决策变量.目标函数和约束条件. (1)分析影响结果的因素是什么,确定决策变量 (2)决策变量与优化目标的关系是什么,确定目标函数 (3)决策变量所受的限制条件是 ...

  5. Python数模笔记-Sklearn(1) 介绍

    1.SKlearn 是什么 Sklearn(全称 SciKit-Learn),是基于 Python 语言的机器学习工具包. Sklearn 主要用Python编写,建立在 Numpy.Scipy.Pa ...

  6. Python数模笔记-(1)NetworkX 图的操作

    1.NetworkX 图论与网络工具包 NetworkX 是基于 Python 语言的图论与复杂网络工具包,用于创建.操作和研究复杂网络的结构.动力学和功能. NetworkX 可以以标准和非标准的数 ...

  7. Python数模笔记-Sklearn(2)样本聚类分析

    1.分类的分类 分类的分类?没错,分类也有不同的种类,而且在数学建模.机器学习领域常常被混淆. 首先我们谈谈有监督学习(Supervised learning)和无监督学习(Unsupervised ...

  8. Python数模笔记-Sklearn(4)线性回归

    1.什么是线性回归? 回归分析(Regression analysis)是一种统计分析方法,研究自变量和因变量之间的定量关系.回归分析不仅包括建立数学模型并估计模型参数,检验数学模型的可信度,也包括利 ...

  9. Python数模笔记-PuLP库(2)线性规划进阶

    1.基于字典的创建规划问题 上篇中介绍了使用 LpVariable 对逐一定义每个决策变量,设定名称.类型和上下界,类似地对约束条件也需要逐一设置模型参数.在大规模的规划问题中,这样逐个定义变量和设置 ...

随机推荐

  1. 【java框架】SpringBoot(4)--SpringBoot实现异步、邮件、定时任务

    1.SpringBoot整合任务机制 1.1.SpringBoot实现异步方法 日常开发中涉及很多界面与后端的交互响应,都不是同步的,基于SpringBoot为我们提供了注解方式实现异步方法.使得前端 ...

  2. 推荐一款全能测试开发神器:Mockoon!1分钟快速上手!

    1. 说一下背景 在日常开发或者测试工作中,经常会因为下游服务不可用或者不稳定时,通过工具或者技术手段去模拟一个HTTP Server,或者模拟所需要的接口数据. 这个时候,很多人脑海里,都会想到可以 ...

  3. Redis扩展数据类型详解

    在Redis中有5种基本数据类型,分别是String, List, Hash, Set, Zset.除此之外,Redis中还有一些实用性很高的扩展数据类型,下面来介绍一下这些扩展数据类型以及它们的使用 ...

  4. SpringCloud+Nacos实现服务配置中心(Hoxton版本)

    关于 Nacos Spring Cloud 的详细文档请参看:Nacos Config和Nacos Discovery. 通过 Nacos Server 和 spring-cloud-starter- ...

  5. 简单创建ASP.NET网站(1)

    闲话 公司员工辞职了,我从原来的Delphi开发转型到ASP.NET开发,接受同时的相关工作,因为网上搜了视频学习,还是不觉得有什么提升,一脸懵逼,所以就买了书籍自己慢慢学习,为了加深记忆,我就记录一 ...

  6. React 错误边界组件

    这是React16的内容,并不是最新的技术,但是用很少被讨论,直到通过文档发现其实也是很有用的一部分内容,还是总结一下- React中的未捕获的 JS 错误会导致整个应用的崩溃,和整个组件树的卸载.从 ...

  7. Recoil 中默认值的正确处理

    继续使用 Recoil 默认值及数据级联的使用 的地域可用区级联的例子. 地域变更后可用区随之联动,两个下拉框皆默认选中第一个可选项. 从 URL 获取默认值 考虑这种情况,当 URL 中带了 que ...

  8. (十一)struts2的未知处理器

    从struts2.1开始,struts2增加了未知处理器. 当用户请求未知Action,或指定Action里的未知方法,或Action处理结束后返回一个未知的result.struts2允许使用未知处 ...

  9. Dynamics CRM实体系列之视图

    这一节开始讲视图.视图在Dynamics CRM中代表着实体的数据展示列表,通过这个列表可以对数据进行一个初步预览,也可以进行一些数据的定向筛选和搜索进行精确的浏览一部分数据.同时视图也是查看详细实体 ...

  10. pwnable.tw orw

    orw 首先,检查一下程序的保护机制 开启了canary保护,还是个32位的程序,应该是个简单的题