Skip to content

Commit 0cc0e17

Browse files
committed
更新「单源最短路径知识(二)」相关内容
1 parent 02cf520 commit 0cc0e17

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
## 1. Bellman-Ford 算法
2+
3+
### 1.1 Bellman-Ford 算法的算法思想
4+
5+
> **Bellman-Ford 算法**:一种用于计算单源最短路径的算法,可以处理图中存在负权边的情况,并且能够检测负权环。
6+
7+
Bellman-Ford 算法的核心思想是:
8+
1. 对图中的所有边进行 $V-1$ 次松弛操作,其中 $V$ 是图中顶点的数量
9+
2. 每次松弛操作都会尝试通过当前边来缩短源点到目标顶点的距离
10+
3. 如果在 $V-1$ 次松弛后还能继续松弛,说明图中存在负权环
11+
4. 算法可以处理负权边,但不能处理负权环
12+
13+
### 1.2 Bellman-Ford 算法的实现步骤
14+
15+
1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大
16+
2. 进行 $V-1$ 次迭代,每次迭代:
17+
- 遍历图中的所有边
18+
- 对每条边进行松弛操作:如果通过当前边可以缩短源点到目标顶点的距离,则更新距离
19+
3. 进行第 $V$ 次迭代,检查是否还能继续松弛:
20+
- 如果还能松弛,说明图中存在负权环
21+
- 如果不能松弛,说明已经找到最短路径
22+
4. 返回最短路径距离数组
23+
24+
### 1.3 Bellman-Ford 算法的实现代码
25+
26+
```python
27+
class Solution:
28+
def bellmanFord(self, graph, n, source):
29+
# 初始化距离数组
30+
dist = [float('inf') for _ in range(n + 1)]
31+
dist[source] = 0
32+
33+
# 进行 V-1 次迭代
34+
for i in range(n - 1):
35+
# 遍历所有边
36+
for vi in graph:
37+
for vj in graph[vi]:
38+
# 松弛操作
39+
if dist[vj] > graph[vi][vj] + dist[vi]:
40+
dist[vj] = graph[vi][vj] + dist[vi]
41+
42+
# 检查是否存在负权环
43+
for vi in graph:
44+
for vj in graph[vi]:
45+
if dist[vj] > dist[vi] + graph[vi][vj]:
46+
return None # 存在负权环
47+
48+
return dist
49+
```
50+
51+
代码解释:
52+
53+
1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。
54+
2. `n` 是图中顶点的数量。
55+
3. `source` 是源节点的编号。
56+
4. `dist` 数组存储源点到各个节点的最短距离。
57+
5. 外层循环进行 $V-1$ 次迭代,确保所有可能的最短路径都被找到。
58+
6. 内层循环遍历所有边,进行松弛操作。
59+
7. 最后检查是否存在负权环,如果存在则返回 None。
60+
61+
### 1.4 Bellman-Ford 算法复杂度分析
62+
63+
- **时间复杂度**:$O(VE)$
64+
- 需要进行 $V-1$ 次迭代
65+
- 每次迭代需要遍历所有边 $E$
66+
- 因此总时间复杂度为 $O(VE)$
67+
68+
- **空间复杂度**:$O(V)$
69+
- 需要存储距离数组,大小为 $O(V)$
70+
- 不需要额外的空间来存储图的结构,因为使用邻接表表示
71+
72+
73+
## 2. SPFA 算法
74+
75+
### 2.1 SPFA 算法的算法思想
76+
77+
> **SPFA 算法(Shortest Path Faster Algorithm)**:是 Bellman-Ford 算法的一种队列优化版本,通过使用队列来维护待更新的节点,从而减少不必要的松弛操作。
78+
79+
SPFA 算法的核心思想是:
80+
1. 使用队列来维护待更新的节点,而不是像 Bellman-Ford 算法那样遍历所有边
81+
2. 只有当某个节点的距离被更新时,才将其加入队列
82+
3. 通过这种方式,避免了大量不必要的松弛操作,提高了算法的效率
83+
4. 算法可以处理负权边,并且能够检测负权环
84+
85+
### 2.2 SPFA 算法的实现步骤
86+
87+
1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大
88+
2. 创建一个队列,将源节点加入队列
89+
3. 当队列不为空时:
90+
- 取出队首节点
91+
- 遍历该节点的所有相邻节点
92+
- 如果通过当前节点可以缩短到相邻节点的距离,则更新距离
93+
- 如果相邻节点不在队列中,则将其加入队列
94+
4. 重复步骤 3,直到队列为空
95+
5. 返回最短路径距离数组
96+
97+
### 2.3 SPFA 算法的实现代码
98+
99+
```python
100+
from collections import deque
101+
102+
def spfa(graph, n, source):
103+
# 初始化距离数组
104+
dist = [float('inf') for _ in range(n + 1)]
105+
dist[source] = 0
106+
107+
# 初始化队列和访问数组
108+
queue = deque([source])
109+
in_queue = [False] * (n + 1)
110+
in_queue[source] = True
111+
112+
# 记录每个节点入队次数,用于检测负环
113+
count = [0] * (n + 1)
114+
115+
while queue:
116+
# 取出队首节点
117+
current = queue.popleft()
118+
in_queue[current] = False
119+
120+
# 遍历当前节点的所有相邻节点
121+
for neighbor, weight in graph[current].items():
122+
# 如果通过当前节点可以缩短距离
123+
if dist[neighbor] > dist[current] + weight:
124+
dist[neighbor] = dist[current] + weight
125+
count[neighbor] += 1
126+
127+
# 如果节点入队次数超过 n-1 次,说明存在负环
128+
if count[neighbor] >= n:
129+
return None
130+
131+
# 如果相邻节点不在队列中,将其加入队列
132+
if not in_queue[neighbor]:
133+
queue.append(neighbor)
134+
in_queue[neighbor] = True
135+
136+
return dist
137+
```
138+
139+
代码解释:
140+
141+
1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。
142+
2. `n` 是图中顶点的数量。
143+
3. `source` 是源节点的编号。
144+
4. `dist` 数组存储源点到各个节点的最短距离。
145+
5. `queue` 是一个双端队列,用于维护待更新的节点。
146+
6. `in_queue` 数组用于记录节点是否在队列中,避免重复入队。
147+
7. `count` 数组用于记录每个节点的入队次数,用于检测负环。
148+
8. 主循环中,每次从队列中取出一个节点,遍历其所有相邻节点,如果发现更短的路径则更新距离并将相邻节点加入队列。
149+
9. 如果某个节点的入队次数超过 $n-1$ 次,说明图中存在负环,返回 None。
150+
151+
### 2.4 SPFA 算法复杂度分析
152+
153+
- **时间复杂度**
154+
- 平均情况下:$O(kE)$,其中 $k$ 是每个节点的平均入队次数
155+
- 最坏情况下:$O(VE)$,与 Bellman-Ford 算法相同
156+
- 实际运行中,SPFA 算法通常比 Bellman-Ford 算法快很多
157+
158+
- **空间复杂度**:$O(V)$
159+
- 需要存储距离数组,大小为 $O(V)$
160+
- 需要存储队列和访问数组,大小为 $O(V)$
161+
- 因此总空间复杂度为 $O(V)$

0 commit comments

Comments
 (0)