NetworkX 最短路径算法选型图
目标:一步一步用 Dijkstra 解一个具体例子
我们用一个非常小的有向带权图,源点为 A,权重均为正数。图(邻接表形式):
- A → B (6)
- A → D (1)
- D → B (2)
- B → C (5)
- D → E (1)
- E → C (5)
节点集合:A, B, C, D, E。目标是从 A 找到到每个点的最短距离,并展示如何一步步更新 dist
和 prev
(用于重建路径)。
初始化
dist[A] = 0
(源点)- 其它
dist = ∞
:dist[B]=∞, dist[C]=∞, dist[D]=∞, dist[E]=∞
prev
全为None
:prev[A]=prev[B]=prev[C]=prev[D]=prev[E]=None
- 最小堆(或优先队列)初始放入
(0, A)
。
我会把堆写成有序列表表示(最小在左):例如 [(0,A)]
。
步骤 0 — 初始状态
- 堆:
[(0, A)]
- dist:
A:0, B:∞, C:∞, D:∞, E:∞
- prev: all
None
弹出 1:取出 A(dist=0)
弹出堆得到 u = A, d_u = 0
。现在松弛 A 的邻边:
-
松弛边 A→B (6):
-
计算通过 A 到 B 的距离:
d_u + w = 0 + 6 = 6
。(逐位算:0 + 6 = 6) -
原
dist[B] = ∞
,6 < ∞,所以更新:dist[B] = 6
prev[B] = A
- 把
(6, B)
推入堆
-
-
松弛边 A→D (1):
-
计算
0 + 1 = 1
。 -
原
dist[D] = ∞
,1 < ∞,更新:dist[D] = 1
prev[D] = A
- 推入
(1, D)
到堆
-
更新后:
- 堆(可能无序显示,但最小优先):
[(1, D), (6, B)]
- dist:
A:0, B:6, C:∞, D:1, E:∞
- prev:
A:None, B:A, C:None, D:A, E:None
弹出 2:取出 D(dist=1)
弹出 u = D, d_u = 1
。松弛 D 的邻边:
-
松弛 D→B (2):
-
通过 D 到 B 的候选距离:
d_u + w = 1 + 2 = 3
。(逐位算:1 + 2 = 3) -
现在
dist[B] = 6
,3 < 6,所以更新:dist[B] = 3
prev[B] = D
- 推入
(3, B)
到堆
-
-
松弛 D→E (1):
-
计算
1 + 1 = 2
。 -
dist[E] = ∞
,2 < ∞,更新:dist[E] = 2
prev[E] = D
- 推入
(2, E)
到堆
-
更新后:
- 堆(含旧的过时条目
(6, B)
仍在堆里):[(2, E), (6, B), (3, B)]
(实际堆内部顺序可能不同,但最小是(2,E)
) - dist:
A:0, B:3, C:∞, D:1, E:2
- prev:
A:None, B:D, C:None, D:A, E:D
弹出 3:取出 E(dist=2)
弹出 u = E, d_u = 2
。松弛 E 的邻边:
-
松弛 E→C (5):
-
通过 E 到 C 的候选距离:
d_u + w = 2 + 5 = 7
。(逐位算:2 + 5 = 7) -
dist[C] = ∞
,7 < ∞,更新:dist[C] = 7
prev[C] = E
- 推入
(7, C)
到堆
-
更新后:
- 堆:
[(3, B), (6, B), (7, C)]
((6,B)
仍旧为过时条目) - dist:
A:0, B:3, C:7, D:1, E:2
- prev:
A:None, B:D, C:E, D:A, E:D
弹出 4:取出 B(dist=3)
弹出堆,得到最小 (3, B)
(这是最新有效的 B 条目,因为 3 == dist[B]
)。松弛 B 的邻边:
-
松弛 B→C (5):
- 候选距离:
d_u + w = 3 + 5 = 8
。(逐位算:3 + 5 = 8) - 现在
dist[C] = 7
,8 < 7 不成立,因此不更新C
。(我们发现通过 B 到 C 的路径比已知的通过 E 的路径更长)
- 候选距离:
因此没有更新。
更新后堆仍有 (6, B)
(过时)和 (7, C)
:
- 堆:
[(6, B), (7, C)]
- dist 不变:
A:0, B:3, C:7, D:1, E:2
- prev 不变。
弹出 5:弹出过时的 B 条目 (6, B)
弹出 (6, B)
,但检查发现 dist[B] = 3
与条目距离 6
不一致,说明这是之前的过时条目,直接跳过,不做松弛。
堆现在: [(7, C)]
弹出 6:取出 C(dist=7)
弹出 (7, C)
,这是 C
的有效最小条目(7 == dist[C]
)。松弛 C 的邻边:在我们图中 C 没有出边(或没有更有用的出边),所以没有任何更新。
堆为空,算法结束。
最终结果
-
最短距离
dist
:A: 0
B: 3
—— 路径 A → D → B,计算:A→D (1) + D→B (2) = 3C: 7
—— 路径 A → D → E → C,计算:A→D (1) + D→E (1) + E→C (5) = 7D: 1
—— 路径 A → DE: 2
—— 路径 A → D → E
-
prev
指向(用于重建):prev[A] = None
prev[D] = A
prev[B] = D
prev[E] = D
prev[C] = E
路径重建举例(从 prev
回溯)
重建到 C
的路径:
C <- prev[C]=E <- prev[E]=D <- prev[D]=A
,逆序得到:A → D → E → C。
重建到 B
的路径:
B <- D <- A
,逆序:A → D → B。
简短 Python 验证(你可以复制运行)
import heapq
INF = float('inf')graph = {'A': [('B', 6), ('D', 1)],'B': [('C', 5)],'C': [],'D': [('B', 2), ('E', 1)],'E': [('C', 5)],
}def dijkstra(graph, src):dist = {v: INF for v in graph}prev = {v: None for v in graph}dist[src] = 0heap = [(0, src)]while heap:d,u = heapq.heappop(heap)if d != dist[u]:continuefor v,w in graph[u]:if dist[v] > d + w:dist[v] = d + wprev[v] = uheapq.heappush(heap, (dist[v], v))return dist, prevdist, prev = dijkstra(graph, 'A')
print('dist =', dist)
print('prev =', prev)
# 重建 C 的路径
path = []
cur = 'C'
while cur:path.append(cur)cur = prev[cur]
path.reverse()
print('A -> C path:', path) # ['A','D','E','C']
运行后你会看到:
dist = {'A': 0, 'B': 3, 'C': 7, 'D': 1, 'E': 2}
prev = {'A': None, 'B': 'D', 'C': 'E', 'D': 'A', 'E': 'D'}
A -> C path: ['A', 'D', 'E', 'C']
✅ 一、使用 networkx
自带的 Dijkstra 函数
networkx
是 Python 最常用的图算法库,它内置了 Dijkstra 算法及其变体。
安装:
pip install networkx
💡 示例:最简单的用法
import networkx as nx# 创建一个有向带权图
G = nx.DiGraph()# 添加带权边 (起点, 终点, 权重)
G.add_weighted_edges_from([('A', 'B', 6),('A', 'D', 1),('D', 'B', 2),('B', 'C', 5),('D', 'E', 1),('E', 'C', 5)
])# 1️⃣ 计算从 A 到所有点的最短距离
lengths = nx.single_source_dijkstra_path_length(G, 'A')
print("最短距离:", lengths)# 2️⃣ 计算从 A 到所有点的最短路径
paths = nx.single_source_dijkstra_path(G, 'A')
print("最短路径:", paths)# 3️⃣ 只算从 A 到 C 的最短路径
path = nx.dijkstra_path(G, 'A', 'C')
length = nx.dijkstra_path_length(G, 'A', 'C')print("A 到 C 的最短路径:", path)
print("A 到 C 的距离:", length)
输出结果:
最短距离: {'A': 0, 'D': 1, 'B': 3, 'E': 2, 'C': 7}
最短路径: {'A': ['A'], 'D': ['A', 'D'], 'B': ['A', 'D', 'B'], 'E': ['A', 'D', 'E'], 'C': ['A', 'D', 'E', 'C']}
A 到 C 的最短路径: ['A', 'D', 'E', 'C']
A 到 C 的距离: 7
✅ 二、networkx
中 Dijkstra 的相关函数速查表
函数名 | 作用 | 备注 |
---|---|---|
nx.dijkstra_path(G, source, target, weight='weight') | 求最短路径(单源单目标) | 返回路径节点列表 |
nx.dijkstra_path_length(G, source, target, weight='weight') | 求最短路径的距离 | 返回距离(数字) |
nx.single_source_dijkstra(G, source, target=None, weight='weight') | 同时返回最短路径和距离 | 若指定 target 只算一个 |
nx.single_source_dijkstra_path(G, source) | 求从源点到所有点的最短路径 | 返回 {节点: 路径} |
nx.single_source_dijkstra_path_length(G, source) | 求从源点到所有点的最短距离 | 返回 {节点: 距离} |
nx.multi_source_dijkstra(G, sources, target=None) | 多源最短路径 | 可设多个起点 |
nx.all_pairs_dijkstra(G, weight='weight') | 全源最短路径 | 返回所有对的最短路径 |
nx.all_pairs_dijkstra_path_length(G) | 全源最短距离 | 返回 {源点: {终点: 距离}} |
✅ 三、如果你的图用邻接矩阵表示
也可以用 scipy
的 Dijkstra:
from scipy.sparse.csgraph import dijkstra
from scipy.sparse import csr_matrix# 邻接矩阵(∞用0或np.inf)
import numpy as np
graph = np.array([[0, 6, 0, 1, 0],[0, 0, 5, 0, 0],[0, 0, 0, 0, 0],[0, 2, 0, 0, 1],[0, 0, 5, 0, 0]
])# csr_matrix 稀疏矩阵
g = csr_matrix(graph)# 从节点0 (A) 出发
dist, predecessors = dijkstra(csgraph=g, directed=True, indices=0, return_predecessors=True)print("最短距离:", dist)
print("前驱节点索引:", predecessors)
输出结果类似:
最短距离: [0. 3. 7. 1. 2.]
前驱节点索引: [-9999 3 4 0 3]
(索引 0=A, 1=B, 2=C, 3=D, 4=E)
✅ 四、小结
需求 | 推荐函数 | 所属库 |
---|---|---|
简单地求最短路径(图结构) | nx.dijkstra_path | networkx |
求所有节点的最短距离 | nx.single_source_dijkstra_path_length | networkx |
用矩阵形式计算 | scipy.sparse.csgraph.dijkstra | scipy |
只想学习算法逻辑 | 手写版本(前面我们写的) | 无需库 |
🧭 一、最常用的最短路径算法总览
算法 | 主要函数 | 支持负权 | 适用场景 | 备注 |
---|---|---|---|---|
Dijkstra | nx.dijkstra_path / nx.single_source_dijkstra | ❌ 不支持 | 边权非负的图(稀疏或中等) | 默认权重字段为 "weight" |
Bellman-Ford | nx.bellman_ford_path / nx.single_source_bellman_ford | ✅ 支持 | 存在负权边但无负权环 | 比 Dijkstra 慢 |
Floyd–Warshall | nx.floyd_warshall / nx.floyd_warshall_predecessor_and_distance | ✅ 支持 | 计算所有点对最短路径(全源) | 适合小规模图 |
Johnson | nx.johnson | ✅ 支持 | 计算所有点对最短路径(可含负权边) | 对稀疏图效率高 |
BFS(广度优先搜索) | nx.shortest_path (未指定权重) | — | 无权图(或权重全相等) | O(V+E),最简单最快 |
A* (A-star) | nx.astar_path / nx.astar_path_length | ❌ 不支持 | 有启发函数的单源单目标搜索 | 常用于地图最短路 |
Yen’s K-Shortest Paths | nx.shortest_simple_paths | ❌ 通常非负权 | 求前 K 条最短简单路径 | 比较高级的算法 |
🧩 二、每类算法的常用函数详细说明
1️⃣ Dijkstra(迪杰斯特拉)
最常用、最经典、权重非负时最快。
nx.dijkstra_path(G, source, target, weight='weight')
nx.dijkstra_path_length(G, source, target, weight='weight')
nx.single_source_dijkstra(G, source)
nx.all_pairs_dijkstra(G)
2️⃣ Bellman–Ford(贝尔曼–福德)
支持负权边(只要没有负权环)。
nx.bellman_ford_path(G, source, target, weight='weight')
nx.single_source_bellman_ford(G, source)
nx.bellman_ford_predecessor_and_distance(G, source)
3️⃣ Floyd–Warshall(弗洛伊德–沃肖尔)
适合求所有节点对之间的最短路径。
nx.floyd_warshall(G, weight='weight') # 返回距离矩阵字典
nx.floyd_warshall_predecessor_and_distance(G, weight='weight')
4️⃣ Johnson(约翰逊算法)
也是全源最短路径算法,但对稀疏图(边远小于节点平方)更高效。
nx.johnson(G, weight='weight')
5️⃣ BFS(Breadth-First Search)
权重全为 1 或未加权时的最短路径算法。
nx.shortest_path(G, source, target)
nx.shortest_path_length(G, source, target)
nx.single_source_shortest_path(G, source)
nx.all_pairs_shortest_path(G)
💡 小技巧:
如果你的图有权重但全为 1,可以用nx.single_source_shortest_path_length(G, source)
。
6️⃣ A* (A-Star)
单源单目标,常用于带启发函数的路径搜索(例如地图、游戏寻路)。
nx.astar_path(G, source, target, heuristic=None, weight='weight')
nx.astar_path_length(G, source, target, heuristic=None, weight='weight')
示例(假设坐标启发):
def heuristic(u, v):(x1, y1) = pos[u](x2, y2) = pos[v]return ((x1-x2)**2 + (y1-y2)**2)**0.5
nx.astar_path(G, 'A', 'Z', heuristic=heuristic)
7️⃣ 多条最短路径(K-Shortest Paths)
如果你想求多条不重复的最短路径:
paths = list(nx.shortest_simple_paths(G, source, target, weight='weight'))
print(paths[:3]) # 前3条最短路径
📦 三、全源最短路径相关函数总览
函数 | 算法类型 | 支持负权 | 返回内容 |
---|---|---|---|
nx.all_pairs_dijkstra_path | Dijkstra | ❌ | 各节点对路径 |
nx.all_pairs_dijkstra_path_length | Dijkstra | ❌ | 各节点对距离 |
nx.floyd_warshall | Floyd-Warshall | ✅ | 各节点对距离 |
nx.johnson | Johnson | ✅ | 各节点对距离 |
🧠 四、如何选算法?
场景 | 推荐算法 |
---|---|
无权图(边权=1) | BFS (nx.shortest_path ) |
边权非负 | Dijkstra (nx.single_source_dijkstra ) |
含负权但无负环 | Bellman–Ford 或 Johnson |
所有节点对最短路(全源) | Floyd–Warshall(小图) / Johnson(大图) |
地图或启发式搜索 | A* |
想要多条路径 | shortest_simple_paths |
💡 示例对比
import networkx as nxG = nx.DiGraph()
G.add_weighted_edges_from([('A','B',1), ('B','C',2), ('A','C',5), ('C','D',1)
])# Dijkstra
print("Dijkstra:", nx.dijkstra_path(G, 'A', 'D'))# Bellman–Ford
print("Bellman–Ford:", nx.bellman_ford_path(G, 'A', 'D'))# Floyd–Warshall
print("Floyd–Warshall 距离矩阵:", nx.floyd_warshall(G))# 全源 Johnson
print("Johnson 全源:", nx.johnson(G))