Skip to content

Commit 8be59cc

Browse files
committed
更新「多源最短路径」相关内容
1 parent ea66d5e commit 8be59cc

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
## 1. 多源最短路径简介
2+
3+
> **多源最短路径(All-Pairs Shortest Paths)**:对于一个带权图 $G = (V, E)$,计算图中任意两个顶点之间的最短路径长度。
4+
5+
多源最短路径问题的核心是找到图中任意两个顶点之间的最短路径。这个问题在许多实际应用中都非常重要,比如:
6+
7+
1. 网络路由中的路由表计算
8+
2. 地图导航系统中的距离矩阵计算
9+
3. 社交网络中的最短关系链分析
10+
4. 交通网络中的最优路径规划
11+
12+
常见的解决多源最短路径问题的算法包括:
13+
14+
1. **Floyd-Warshall 算法**:一种动态规划算法,可以处理负权边,但不能处理负权环。
15+
2. **Johnson 算法**:结合了 Bellman-Ford 算法和 Dijkstra 算法,可以处理负权边,但不能处理负权环。
16+
3. **重复 Dijkstra 算法**:对每个顶点运行一次 Dijkstra 算法,适用于无负权边的图。
17+
18+
## 2. Floyd-Warshall 算法
19+
20+
### 2.1 Floyd-Warshall 算法的算法思想
21+
22+
> **Floyd-Warshall 算法**:一种动态规划算法,通过逐步考虑中间顶点来更新任意两点之间的最短路径。
23+
24+
Floyd-Warshall 算法的核心思想是:
25+
26+
1. 对于图中的任意两个顶点 $i$ 和 $j$,考虑是否存在一个顶点 $k$,使得从 $i$ 到 $k$ 再到 $j$ 的路径比已知的从 $i$ 到 $j$ 的路径更短
27+
2. 如果存在这样的顶点 $k$,则更新从 $i$ 到 $j$ 的最短路径
28+
3. 通过考虑所有可能的中间顶点 $k$,最终得到任意两点之间的最短路径
29+
30+
### 2.2 Floyd-Warshall 算法的实现步骤
31+
32+
1. 初始化距离矩阵 $dist$,其中 $dist[i][j]$ 表示从顶点 $i$ 到顶点 $j$ 的最短路径长度
33+
2. 对于每对顶点 $(i, j)$,如果存在边 $(i, j)$,则 $dist[i][j]$ 设为边的权重,否则设为无穷大
34+
3. 对于每个顶点 $k$,作为中间顶点:
35+
- 对于每对顶点 $(i, j)$,如果 $dist[i][k] + dist[k][j] < dist[i][j]$,则更新 $dist[i][j]$
36+
4. 重复步骤 3,直到考虑完所有可能的中间顶点
37+
5. 返回最终的距离矩阵
38+
39+
### 2.3 Floyd-Warshall 算法的实现代码
40+
41+
```python
42+
def floyd_warshall(graph, n):
43+
# 初始化距离矩阵
44+
dist = [[float('inf') for _ in range(n)] for _ in range(n)]
45+
46+
# 设置直接相连的顶点之间的距离
47+
for i in range(n):
48+
dist[i][i] = 0
49+
for j, weight in graph[i].items():
50+
dist[i][j] = weight
51+
52+
# 考虑每个顶点作为中间顶点
53+
for k in range(n):
54+
for i in range(n):
55+
for j in range(n):
56+
if dist[i][k] != float('inf') and dist[k][j] != float('inf'):
57+
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
58+
59+
return dist
60+
```
61+
62+
代码解释:
63+
64+
1. `graph` 是一个字典,表示图的邻接表。例如,`graph[0] = {1: 3, 2: 4}` 表示从节点 0 到节点 1 的边权重为 3,到节点 2 的边权重为 4。
65+
2. `n` 是图中顶点的数量。
66+
3. `dist` 是一个二维数组,存储任意两点之间的最短路径长度。
67+
4. 首先初始化距离矩阵,将对角线元素设为 0,表示顶点到自身的距离为 0。
68+
5. 然后设置直接相连的顶点之间的距离。
69+
6. 主循环中,对于每个顶点 $k$,考虑它作为中间顶点时,是否能缩短其他顶点之间的距离。
70+
7. 最终返回的距离矩阵中,$dist[i][j]$ 表示从顶点 $i$ 到顶点 $j$ 的最短路径长度。
71+
72+
### 2.4 Floyd-Warshall 算法复杂度分析
73+
74+
- **时间复杂度**:$O(V^3)$
75+
- 需要三层嵌套循环,分别遍历所有顶点
76+
- 因此总时间复杂度为 $O(V^3)$
77+
78+
- **空间复杂度**:$O(V^2)$
79+
- 需要存储距离矩阵,大小为 $O(V^2)$
80+
- 不需要额外的空间来存储图的结构,因为使用邻接表表示
81+
82+
Floyd-Warshall 算法的主要优势在于:
83+
84+
1. 实现简单,容易理解
85+
2. 可以处理负权边
86+
3. 可以检测负权环(如果某个顶点到自身的距离变为负数,说明存在负权环)
87+
4. 适用于稠密图
88+
89+
主要缺点:
90+
91+
1. 时间复杂度较高,不适用于大规模图
92+
2. 空间复杂度较高,需要存储完整的距离矩阵
93+
3. 不能处理负权环
94+
95+
## 3. Johnson 算法
96+
97+
### 3.1 Johnson 算法的算法思想
98+
99+
> **Johnson 算法**:一种结合了 Bellman-Ford 算法和 Dijkstra 算法的多源最短路径算法,可以处理负权边,但不能处理负权环。
100+
101+
Johnson 算法的核心思想是:
102+
103+
1. 通过重新赋权,将图中的负权边转换为非负权边
104+
2. 对每个顶点运行一次 Dijkstra 算法,计算最短路径
105+
3. 将结果转换回原始权重
106+
107+
### 3.2 Johnson 算法的实现步骤
108+
109+
1. 添加一个新的顶点 $s$,并添加从 $s$ 到所有其他顶点的边,权重为 0
110+
2. 使用 Bellman-Ford 算法计算从 $s$ 到所有顶点的最短路径 $h(v)$
111+
3. 重新赋权:对于每条边 $(u, v)$,新的权重为 $w(u, v) + h(u) - h(v)$
112+
4. 对每个顶点 $v$,使用 Dijkstra 算法计算从 $v$ 到所有其他顶点的最短路径
113+
5. 将结果转换回原始权重:对于从 $u$ 到 $v$ 的最短路径,原始权重为 $d(u, v) - h(u) + h(v)$
114+
115+
### 3.3 Johnson 算法的实现代码
116+
117+
```python
118+
from collections import defaultdict
119+
import heapq
120+
121+
def johnson(graph, n):
122+
# 添加新顶点 s
123+
new_graph = defaultdict(dict)
124+
for u in graph:
125+
for v, w in graph[u].items():
126+
new_graph[u][v] = w
127+
new_graph[n][u] = 0 # 从 s 到所有顶点的边权重为 0
128+
129+
# 使用 Bellman-Ford 算法计算 h(v)
130+
h = [float('inf')] * (n + 1)
131+
h[n] = 0
132+
133+
for _ in range(n):
134+
for u in new_graph:
135+
for v, w in new_graph[u].items():
136+
if h[v] > h[u] + w:
137+
h[v] = h[u] + w
138+
139+
# 检查是否存在负权环
140+
for u in new_graph:
141+
for v, w in new_graph[u].items():
142+
if h[v] > h[u] + w:
143+
return None # 存在负权环
144+
145+
# 重新赋权
146+
reweighted_graph = defaultdict(dict)
147+
for u in graph:
148+
for v, w in graph[u].items():
149+
reweighted_graph[u][v] = w + h[u] - h[v]
150+
151+
# 对每个顶点运行 Dijkstra 算法
152+
dist = [[float('inf') for _ in range(n)] for _ in range(n)]
153+
for source in range(n):
154+
# 初始化距离数组
155+
d = [float('inf')] * n
156+
d[source] = 0
157+
158+
# 使用优先队列
159+
pq = [(0, source)]
160+
visited = set()
161+
162+
while pq:
163+
current_dist, u = heapq.heappop(pq)
164+
if u in visited:
165+
continue
166+
visited.add(u)
167+
168+
for v, w in reweighted_graph[u].items():
169+
if d[v] > current_dist + w:
170+
d[v] = current_dist + w
171+
heapq.heappush(pq, (d[v], v))
172+
173+
# 转换回原始权重
174+
for v in range(n):
175+
if d[v] != float('inf'):
176+
dist[source][v] = d[v] - h[source] + h[v]
177+
178+
return dist
179+
```
180+
181+
代码解释:
182+
183+
1. `graph` 是一个字典,表示图的邻接表。
184+
2. `n` 是图中顶点的数量。
185+
3. 首先添加一个新的顶点 $s$,并添加从 $s$ 到所有其他顶点的边,权重为 0。
186+
4. 使用 Bellman-Ford 算法计算从 $s$ 到所有顶点的最短路径 $h(v)$。
187+
5. 检查是否存在负权环,如果存在则返回 None。
188+
6. 重新赋权,将图中的负权边转换为非负权边。
189+
7. 对每个顶点运行一次 Dijkstra 算法,计算最短路径。
190+
8. 将结果转换回原始权重,得到最终的距离矩阵。
191+
192+
### 3.4 Johnson 算法复杂度分析
193+
194+
- **时间复杂度**:$O(VE \log V)$
195+
- 需要运行一次 Bellman-Ford 算法,时间复杂度为 $O(VE)$
196+
- 需要运行 $V$ 次 Dijkstra 算法,每次时间复杂度为 $O(E \log V)$
197+
- 因此总时间复杂度为 $O(VE \log V)$
198+
199+
- **空间复杂度**:$O(V^2)$
200+
- 需要存储距离矩阵,大小为 $O(V^2)$
201+
- 需要存储重新赋权后的图,大小为 $O(E)$
202+
- 因此总空间复杂度为 $O(V^2)$

0 commit comments

Comments
 (0)