Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2a4e239

Browse files
committed
修复和优化语句表述
1 parent e1552a9 commit 2a4e239

File tree

1 file changed

+108
-113
lines changed

1 file changed

+108
-113
lines changed

‎docs/06_graph/06_09_graph_multi_source_shortest_path.md‎

Lines changed: 108 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,119 @@
11
## 1. 多源最短路径简介
22

3-
> **多源最短路径(All-Pairs Shortest Paths)**:对于一个带权图 $G = (V, E)$,计算图中任意两个顶点之间的最短路径长度
3+
> **多源最短路径(All-Pairs Shortest Paths)**:指的是在一个带权图 $G = (V, E)$ 中,计算任意两个顶点之间的最短路径长度
44
5-
多源最短路径问题的核心是找到图中任意两个顶点之间的最短路径。这个问题在许多实际应用中都非常重要,比如:
5+
多源最短路径问题的本质,就是要找出图中每一对顶点之间的最短路径。这类问题在实际生活和工程中非常常见,例如:
66

7-
1. 网络路由中的路由表计算
8-
2. 地图导航系统中的距离矩阵计算
9-
3. 社交网络中的最短关系链分析
10-
4. 交通网络中的最优路径规划
7+
1. 网络通信中,生成路由表以确定任意两点之间的最优传输路径;
8+
2. 地图导航系统中,计算所有地点之间的距离矩阵;
9+
3. 社交网络分析中,寻找两个人之间的最短关系链;
10+
4. 交通网络中,规划任意两地之间的最优行车路线。
1111

12-
常见的解决多源最短路径问题的算法包括:
12+
常用的多源最短路径算法有:
1313

14-
1. **Floyd-Warshall 算法**:一种动态规划算法,可以处理负权边,但不能处理负权环
15-
2. **Johnson 算法**:结合了 Bellman-Ford 算法和 Dijkstra 算法,可以处理负权边,但不能处理负权环
16-
3. **重复 Dijkstra 算法**:对每个顶点运行一次 Dijkstra 算法,适用于无负权边的图
14+
1. **Floyd-Warshall 算法**:一种基于动态规划的方法,能处理负权边,但无法处理负权环
15+
2. **Johnson 算法**:结合了 Bellman-Ford Dijkstra 算法,既能处理负权边,也能高效应对稀疏图,但同样不能处理负权环
16+
3. **多次 Dijkstra 算法**:对每个顶点分别运行一次 Dijkstra 算法,适用于没有负权边的图
1717

1818
## 2. Floyd-Warshall 算法
1919

20-
### 2.1 Floyd-Warshall 算法的算法思想
20+
### 2.1 Floyd-Warshall 算法的核心思想
2121

22-
> **Floyd-Warshall 算法**:一种动态规划算法,通过逐步考虑中间顶点来更新任意两点之间的最短路径
22+
> **Floyd-Warshall 算法**:这是一种经典的动态规划算法,通过不断尝试引入不同的中间节点,来优化任意两点之间的最短路径
2323
24-
Floyd-Warshall 算法的核心思想是:
24+
通俗来说,Floyd-Warshall 算法的核心思想如下:
2525

26-
1. 对于图中的任意两个顶点 $i$ $j$,考虑是否存在一个顶点 $k,ドル使得从 $i$ 到 $k$ 再到 $j$ 的路径比已知的从 $i$ 到 $j$ 的路径更短
27-
2. 如果存在这样的顶点 $k,ドル则更新从 $i$ 到 $j$ 的最短路径
28-
3. 通过考虑所有可能的中间顶点 $k,ドル最终得到任意两点之间的最短路径
26+
1. 假设要找从顶点 $i$ 到顶点 $j$ 的最短路径,试着经过某个中间顶点 $k,ドル看看能不能让路径更短。
27+
2. 如果发现「先从 $i$ 到 $k,ドル再从 $k$ 到 $j$」的路径比原来「直接从 $i$ 到 $j$」的路径更短,就用这个更短的路径来更新答案。
28+
3. 依次尝试所有顶点作为中间点 $k,ドル每次都用上述方法去优化所有点对之间的最短路径,最终就能得到全局最优解。
2929

