1、带有条件约束的最短路径问题
最短路径问题是图论中求两个顶点之间的最短路径问题,通常是求最短加权路径。
条件最短路径,指带有约束条件、限制条件的最短路径。例如,顶点约束,包括必经点或禁止点的限制;边的约束,包括必经路段或禁止路段;还包括无权路径长度的限制,即经过几步到达终点。进一步地,还有双目标限制的最短路径问题,求最短距离中花费最小的路线;交通限制条件下的最短路径问题,需要考虑转向限制和延误的约束。
求解带有限制条件的最短路径问题,总体来说可以分为两类基本方法:一类是基于不带限制条件的最短路径算法,对求解过程中的每一条有效路径,都用限制条件进行判断,如果满足所有限制条件则继续,如果不满足限制条件则放弃该路径;另一类方法是基于具体问题和选择算法的特点,将问题转化为有约束的规划问题来处理。
但是,如果使用 NetworkX 求解带有限制条件的最短路径问题,采用这两类方法都会有一些困难。原因在于前文所介绍的 NetworkX 提供的 Dijkstra 算法、Bellman-Ford 算法、Floyd 算法和启发式算法 A* 都是封装函数,没有提供设置约束条件的选项和接口,因此用户不能把条件判断语句加入这些封装函数的程序内部。这种问题不仅存在于 Python 语言的 Network 工具包,对于其它计算机语言的工具包也是类似的:自己编程序费时费力,但可以根据需要修改和扩展;直接调用工具包的算法函数非常方便,但不能进行修改或扩展。
不过,NetworkX 可以生成两个顶点之间的所有简单路径,而且可以获得所有简单路径的边的列表。利用简单路径算法,可以通过对约束条件的判断来求解带有顶点约束和边约束的最短路径问题。
欢迎关注 Youcans 原创系列,每周更新数模笔记
Python数模笔记-PuLP库
Python数模笔记-StatsModels统计回归
Python数模笔记-Sklearn
Python数模笔记-NetworkX
Python数模笔记-模拟退火算法
2、问题案例:蚂蚁的最优路径分析
蚁巢有若干个储藏间(图中圆圈表示),储藏间之间有路径相连(路径拓扑结构如图所示)。该图为无向图,路径通行的花费如图中线路上的数字所示,路径正反方向通行的花费相同。要求从起点 N0 到终点 N17 的最优路径,并需要满足限制条件:
- 需要尽可能以最少的花费到达终点;
- 必须经过图中的绿色节点;
- 必须经过图中的两段绿色路段;
- 必须避开图中的红色路段。
说明:本案例出自西安邮电大学第12届数学建模竞赛赛题,本文进行了改编。
3、NetworkX 求解带有条件约束的最短路径问题
3.1 图的创建和可视化
Python 例程(NetworkX)
# networkX_E3.py
# Demo of shortest path with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-05-20
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx # 导入 NetworkX 工具包
# 问题 1:蚂蚁的最优路径分析(西安邮电大学第12届数学建模竞赛B题)
gAnt = nx.Graph() # 创建:空的 无向图
gAnt.add_weighted_edges_from([(0,1,3),(0,2,1),(0,3,1),
(1,2,1),(1,4,1),(1,9,4),
(2,3,1),(2,4,2),(2,5,1),
(3,5,2),(3,6,2),(3,7,1),
(4,5,1),(4,9,1),
(5,6,1),(5,9,3),(5,10,1),(5,12,3),
(6,7,1),(6,8,2),(6,12,2),(6,13,4),(6,14,3),
(7,8,1),
(8,14,1),(8,15,3),
(9,10,1),(9,11,1),
(10,11,1),(10,12,2),
(11,12,1),(11,16,1),
(12,13,2),(12,16,1),
(13,14,1),(13,15,2),(13,16,2),(13,17,1),
(14,15,1),
(15,17,4),
(16,17,1)]) # 向图中添加多条赋权边: (node1,node2,weight)
pos={0:(1,8),1:(4,12),2:(4,9),3:(4,6),4:(8,11),5:(9,8), # 指定顶点位置
6:(11,6),7:(8,4),8:(12,2),9:(12,13),10:(15,11),11:(18,13),
12:(19,9),13:(22,6),14:(18,4),15:(21,2),16:(22,11),17:(28,8)}
nx.draw(gAnt, pos, with_labels=True, alpha=0.8)
labels = nx.get_edge_attributes(gAnt,\'weight\')
nx.draw_networkx_edge_labels(gAnt,pos,edge_labels=labels, font_color=\'c\') # 显示权值
nx.draw_networkx_nodes(gAnt,pos,nodelist=[0,17],node_color=\'yellow\') # 设置顶点颜色
nx.draw_networkx_nodes(gAnt,pos,nodelist=[7,12],node_color=\'lime\') # 设置顶点颜色
nx.draw_networkx_edges(gAnt,pos,edgelist=[(2,4),(13,14)],edge_color=\'lime\',width=2.5) # 设置边的颜色
nx.draw_networkx_edges(gAnt,pos,edgelist=[(11,12)],edge_color=\'r\',width=2.5) # 设置边的颜色
plt.show()
运行结果
本段程序绘制网络图,包括顶点、边、边的权值,特殊顶点和特殊边的颜色设置。
程序说明
3.2 无限制条件的最短路径
程序说明
Python 例程(NetworkX)
# 两个指定顶点之间的最短加权路径
minWPath1 = nx.dijkstra_path(gAnt, source=0, target=17) # 顶点 0 到 顶点 17 的最短加权路径
# 两个指定顶点之间的最短加权路径的长度
lMinWPath1 = nx.dijkstra_path_length(gAnt, source=0, target=17) #最短加权路径长度
print(\"\\n问题1: 无限制条件\")
print(\"S 到 E 的最短加权路径: \", minWPath1)
print(\"S 到 E 的最短加权路径长度: \", lMinWPath1)
运行结果
问题1: 无限制条件
S 到 E 的最短加权路径: [0, 2, 5, 10, 11, 16, 17]
S 到 E 的最短加权路径长度: 6
3.3 限制条件:禁止点或禁止边
程序说明
Python 例程
# 2. 限制条件:禁止点或禁止边
# 解决方案:从图中删除禁止顶点或禁止边
gAnt.remove_nodes_from([5]) # 通过顶点标签 5 删除顶点
gAnt.remove_edge(13,17) # 删除边 (13,17)
minWPath2 = nx.dijkstra_path(gAnt, source=0, target=17) # 顶点 0 到 顶点 17 的最短加权路径
lMinWPath2 = nx.dijkstra_path_length(gAnt, source=0, target=17) #最短加权路径长度
print(\"\\n问题2: 禁止点或禁止边的约束\")
print(\"S 到 E 的最短加权路径: \", minWPath2)
print(\"S 到 E 的最短加权路径长度: \", lMinWPath2)
运行结果
问题2: 禁止点或禁止边的约束
S 到 E 的最短加权路径: [0, 3, 6, 12, 16, 17]
S 到 E 的最短加权路径长度: 7
3.4 限制条件:一个必经点
程序说明
Python 例程
# 3. 限制条件:一个必经点
# 解决方案:分解为两个问题,问题 1 为起点N0至必经点N6,问题 2 为必经点N6至终点N17
minWPath3a = nx.dijkstra_path(gAnt, source=0, target=6) # N0 到 N6 的最短加权路径
lMinWPath3a = nx.dijkstra_path_length(gAnt, source=0, target=6) # 最短加权路径长度
minWPath3b = nx.dijkstra_path(gAnt, source=6, target=17) # N6 到 N17 的最短加权路径
lMinWPath3b = nx.dijkstra_path_length(gAnt, source=6, target=17) # 最短加权路径长度
minWPath3a.extend(minWPath3b[1:]) # 拼接 minWPath3a、minWPath3b 并去重 N7
print(\"\\n问题3: 一个必经点的约束\")
print(\"S 到 E 的最短加权路径: \", minWPath3a)
print(\"S 到 E 的最短加权路径长度: \", lMinWPath3a+lMinWPath3b)
运行结果
问题3: 一个必经点的约束
S 到 E 的最短加权路径: [0, 3, 6, 12, 16, 17]
S 到 E 的最短加权路径长度: 7
3.5 限制条件:多个必经点(方案一)
程序说明
Python 例程
# 4. 限制条件:多个必经点 (N7,N15)
# 解决方案:遍历从起点到终点的简单路径,求满足必经点条件的最短路径
minWPath4 = min([path # 返回 key 为最小值的 path
for path in nx.all_simple_paths(gAnt, 0, 17) # gAnt 中所有起点为0、终点为17的简单路径
if all(n in path for n in (7, 15))], # 满足路径中包括顶点 N7,N15
key=lambda x: sum(gAnt.edges[edge][\'weight\'] for edge in nx.utils.pairwise(x))) # key 为加权路径长度
lenPath = sum(gAnt.edges[edge][\'weight\'] for edge in nx.utils.pairwise(minWPath4)) # 求指定路径的加权路径长度
print(\"\\n问题4: 多个必经点的约束\")
print(\"S 到 E 的最短加权路径: \", minWPath4)
print(\"S 到 E 的最短加权路径长度: \", lenPath)
运行结果
问题4: 多个必经点的约束
S 到 E 的最短加权路径: [0, 3, 7, 8, 14, 15, 13, 17]
S 到 E 的最短加权路径长度: 8
3.6 限制条件:多个必经点(方案二)
程序说明
Python 例程
# 5. 限制条件:多个必经点 (N7,N15)
# 解决方案:遍历从起点到终点的简单路径,求满足必经点条件的最短路径
lMinWPath5 = minWPath5 = 1e9
for path in nx.all_simple_paths(gAnt, 0, 17):
if all(n in path for n in (7,15)): # 满足路径中包括顶点 N7,N15
lenPath = sum(gAnt.edges[edge][\'weight\'] for edge in nx.utils.pairwise(path))
if lenPath < lMinWPath5:
lMinWPath5 = lenPath
minWPath5 = path
print(\"\\n问题5: 多个必经点的约束\")
print(\"S 到 E 的最短加权路径: \", minWPath5)
print(\"S 到 E 的最短加权路径长度: \", lMinWPath5)
运行结果
问题5: 多个必经点的约束
S 到 E 的最短加权路径: [0, 3, 7, 8, 14, 15, 13, 17]
S 到 E 的最短加权路径长度: 8
3.7 限制条件:必经边
程序说明
Python 例程
# 6. 限制条件:必经边 (N2,N4), (N13,N14),必经点 N7,N12
# 解决方案:遍历从起点到终点的简单路径,求满足必经边条件的最短路径
gAnt.remove_edge(11,12) # 禁止边 (11,12)
lMinWPath6 = minWPath6 = 1e9 # 置初值
for path in nx.all_simple_paths(gAnt, 0, 17): # 所有起点为0、终点为17的简单路径
if all(n in path for n in (2,4,7,12,13,14)): # 满足路径中包括顶点 N7,N12
# 检查 (N2,N4)
p1 = path.index(2) # N2 的位置
if (path[p1-1]!=4 and path[p1+1]!=4): continue # 判断 N2~N4 是否相邻
# 检查 (N13,N14)
p2 = path.index(13) # # N13 的位置
if (path[p2-1]!=14 and path[p2+1]!=14): continue # 判断 N13~N14 是否相邻
lenPath = sum(gAnt.edges[edge][\'weight\'] for edge in nx.utils.pairwise(path))
if lenPath < lMinWPath6:
lMinWPath6 = lenPath
minWPath6 = path
print(\"\\n问题6: 多个必经边、必经点的约束\")
print(\"S 到 E 的最短加权路径: \", minWPath6)
print(\"S 到 E 的最短加权路径长度: \", lMinWPath6)
edgeList = []
for i in range(len(minWPath6)-1):
edgeList.append((minWPath6[i],minWPath6[i+1]))
nx.draw_networkx_edges(gAnt,pos,edgelist=edgeList,edge_color=\'m\',width=4) # 设置边的颜色
plt.show() # YouCans, XUPT
运行结果
问题6: 多个必经边、必经点的约束
S 到 E 的最短加权路径: [0, 2, 4, 5, 6, 7, 8, 14, 13, 12, 16, 17]
S 到 E 的最短加权路径长度: 13
版权说明:
本文内容及例程为作者原创,并非转载书籍或网络内容。
本文中案例问题来自:
YouCans 原创作品,如转载需标注原始链接。
Copyright 2021 YouCans, XUPT
Crated:2021-05-20
欢迎关注 Youcans 原创系列,每周更新数模笔记
Python数模笔记-PuLP库(1)线性规划入门
Python数模笔记-PuLP库(2)线性规划进阶
Python数模笔记-PuLP库(3)线性规划实例
Python数模笔记-Scipy库(1)线性规划问题
Python数模笔记-StatsModels 统计回归(1)简介
Python数模笔记-StatsModels 统计回归(2)线性回归
Python数模笔记-StatsModels 统计回归(3)模型数据的准备
Python数模笔记-StatsModels 统计回归(4)可视化
Python数模笔记-Sklearn (1)介绍
Python数模笔记-Sklearn (2)聚类分析
Python数模笔记-Sklearn (3)主成分分析
Python数模笔记-Sklearn (4)线性回归
Python数模笔记-Sklearn (5)支持向量机
Python数模笔记-NetworkX(1)图的操作
Python数模笔记-NetworkX(2)最短路径
Python数模笔记-NetworkX(3)条件最短路径
Python数模笔记-模拟退火算法(1)多变量函数优化
Python数模笔记-模拟退火算法(2)约束条件的处理
Python数模笔记-模拟退火算法(3)整数规划问题
Python数模笔记-模拟退火算法(4)旅行商问题
来源:https://www.cnblogs.com/youcans/p/14791415.html
图文来源于网络,如有侵权请联系删除。