图与网络——最短路问题精解
最短路问题(Shortest Path Problem)是图论中的一个经典问题,广泛应用于现实世界中的多个领域。该问题的核心在于如何在一个图中找到给定起点和终点之间路径权重最小的路径。路径权重通常代表时间、成本、距离等因素,因此最短路问题不仅具有理论上的研究价值,还在实际问题的解决中发挥了重要作用,如管路铺设、线路安装、交通规划等领域。
一、最短路问题概述
1.1 最短路问题的界定
在最短路问题中,图的每一条边都赋予一个权重,通常表示距离、成本、时间或其他与问题相关的度量标准。给定一个图和两点,最短路问题的目标是找到两点之间经过的路径,使得沿路径的边权重之和最小。根据问题的具体形式,最短路问题可以分为不同的类型。
单源最短路问题:这是经典的、单源的、标准的最短路问题,目标是给定一个起点,找到从该起点到图中其他所有点的最短路径。常见的算法包括Dijkstra算法、Bellman-Ford算法等。
给定起点的最短路径问题:即从一个固定的起点出发,求出从该起点到图中其他所有节点的最短路径。这类问题在很多场景下都有应用,如在交通运输中规划从一个城市出发到其他城市的最短路线。
给定终点的最短路径问题:这是给定终点情况下的最短路径问题。在无向图中,这与给定起点问题相同,而在有向图中则需要将路径的方向进行逆转。这类问题也可以通过修改Dijkstra算法来解决。
全局最短路问题:多源最短路问题,即任意两点之间的最短路径问题。求解这一问题需要找到图中每对点之间的最短路径。常见的算法包括Floyd-Warshall算法和Johnson算法。全局最短路问题的典型应用是在交通管理系统中,计算城市路网中任意两个点之间的最短时间或最短距离。
给定一个网络图 ,记 中每一条弧 上的权为 。指定 的起点 和终点 ,设 是 中从 到 的一条路,则定义路 的权是 中所有弧的权之和,记为 ,即:
若 是 中 到 的一条路,且满足:
其中,min 表示对 的所有从 到 的路 的权取最小,则称 为从 到 的最短路, 为从 到 的最短距离。
在一个图 中,求从 到 的最短路或最短距离的问题称为最短路问题,这即时经典的、单源的、标准的最短路问题。
1.2最短路问题的应用
城市网络管理:最短路问题在城市交通网络中的应用非常广泛。现代城市交通系统复杂多样,不仅要考虑道路的长度,还需要综合考虑实时交通状况、路面拥堵等问题。在城市网络中,每条道路可以被赋予时间权重,时间权重表示车辆在该道路上行驶的时间。利用最短路算法,可以计算出车辆从一个地点到达目的地所需的最短时间路线,进而为交通管理系统提供优化的行车路线规划。例如,许多导航应用程序(如Google地图、高德地图等)实时获取交通数据,根据不同路段的拥堵情况更新道路权重,计算出当前情况下驾驶时间最短的路线。这些应用通过最短路算法快速响应,并为驾驶者提供最优的行驶路线,减少交通拥堵的影响,提高道路的利用率。
舰船通道设计:图论及最短路径问题在舰船通道优化设计中也得到了应用。在舰艇的多层甲板结构中,人员的通行效率至关重要,尤其是在紧急情况下,人员必须快速而有序地撤离。为此,设计合理的通道路线显得尤为重要。在舰船通道设计中,每一段通道(包括楼梯)都可以看作图中的边,而每条边的权重代表了人员通过该段通道所需的时间。该时间可以根据人员流动速度和通道拥堵情况来估算。通过建立舰船的通道网络图,并以行程时间为通道网络的路权,设计者可以利用最短路算法来计算出从任何起点到终点的最短撤离路径。这种方法在舰艇设计中可以确保人员的快速、安全撤离,提高通行效率,减少不必要的延误。
其他应用领域:最短路问题在很多其他领域也有广泛的应用。例如,在物流配送中,最短路问题用于寻找最优的配送路线,以降低运输成本和时间;在互联网路由协议中,最短路算法被用于寻找数据包从源节点到目的节点的最优传输路径;在社交网络分析中,最短路径算法可用于衡量网络中两个节点之间的距离,帮助研究人员发现用户之间的潜在联系。
二、最短路问题的Python实现
最短路问题有多种经典的算法来解决:Dijkstra算法:用于解决非负权重的单源最短路径问题。该算法以贪心的思想为基础,逐步扩展最短路径,直到找到所有点的最短路径。Bellman-Ford算法:可解决带有负权边的单源最短路径问题,能够处理负权回路。Floyd-Warshall算法:用于解决全局最短路径问题,能够找出任意两点之间的最短路径,时间复杂度较高,适用于小规模图。Johnson算法:通过重新标定图中的边权,能够高效地解决大规模图的全局最短路径问题。
Dijkstra算法 | Floyd算法 |
---|---|
![]() |
![]() |
2.1 Dijkstra算法
Dijkstra算法是由荷兰计算机科学家狄克斯特拉于1959年提出的。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
Bellman最优化原理
若 是网络图 中从 到 的一条最短路, 是 中除 与 外的任意一个中间点,则沿 从 到 的路 也是 到 的最短路。
在上图中,从 到 有三条路: (路权 9); (路权 13); (路权 10)。因为 到 最短路是 ,所以 也是 到 的最短路。
从上就得如下求解思路:
- 为求得 到 的最短路,可先求得 到中间点的最短路,然后由中间点再逐步过渡到终点 。
- 在计算过程中,需要把顶点集 中“经判断为最短路 途经之点 ”和“尚未判断是否为最短路 途经之点 ”区分开来,为此可设置集合 和 ,前者归入 ,后者归入 。
- 为区分中间点 是否已归入 中以及逆向追踪的便利,可在归入 中的点 给予双标号 ,此中 表示从 到 最短路的距离,而 则为从 到 最短路 中 的前一途经节点。
算法步骤
设图为 , V为点集, E为边集, 其中, 对于 为从点i到点j的距离,这样,可以归纳出简化的 Dijkstra 算法——双标号法,且能用其求解无向图的最短路径。
(1) 给起点 标号为 是所有顶点的集合。
(2)基于标号集合 ,未标号集合 ,找出弧或边的集合 。
(3)如果弧的集 ,则计算结束。如果 已经标号 ,则从 到 的最短路长度为 , 而最短路径可从 点反向追踪到起点 获得。如果 未标号, 则可以判定不存在从 到 的最短路径。如果弧的集合 , 则转下一步。
(4)对集合 中的每一条弧,计算
则给顶点 标号为 , 返回步骤(2)。
例1:求下面网络图的到最短路
网络图 | 最短路 |
---|---|
![]() |
![]() |
解:首先给起点 标号, 第一个标号表示的是始点, 第二个标号表示 , 由于 是起点, 故令起点的第一个标号为 0 , 令 , 于是得到起点 的标号 。
- 迭代1: 点的始点是 , 计算 , 即
得到 的标号 。
- 迭代 2: 点的始点是 , 计算 , 即
得到 的标号 。
- 迭代 3: 点的始点是 , 计算, 即
得到 的标号 。
- 迭代 4: 的始点有 但 还没有标号, 因此首先计算 。 点的来源是 都已经标号, 计算 , 即
得到 的标号 。
- 迭代 5: 的始点有 , 计算 , 即
得到 的标号 。
- 迭代 6: 的始点有 , 计算 , 即
得到 的标号 。
- 迭代 7: 的始点有 都已经标号, 计算 , 即
得到 的标号 。
标号过程结束, 见上图最短路所示。沿着第一个标号, 由终点反向跟踪, 很容易求得该网络最短路径 , 而终点 的第二个标号就是此最短路长度。
例2:求下面网络图的到最短路
import networkx as nx
import matplotlib.pyplot as plt
import heapq
import warnings
# 忽略DeprecationWarning警告
warnings.filterwarnings("ignore", category=DeprecationWarning)
# Dijkstra算法实现
def dijkstra(graph, start, end):
queue = [(0, start, [])]
visited = set() # 用于记录已访问的节点
while queue:
(cost, node, path) = heapq.heappop(queue)
if node in visited:
continue
visited.add(node)
path = path + [node]
if node == end:
return (cost, path)
for (adjacent, weight) in graph.get(node, []):
if adjacent not in visited:
heapq.heappush(queue, (cost + weight, adjacent, path))
return float("inf"), [] # 如果无可行路径,返回无穷大和空路径
# 定义图结构 (邻接表)
graph = {
0: [(1, 2), (3, 4)], # 节点0到节点1的权重为2,到节点3的权重为4
1: [(2, 2), (4, 5)], # 节点1到节点2的权重为2,到节点4的权重为5
2: [(5, 3)], # 节点2到节点5的权重为3
3: [(4, 4)], # 节点3到节点4的权重为4
4: [(2, 3), (5, 1)], # 节点4到节点2的权重为3,到节点5的权重为1
5: [] # 节点5为终点
}
# 使用Dijkstra算法找到从0到5的最短路径
distance, shortest_path = dijkstra(graph, 0, 5)
# 输出最短路径和距离
print(f"从节点 0 到节点 5 的最短路径为: {shortest_path}, 距离为: {distance}")
# 可视化图形
plt.figure(figsize=(12, 8)) # 调整图形尺寸,使整体图形更大
G = nx.DiGraph() # 有向图
# 添加图的边
edges = [
(0, 1, 2), (0, 3, 4), (1, 2, 2), (1, 4, 5),
(2, 5, 3), (3, 4, 4), (4, 2, 3), (4, 5, 1)
]
# 将边添加到图中
for edge in edges:
G.add_edge(edge[0], edge[1], weight=edge[2])
# 设置节点位置
pos = {0: (0, 2), 1: (1, 3), 2: (2, 3), 3: (0, 1),
4: (1, 1), 5: (2, 1)}
# 获取边权重
edge_labels = {(u, v): f'{d["weight"]}' for u, v, d in G.edges(data=True)}
# 绘制节点和边,增加节点大小和字体大小
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=4000, font_size=35, font_weight='bold')
# 绘制边权重并调整字体大小
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=35)
# 高亮显示最短路径,线条宽度加粗三倍
path_edges = [(shortest_path[i], shortest_path[i+1]) for i in range(len(shortest_path)-1)]
nx.draw_networkx_edges(G, pos, edgelist=path_edges, edge_color='red', width=10, arrowsize=30) # 线条宽度和箭头加粗三倍
# 调整箭头大小,使用 `arrowsize` 参数控制箭头的大小
nx.draw_networkx_edges(G, pos, edgelist=edges, arrows=True, width=6, arrowstyle='-|>', arrowsize=50)
# 显示图形
plt.title("Graph Representation with Shortest Path Highlighted", fontsize=36)
plt.show()
从节点 0 到节点 5 的最短路径为: [0, 1, 2, 5], 距离为: 7
2.2 Floyd算法
Floyd算法是一个基于贪心、动态规划来求一个图中所有点到所有点最短路径的算法,适用于带权有向图或无向图,可以处理负权边,但不能处理负权回路,时间复杂度 (O(n^3))。算法核心公式
- 分别指行、列
- 指起点到终点途中除终点外的所有节点数
- 表示从节点到节点的最短路径
- 表示从节点到节点的最短路径
- 表示从节点到节点的最短路径
通过上式规则,逐步引入中间节点的信息来改进从从节点到节点的最短路径估计。
算法步骤
- 初始化:初始化图的邻接矩阵,初始化为图中到的权,如果 从节点到节点之间没有直接边,则设置为无穷大(∞),自循环的距离为0。
- 更新邻接矩阵:更新上述邻接矩阵为距离矩阵,具体是使用三重循环遍历所有顶点对和中间点来更新距离矩阵。在此过程中主要思想是对于每对顶点,检查是否通过某个中间点可以找到更短的路径。
- 检测负权回路:Floyd算法可以检测负权回路(即从某个顶点出发经过若干条边后回到该顶点的总权重为负),但无法处理负权回路。在完成所有的更新后,检查对角线元素是否小于0。如果小于0则表示存在负权回路。
例3:求下面网络图的最短路
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
# Floyd-Warshall算法实现
def floyd_warshall(graph):
num_vertices = len(graph)
dist = np.copy(graph) # 距离矩阵初始化为输入的图
next_vertex = np.zeros((num_vertices, num_vertices), dtype=int) # 保存路径中继点
# 初始化路径
for i in range(num_vertices):
for j in range(num_vertices):
if graph[i][j] != float('inf') and i != j:
next_vertex[i][j] = j + 1 # 节点从1开始
# Floyd-Warshall核心算法
for k in range(num_vertices):
for i in range(num_vertices):
for j in range(num_vertices):
if dist[i][j] > dist[i][k] + dist[k][j]:
dist[i][j] = dist[i][k] + dist[k][j]
next_vertex[i][j] = next_vertex[i][k]
return dist, next_vertex
# 构建邻接矩阵(根据图像)
INF = float('inf')
graph = [
[0, INF, INF, 3, INF, 1, INF], # 1号节点
[INF, 0, INF, 2, INF, INF, 1], # 2号节点
[INF, INF, 0, 3, 4, INF, 3], # 3号节点
[3, 2, 3, 0, INF, INF, INF], # 4号节点
[INF, INF, 4, INF, 0, 3, 5], # 5号节点
[1, INF, INF, INF, 3, 0, INF], # 6号节点
[INF, 1, 3, INF, 5, INF, 0] # 7号节点
]
# 运行Floyd-Warshall算法
dist_matrix, path_matrix = floyd_warshall(graph)
# 打印所有节点对的最短路径和距离(以矩阵形式输出)
def print_shortest_path_matrix(dist_matrix):
print("最短路径距离矩阵:")
print(np.array(dist_matrix))
# 打印最短路径矩阵
print_shortest_path_matrix(dist_matrix)
# 可视化图
def draw_graph(graph):
G = nx.Graph()
num_vertices = len(graph)
# 添加节点
for i in range(num_vertices):
G.add_node(i + 1)
# 添加边和权值
for i in range(num_vertices):
for j in range(num_vertices):
if graph[i][j] != INF and i != j:
G.add_edge(i + 1, j + 1, weight=graph[i][j])
pos = nx.spring_layout(G)
labels = nx.get_edge_attributes(G, 'weight')
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=500, font_size=10)
nx.draw_networkx_edge_labels(G, pos, edge_labels=labels)
plt.show()
# 绘制图
draw_graph(graph)
最短路径距离矩阵:
[[0. 5. 6. 3. 4. 1. 6.]
[5. 0. 4. 2. 6. 6. 1.]
[6. 4. 0. 3. 4. 7. 3.]
[3. 2. 3. 0. 7. 4. 3.]
[4. 6. 4. 7. 0. 3. 5.]
[1. 6. 7. 4. 3. 0. 7.]
[6. 1. 3. 3. 5. 7. 0.]]
三、最长路问题的Python实现
import networkx as nx
import matplotlib.pyplot as plt
from collections import defaultdict
# 创建图类
class Graph:
def __init__(self, vertices):
self.V = vertices # 顶点的数量
self.graph = defaultdict(list) # 默认字典,用于存储图
# 添加边到图中
def add_edge(self, u, v, weight):
self.graph[u].append((v, weight))
# 拓扑排序函数
def topological_sort_util(self, v, visited, stack):
visited[v] = True
if v in self.graph:
for node, weight in self.graph[v]:
if not visited[node]:
self.topological_sort_util(node, visited, stack)
stack.append(v)
# 获取DAG中从给定起点到终点的最长路径
def longest_path(self, s, t):
# 初始化
visited = [False] * self.V
stack = []
# 拓扑排序
for i in range(self.V):
if not visited[i]:
self.topological_sort_util(i, visited, stack)
# 初始化距离数组,起点距离为0,其它为负无穷
dist = [-float('inf')] * self.V
dist[s] = 0
# 初始化路径字典来跟踪路径
path = {i: [] for i in range(self.V)}
path[s] = [s]
# 处理拓扑排序
while stack:
i = stack.pop()
if dist[i] != -float('inf'):
for node, weight in self.graph[i]:
if dist[node] < dist[i] + weight:
dist[node] = dist[i] + weight
# 更新路径信息
path[node] = path[i] + [node]
# 如果终点不可达
if dist[t] == -float('inf'):
print(f"从顶点 {s} 到顶点 {t} 不可达")
else:
# 打印从s到t的最长路径及其距离
print(f"从顶点 {s} 到顶点 {t} 的最长路径为: {' -> '.join(map(str, path[t]))}")
print(f"路径长度为: {dist[t]}")
return dist, path
# 绘制图形
def draw_graph(self):
G = nx.DiGraph()
# 添加有向边及权重
for u in self.graph:
for v, weight in self.graph[u]:
G.add_edge(u, v, weight=weight)
# 设置布局和绘制参数,调整 k 参数拉开节点
pos = nx.spring_layout(G, k=1.2) # 调整 k 参数,k 值越大节点距离越远
plt.figure(figsize=(8, 6))
# 绘制节点和边
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=3000, font_size=12, font_weight='bold', arrows=True)
nx.draw_networkx_edge_labels(G, pos, edge_labels={(u, v): f'{weight}' for u, v, weight in G.edges(data='weight')}, font_color='red')
# 显示图形
plt.title("有向无环图(DAG)与边权重")
plt.show()
# 示例用法
g = Graph(5) # 图中的5个节点 A, B, C, D, E
g.add_edge(0, 1, 3) # A -> B, 权重3
g.add_edge(0, 2, 2) # A -> C, 权重2
g.add_edge(1, 3, 1) # B -> D, 权重1
g.add_edge(3, 0, 4) # D -> A, 权重4
g.add_edge(3, 1, 1) # D -> B, 权重1
g.add_edge(3, 2, 6) # D -> C, 权重6
g.add_edge(3, 4, 4) # D -> E, 权重4
g.add_edge(4, 2, 4) # D -> C, 权重4
# 查找从顶点A(0)到顶点E(4)的最长路径
g.longest_path(0, 2)
# 绘制图形
g.draw_graph()
总结
最短路问题是图论中的核心问题之一,具有广泛的应用价值。其目标是在图中找到两点之间权重总和最小的路径,这些权重通常代表时间、成本或距离。最短路问题在多个领域发挥重要作用,例如,城市交通管理可以通过最短路径算法为车辆规划最优路线,减少交通拥堵;在物流配送中,最短路算法有助于找到运输成本最低的路线;在互联网网络路由中,最短路算法用于确定数据包的最佳传输路径。常见的最短路算法包括Dijkstra算法、Bellman-Ford算法和Floyd-Warshall算法等,针对不同问题和需求提供高效的解决方案。最短路问题通过这些算法在优化决策中发挥了关键作用,提升了效率和资源利用率。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!