3030
### 2.2 Floyd-Warshall 算法的实现步骤
3131

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. 返回最终的距离矩阵
32+
1. 先初始化一个距离矩阵 $dist,ドル$dist[i][j]$ 表示从顶点 $i$ 到顶点 $j$ 的当前最短路径长度。
33+
2. 如果 $i$ 和 $j$ 之间有直接的边,就把 $dist[i][j]$ 设为这条边的权重;如果没有,设为无穷大(表示不可达)。
34+
3. 然后,依次枚举每个顶点 $k$ 作为「中转站」:
35+
- 对于所有顶点对 $(i, j),ドル如果「从 $i$ 经过 $k$ 到 $j$」的路径更短(即 $dist[i][k] + dist[k][j] < dist[i][j]$),就用更短的路径更新 $dist[i][j]$
36+
4. 重复第 3 步,直到所有顶点都被作为中间点尝试过。
37+
5. 最终,$dist$ 矩阵中每个 $dist[i][j]$ 就是从 $i$ 到 $j$ 的最短路径长度。
3838

3939
### 2.3 Floyd-Warshall 算法的实现代码
4040

4141
```python
4242
def floyd_warshall(graph, n):
43-
# 初始化距离矩阵
44-
dist = [[float('inf') for _ in range(n)] for _ in range(n)]
43+
"""
44+
Floyd-Warshall 算法,计算所有点对之间的最短路径。
45+
:param graph: 邻接表,graph[i] = {j: weight, ...},节点编号为 0~n-1
46+
:param n: 节点总数
47+
:return: dist 矩阵,dist[i][j] 表示 i 到 j 的最短路径长度
48+
"""
49+
# 初始化距离矩阵,所有点对距离设为无穷大
50+
dist = [[float('inf')] * n for _ in range(n)]
4551

46-
# 设置直接相连的顶点之间的距离
52+
# 距离矩阵对角线设为 0,表示自己到自己的距离为 0
4753
for i in range(n):
4854
dist[i][i] = 0
49-
for j, weight in graph[i].items():
55+
# 设置直接相连的顶点之间的距离
56+
for j, weight in graph.get(i, {}).items():
5057
dist[i][j] = weight
51-
52-
# 考虑每个顶点作为中间顶点
58+
59+
# 三重循环,枚举每个中间点 k
5360
for k in range(n):
5461
for i in range(n):
62+
# 跳过不可达的起点
63+
if dist[i][k] == float('inf'):
64+
continue
5565
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])
66+
# 跳过不可达的终点
67+
if dist[k][j] == float('inf'):
68+
continue
69+
# 如果经过 k 能让 i 到 j 更短,则更新
70+
if dist[i][j] > dist[i][k] + dist[k][j]:
71+
dist[i][j] = dist[i][k] + dist[k][j]
5872

5973
return dist
6074
```
6175

62-
代码解释:
76+
### 2.4 Floyd-Warshall 算法分析
6377

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$ 的最短路径长度。
78+
- **时间复杂度**:$O(V^3)$
79+
- 算法包含三重嵌套循环,分别枚举所有中间点、起点和终点,因此总时间复杂度为 $O(V^3)$。
7180

72-
### 2.4 Floyd-Warshall 算法复杂度分析
81+
- **空间复杂度**:$O(V^2)$
82+
- 主要空间消耗在距离矩阵 $dist,ドル需要 $O(V^2)$ 的空间。
83+
- 由于采用邻接表存储原图结构,无需额外空间存储图的边。
7384

