最短路径问题

​​​​​​Python小白的数学建模课-16.最短路径算法​​​​​​​​​​​​

建模时,去计算最短路径,往往是带有限制条件的

1. 创建无向图

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包
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:(0,8),1:(7,12),2:(6,9),3:(5,6),4:(11,10),5:(14,8),  # 指定顶点位置
     6:(17,6),7:(10,4),8:(19,4),9:(18,12),10:(21,10),11:(28,12),
     12:(25,8),13:(30,7),14:(24,5),15:(29,4),16:(32,10),17:(37,8)}

fig, ax = plt.subplots(figsize=(9, 6))
nx.draw_networkx(gAnt, pos, with_labels=True, node_color='cyan', alpha=0.8)
labels = nx.get_edge_attributes(gAnt, 'weight')
nx.draw_networkx_edge_labels(gAnt, pos, edge_labels=labels, font_color='m')
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=3)
nx.draw_networkx_edges(gAnt, pos, edgelist=[(11, 12)], edge_color='r', width=3)
plt.show()

向图中添加多条赋权边的格式是(node1,node2,weight),即(source node,next node,weight)

2. 无条件求最短路径

# 1. 无限制条件的最短路径
# Dijkstra 算法:两个指定顶点之间的最短加权路径
minWPath1 = nx.dijkstra_path(gAnt, source=0, target=17)  # 顶点 0 到 顶点 17 的最短加权路径
# Dijkstra 算法:两个指定顶点之间的最短加权路径的长度
lMinWPath1 = nx.dijkstra_path_length(gAnt, source=0, target=17)  # 最短加权路径长度
print("\n问题1: 无限制条件")  
print("N0 到 N17 的最短加权路径: ", minWPath1)
print("N0 到 N17 的最短加权路径长度: ", lMinWPath1)

如果对最短路径没有任何条件的限制,就直接使用networkx的dijkstra_path和dijkstra_path_length方法,求出最短加权路径和最短加权路径的长度。在这里只需要修改source  node和target的值,对应起始点和目标点。

3. 禁止点或者边

# 2. 限制条件:禁止点或禁止边
# 解决方案:从图中删除禁止顶点或禁止边
gAntF = gAnt.copy()
gAntF.remove_node(5)  # 通过顶点标签 5 删除顶点
gAntF.remove_edges_from([(11,12), (13,17)])  # 删除多条边 (11,12), (13,17)
minWPath2 = nx.dijkstra_path(gAntF, source=0, target=17)  # 顶点 0 到 顶点 17 的最短加权路径
lMinWPath2 = nx.dijkstra_path_length(gAntF, source=0, target=17)  # 最短加权路径长度
print("\n问题2: 禁止点或禁止边的约束")  # youcans @ XUPT
print("N0 到 N17 的最短加权路径: ", minWPath2)
print("N0 到 N17 的最短加权路径长度: ", lMinWPath2)

当限制条件是找寻最短路径时,不能经过某个点或者边,我们需要从图中将点或者边删除。

gAntF.remove_node(5)  # 通过顶点标签 5 删除顶点
gAntF.remove_edges_from([(11,12), (13,17)])  # 删除多条边 (11,12), (13,17)

在调用networkx的dijkstra_path和dijkstra_path_length方法之前,首先,remove_node 或者使用remove_edges-from去删除节点或者边

4. 有一个必经点

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

当有一个必经点时,我们可以把问题化成两部分,第一部分是从起始点到必经点,第二部分是从必经点到目标点。当我们需要经过多个必经点时,如果已知必经点的通过顺序,也可以通过这种分步的办法去解决最短路径问题。但很多时候,题目并没有给出必经点的通过顺序,我们需要找寻其它的办法。

minWPath3a.extend(minWPath3b[1:]) # 拼接 minWPath3a、minWPath3b 并去重 N7

两部分分别按照无条件求路径的方法求出以后,要记得拼接在一起

5. 有多个必经点

# 5. 限制条件:多个必经点 (N7,N15)
# 解决方案:多重循环、判断结构,遍历从起点到终点的简单路径,求满足必经点条件的最短路径
gAntM = gAnt.copy()
lMinWPath5 = minWPath5 = 1e9
for path in nx.all_simple_paths(gAntM, 0, 17):
    if all(n in path for n in (7,15)):  # 满足路径中包括顶点 N7,N15
        lenPath = sum(gAntM.edges[edge]['weight'] for edge in nx.utils.pairwise(path))
        if lenPath < lMinWPath5:
            lMinWPath5 = lenPath
            minWPath5 = path
print("\n问题5: 多个必经点(N7, N15)的约束")
print("N0 经 N7, N15 到 N17 的最短加权路径: ", minWPath5)
print("N0 经 N7, N15 到 N17 的最短加权路径长度: ", lMinWPath5)

 实际建模运用时需要修改相对应的节点。该方案利用多重的循环和 if 判断去遍历,以求得最短路径

 6. 必经边

# 6. 限制条件:必经边 (N2,N4), (N13,N14)
# 解决方案:遍历从起点到终点的简单路径,求满足必经边条件的最短路径
gAntE = gAnt.copy()
lMinWPath6 = minWPath6 = 1e9  # 置初值
for path in nx.all_simple_paths(gAntE, 0, 17):  # 所有起点为0、终点为17的简单路径
    if all(n in path for n in (2,4,13,14)):  # 满足路径中包括必经边的顶点 N2,N4,N13,N14
        # 检查 (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(gAntE.edges[edge]['weight'] for edge in nx.utils.pairwise(path))
        if lenPath < lMinWPath6:
            lMinWPath6 = lenPath
            minWPath6 = path
print("\n问题6: 必经边 (N2,N4), (N13,N14) 的约束")
print("N0 到 N17 的最短加权路径: ", minWPath6)
print("N0 到 N17 的最短加权路径长度: ", lMinWPath6)

必经边跟多个必经点写法相似,将边的点看成是必经点,再去判断这两个必经点是否是相连的 

 

7. 案例

案例:蚂蚁的最优路径分析

2.1 问题描述 蚁巢有若干个储藏间(图中圆圈所示),储藏间之间有路径相连(路径拓扑结构如图所示)。

该图为无向图,路径通行的花费如图中线路上的数字所示,路径正反方向通行的花费相同。

要求从起点 N0 到终点 N17 的最优路径,并需要满足条件:

必须经过图中的绿色节点 N7、N12; 必须经过图中的两段绿色路段 (N2, N4)、(N13, N14); 必须避开图中的红色路段 (N11, N12); 求花费最少的最优路径。

代码:

# mathmodel17_v1.py
# Demo17 of mathematical modeling algorithm
# Demo of shortest path with constraints with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-09

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包

# 问题:蚂蚁的最优路径分析(西安邮电大学第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:(0,8),1:(7,12),2:(6,9),3:(5,6),4:(11,10),5:(14,8),  # 指定顶点位置
     6:(17,6),7:(10,4),8:(19,4),9:(18,12),10:(21,10),11:(28,12),
     12:(25,8),13:(30,7),14:(24,5),15:(29,4),16:(32,10),17:(37,8)}

fig, ax = plt.subplots(figsize=(9, 6))
nx.draw_networkx(gAnt, pos, with_labels=True, node_color='cyan', alpha=0.8)
labels = nx.get_edge_attributes(gAnt, 'weight')
nx.draw_networkx_edge_labels(gAnt, pos, edge_labels=labels, font_color='m')
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=3)
nx.draw_networkx_edges(gAnt, pos, edgelist=[(11, 12)], edge_color='r', width=3)

# 7. 限制条件:必经点 N7,N12,必经边 (N2,N4), (N13,N14),禁止边 (11,12)
# 解决方案:遍历从起点到终点的简单路径,求满足必经边条件的最短路径
gAntS = gAnt.copy()
gAntS.remove_edge(11,12)  # 删除禁止边 (11,12)
lMinWPath = minWPath = 1e9  # 置初值
for path in nx.all_simple_paths(gAntS, 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(gAntS.edges[edge]['weight'] for edge in nx.utils.pairwise(path))
        if lenPath < lMinWPath:
            lMinWPath = lenPath
            minWPath = path

print("\n蚂蚁最优路径问题(带有禁止点、禁止边、必经点、必经边的约束条件)")
print("约束条件:必经点 N7,N12,必经边 (N2,N4), (N13,N14),禁止边 (11,12)")
print("N0 到 N17 的最短加权路径: ", minWPath)
print("N0 到 N17 的最短加权路径长度: ", lMinWPath)

edgeList = []
for i in range(len(minWPath)-1):
    edgeList.append((minWPath[i],minWPath[i+1]))
nx.draw_networkx_edges(gAnt,pos,edgelist=edgeList,edge_color='b',width=4)  # 设置边的颜色
plt.show()

先去删除不能经过的点和边

gAntS.remove_edge(11,12)  # 删除禁止边 (11,12)

 然后设置必经点

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 是否相邻

将最短路径标出

edgeList = []
for i in range(len(minWPath)-1):
    edgeList.append((minWPath[i],minWPath[i+1]))
nx.draw_networkx_edges(gAnt,pos,edgelist=edgeList,edge_color='b',width=4)  # 设置边的颜色
plt.show()

8. 补充

# 找到一条从start到end的路径
def findPath(graph, start, end, path=[]):
    path = path + [start]
    if start == end:
        return path
    for node in graph[start]:
        if node not in path:
            newpath = findPath(graph, node, end, path)
            if newpath:
                return newpath
    return None


# 找到所有从start到end的路径
def findAllPath(graph, start, end, path=[]):
    path = path + [start]
    if start == end:
        return [path]

    paths = []  # 存储所有路径
    for node in graph[start]:
        if node not in path:
            newpaths = findAllPath(graph, node, end, path)
            for newpath in newpaths:
                paths.append(newpath)
    return paths


# 查找最短路径
def findShortestPath(graph, start, end, path=[]):
    path=path + [start]
    if start == end:
        return path

    shortestPath = []
    for node in graph[start]:
        if node not in path:
            newpath = findShortestPath(graph, node, end, path)
            if newpath:
                if not shortestPath or len(newpath) < len(shortestPath):
                    shortestPath = newpath
    return shortestPath


graph = {'A': ['B', 'C', 'D'],
         'B': ['E'],
         'C': ['D', 'F'],
         'D': ['B', 'E', 'G'],
         'E': [],
         'F': ['D', 'G'],
         'G': ['E']}

onepath = findPath(graph, 'A', 'G')
print('一条路径:', onepath)

allpath = findAllPath(graph, 'A', 'G')
print('\n所有路径:', allpath)

shortpath = findShortestPath(graph, 'A', 'G')
print('\n最短路径:', shortpath)
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值