74-
- **时间复杂度**:$O(V^3)$
75-
- 需要三层嵌套循环,分别遍历所有顶点
76-
- 因此总时间复杂度为 $O(V^3)$
77-
78-
- **空间复杂度**:$O(V^2)$
79-
- 需要存储距离矩阵,大小为 $O(V^2)$
80-
- 不需要额外的空间来存储图的结构,因为使用邻接表表示
85+
**Floyd-Warshall 算法优点**:
8186

82-
Floyd-Warshall 算法的主要优势在于:
87+
1. 实现简洁,易于理解和编码。
88+
2. 能处理负权边(但不能有负权环)。
89+
3. 可用于检测负权环(若某个顶点 $i$ 满足 $dist[i][i] < 0,ドル则存在负权环)。
90+
4. 特别适合稠密图(边数接近 $V^2$)。
8391

84-
1. 实现简单,容易理解
85-
2. 可以处理负权边
86-
3. 可以检测负权环(如果某个顶点到自身的距离变为负数,说明存在负权环)
87-
4. 适用于稠密图
92+
**Floyd-Warshall 算法缺点**:
8893

89-
主要缺点:
90-
91-
1. 时间复杂度较高,不适用于大规模图
92-
2. 空间复杂度较高,需要存储完整的距离矩阵
93-
3. 不能处理负权环
94+
1. 时间复杂度较高,不适合节点数很大的图。
95+
2. 空间复杂度较高,需要维护完整的 $V \times V$ 距离矩阵。
96+
3. 无法处理存在负权环的情况(若有负权环,最短路无意义)。
9497

9598
## 3. Johnson 算法
9699

97-
### 3.1 Johnson 算法的算法思想
100+
### 3.1 Johnson 算法的核心思想
98101

99-
> **Johnson 算法**:一种结合了 Bellman-Ford 算法和 Dijkstra 算法的多源最短路径算法,可以处理负权边,但不能处理负权环
102+
> **Johnson 算法**:是一种结合 Bellman-Ford Dijkstra 算法的多源最短路径算法,能够处理负权边,但无法处理负权环
100103
101-
Johnson 算法的核心思想是:
104+
Johnson 算法的核心思想如下:
102105

103-
1. 通过重新赋权,将图中的负权边转换为非负权边
104-
2. 对每个顶点运行一次 Dijkstra 算法,计算最短路径
105-
3. 将结果转换回原始权重
106+
1. 通过对图进行重新赋权,将所有边权变为非负,从而使 Dijkstra 算法适用;
107+
2. 对每个顶点分别运行一次 Dijkstra 算法,计算其到其他所有顶点的最短路径;
108+
3. 最后将结果还原为原图的最短路径权值。
106109

107110
### 3.2 Johnson 算法的实现步骤
108111

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)$
112+
1. 向原图添加一个新顶点 $s,ドル并从 $s$ 向所有其他顶点连一条权重为 0 的边;
113+
2. 使用 Bellman-Ford 算法以 $s$ 为源点,计算 $s$ 到每个顶点 $v$ 的最短距离 $h(v)$;
114+
3. 对于原图中的每条边 $(u, v),ドル将其权重调整为 $w'(u, v) = w(u, v) + h(u) - h(v)$,使所有边权非负;
115+
4. 对每个顶点 $u,ドル以 $u$ 为源点在重新赋权后的图上运行 Dijkstra 算法,得到 $u$ 到所有顶点的最短距离 $d'(u, v)$;
116+
5. 最终结果还原为原图权重:$d(u, v) = d'(u, v) - h(u) + h(v)$,即为原图中 $u$ 到 $v$ 的最短路径长度。
114117

115118
### 3.3 Johnson 算法的实现代码
116119

@@ -119,76 +122,69 @@ from collections import defaultdict
119122
import heapq
120123

121124
def johnson(graph, n):
122-
# 添加新顶点 s
125+
"""
126+
Johnson 算法:多源最短路径,支持负权边但不支持负权环。
127+
:param graph: 邻接表,graph[u] = {v: w, ...},节点编号 0~n-1
128+
:param n: 节点总数
129+
:return: dist 矩阵,dist[i][j] 表示 i 到 j 的最短路径长度;若有负权环返回 None
130+
"""
131+
# 1. 构建新图,添加超级源点 s(编号为 n),从 s 向所有顶点连权重为 0 的边
123132
new_graph = defaultdict(dict)
124133
for u in graph:
125134
for v, w in graph[u].items():
126135
new_graph[u][v] = w
127-
new_graph[n][u] = 0 # 从 s 到所有顶点的边权重为 0
128-
129-
# 使用 Bellman-Ford 算法计算 h(v)
136+
for u in range(n):
137+
new_graph[n][u] = 0 # s -> u,权重为 0
138+
139+
# 2. Bellman-Ford 算法,计算超级源点 s 到每个顶点的最短距离 h(v)
130140
h = [float('inf')] * (n + 1)
131-
h[n] = 0
132-
141+
h[n] = 0# s 到自身距离为 0
142+
# 最多 n 轮松弛
133143
for _ in range(n):
144+
updated = False
134145
for u in new_graph:
135146
for v, w in new_graph[u].items():
136-
if h[v] > h[u] + w:
147+
if h[u] !=float('inf') and h[v] > h[u] + w:
137148
h[v] = h[u] + w
138-
139-
# 检查是否存在负权环
149+
updated = True
150+
if not updated:
151+
break
152+
153+
# 检查负权环:如果还能松弛,说明有负环
140154
for u in new_graph:
141155
for v, w in new_graph[u].items():
142-
if h[v] > h[u] + w:
156+
if h[u] !=float('inf') and h[v] > h[u] + w:
143157
return None # 存在负权环
144-
145-
# 重新赋权
158+
159+
# 3. 重新赋权:w'(u,v) = w(u,v) + h[u] - h[v],保证所有边权非负
146160
reweighted_graph = defaultdict(dict)
147161
for u in graph:
148162
for v, w in graph[u].items():
149163
reweighted_graph[u][v] = w + h[u] - h[v]
150-
151-
# 对每个顶点运行 Dijkstra 算法
152-
dist = [[float('inf')for _ inrange(n)] for _ in range(n)]
164+
165+
# 4. 对每个顶点运行 Dijkstra 算法,计算最短路径
166+
dist = [[float('inf')] * n for _ in range(n)]
153167
for source in range(n):
154-
# 初始化距离数组
155168
d = [float('inf')] * n
156169
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:
170+
heap = [(0, source)]
171+
visited = [False] * n
172+
while heap:
173+
cur_dist, u = heapq.heappop(heap)
174+
if visited[u]:
165175
continue
166-
visited.add(u)
167-
176+
visited[u] = True
168177
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-
# 转换回原始权重
178+
if d[v] > cur_dist + w:
179+
d[v] = cur_dist + w
180+
heapq.heappush(heap, (d[v], v))
181+
# 5. 还原原图权重
174182
for v in range(n):
175183
if d[v] != float('inf'):
176184
dist[source][v] = d[v] - h[source] + h[v]
177-
178185
return dist
179186
```
180187

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-
192188
### 3.4 Johnson 算法复杂度分析
193189

194190
- **时间复杂度**:$O(VE \log V)$
@@ -197,9 +193,8 @@ def johnson(graph, n):
197193
- 因此总时间复杂度为 $O(VE \log V)$
198194

199195
- **空间复杂度**:$O(V^2)$
200-
- 需要存储距离矩阵,大小为 $O(V^2)$
201-
- 需要存储重新赋权后的图,大小为 $O(E)$
202-
- 因此总空间复杂度为 $O(V^2)$
196+
- 主要空间消耗在距离矩阵($O(V^2)$)以及重新赋权后的图($O(E)$)。
197+
- 因此总体空间复杂度为 $O(V^2)$。
203198

204199
## 练习题目
205200

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /