Skip to content

Commit 02cf520

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

File tree

1 file changed

+163
-12
lines changed

1 file changed

+163
-12
lines changed

Contents/08.Graph/04.Graph-Shortest-Path/01.Graph-Single-Source-Shortest-Path-01.md

Lines changed: 163 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## 1. 单源最短路径的定义
1+
## 1. 单源最短路径简介
22

33
> **单源最短路径(Single Source Shortest Path)**:对于一个带权图 $G = (V, E)$,其中每条边的权重是一个实数。另外,给定 $v$ 中的一个顶点,称之为源点。则源点到其他所有各个顶点之间的最短路径长度,称为单源最短路径。
44
@@ -20,34 +20,185 @@
2020

2121
> **Dijkstra 算法的算法思想**:通过逐步选择距离起始节点最近的节点,并根据这些节点的路径更新其他节点的距离,从而逐步找到最短路径。
2222
23+
Dijkstra 算法是一种用来解决单源最短路径问题的算法。这个算法适用于没有负权边的图。算法的核心思想是从源点出发,逐步找到到其他所有点的最短路径。它通过不断选择当前距离源点最近的节点,并更新与该节点相邻的节点的距离,最终得到所有节点的最短路径。
24+
25+
Dijkstra 算法使用贪心的策略。它每次选择当前未处理的节点中距离源点最近的节点,认为这个节点的最短路径已经确定。然后,它用这个节点的最短路径去更新其他相邻节点的距离。这个过程重复进行,直到所有节点的最短路径都被确定。
26+
27+
Dijkstra 算法的一个重要特点是它不能处理有负权边的图。因为负权边可能导致已经确定的最短路径被破坏。如果图中存在负权边,应该使用 Bellman-Ford 算法或 SPFA 算法。
28+
2329
### 2.2 Dijkstra 算法的实现步骤
2430

31+
1. 初始化距离数组,将源节点 $source$ 的距离设为 $0$,其他节点的距离设为无穷大。
32+
2. 维护一个访问数组 $visited$,记录节点是否已经被访问。
33+
3. 每次从未访问的节点中找到距离最小的节点,标记为已访问。
34+
4. 更新该节点的所有相邻节点的距离。
35+
5. 重复步骤 $3 \sim 4$,直到所有节点都被访问。
36+
6. 最后返回所有节点中最大的距离值,如果存在无法到达的节点则返回 $-1$。
37+
38+
39+
2540
### 2.3 Dijkstra 算法的实现代码
2641

2742
```python
28-
43+
class Solution:
44+
def dijkstra(self, graph, n, source):
45+
# 初始化距离数组
46+
dist = [float('inf') for _ in range(n + 1)]
47+
dist[source] = 0
48+
# 记录已处理的节点
49+
visited = set()
50+
51+
while len(visited) < n:
52+
# 选择当前未处理的、距离源点最近的节点
53+
current_node = None
54+
min_distance = float('inf')
55+
for i in range(1, n + 1):
56+
if i not in visited and dist[i] < min_distance:
57+
min_distance = dist[i]
58+
current_node = i
59+
60+
# 如果没有可处理的节点(非连通图),提前结束
61+
if current_node is None:
62+
break
63+
64+
# 标记当前节点为已处理
65+
visited.add(current_node)
66+
67+
# 更新相邻节点的距离
68+
for neighbor, weight in graph[current_node].items():
69+
new_distance = dist[current_node] + weight
70+
if new_distance < dist[neighbor]:
71+
dist[neighbor] = new_distance
72+
73+
return dist
74+
75+
# 使用示例
76+
# 创建一个有向图,使用邻接表表示
77+
graph = {
78+
1: {2: 2, 3: 4},
79+
2: {3: 1, 4: 7},
80+
3: {4: 3},
81+
4: {}
82+
}
83+
n = 4 # 图中节点数量
84+
source = 1 # 源节点
85+
86+
dist = Solution().dijkstra(graph, n, source)
87+
print("从节点", source, "到其他节点的最短距离:")
88+
for i in range(1, n + 1):
89+
if dist[i] == float('inf'):
90+
print(f"到节点 {i} 的距离:不可达")
91+
else:
92+
print(f"到节点 {i} 的距离:{dist[i]}")
2993
```
3094

31-
## 3. Bellman-Ford 算法
95+
### 2.4 Dijkstra 算法复杂度分析
3296

33-
### 3.1 Bellman-Ford 算法的算法思想
97+
- **时间复杂度**:$O(V^2)$
98+
- 外层循环需要遍历所有节点,时间复杂度为 $O(V)$
99+
- 内层循环需要遍历所有未访问的节点来找到距离最小的节点,时间复杂度为 $O(V)$
100+
- 因此总时间复杂度为 $O(V^2)$
34101

35-
### 3.2 Bellman-Ford 算法的实现步骤
102+
- **空间复杂度**:$O(V)$
103+
- 需要存储距离数组 `dist`,大小为 $O(V)$
104+
- 需要存储访问集合 `visited`,大小为 $O(V)$
105+
- 因此总空间复杂度为 $O(V)$
36106

37-
### 3.3 Bellman-Ford 算法的实现代码
38107

39-
```python
108+
## 3. 堆优化的 Dijkstra 算法
40109

41-
```
110+
### 3.1 堆优化的 Dijkstra 算法思想
111+
112+
> **堆优化的 Dijkstra 算法**:通过使用优先队列(堆)来优化选择最小距离节点的过程,从而降低算法的时间复杂度。
42113
43-
## 4. SPFA 算法
114+
在原始的 Dijkstra 算法中,每次都需要遍历所有未访问的节点来找到距离最小的节点,这个过程的时间复杂度是 $O(V)$。通过使用优先队列(堆)来维护当前已知的最短距离,我们可以将这个过程的时间复杂度优化到 $O(\log V)$。
44115

45-
### 4.1 SPFA 算法的算法思想
116+
堆优化的主要思想是:
117+
1. 使用优先队列存储当前已知的最短距离
118+
2. 每次从队列中取出距离最小的节点进行处理
119+
3. 当发现更短的路径时,将新的距离加入队列
120+
4. 通过优先队列的特性,保证每次取出的都是当前最小的距离
46121

47-
### 4.2 SPFA 算法的实现步骤
122+
### 3.2 堆优化的 Dijkstra 算法实现步骤
48123

49-
### 4.3 SPFA 算法的实现代码
124+
1. 初始化距离数组,将源节点的距离设为 $0$,其他节点的距离设为无穷大。
125+
2. 创建一个优先队列,将源节点及其距离 $(0, source)$ 加入队列。
126+
3. 当队列不为空时:
127+
- 取出队列中距离最小的节点
128+
- 如果该节点的距离大于已知的最短距离,则跳过
129+
- 否则,遍历该节点的所有相邻节点
130+
- 如果通过当前节点到达相邻节点的距离更短,则更新距离并将新的距离加入队列
131+
4. 重复步骤 3,直到队列为空
132+
5. 返回所有节点的最短距离
133+
134+
### 3.3 堆优化的 Dijkstra 算法实现代码
50135

51136
```python
137+
import heapq
138+
139+
class Solution:
140+
def dijkstra(self, graph, n, source):
141+
# 初始化距离数组
142+
dist = [float('inf') for _ in range(n + 1)]
143+
dist[source] = 0
144+
145+
# 创建优先队列,存储 (距离, 节点) 的元组
146+
priority_queue = [(0, source)]
147+
148+
while priority_queue:
149+
current_distance, current_node = heapq.heappop(priority_queue)
150+
151+
# 如果当前距离大于已知的最短距离,跳过
152+
if current_distance > dist[current_node]:
153+
continue
154+
155+
# 遍历当前节点的所有相邻节点
156+
for neighbor, weight in graph[current_node].items():
157+
distance = current_distance + weight
158+
if distance < dist[neighbor]:
159+
dist[neighbor] = distance
160+
heapq.heappush(priority_queue, (distance, neighbor))
161+
162+
return dist
163+
164+
# 使用示例
165+
# 创建一个有向图,使用邻接表表示
166+
graph = {
167+
1: {2: 2, 3: 4},
168+
2: {3: 1, 4: 7},
169+
3: {4: 3},
170+
4: {}
171+
}
172+
n = 4 # 图中节点数量
173+
source = 1 # 源节点
174+
175+
dist = Solution().dijkstra(graph, n, source)
176+
print("从节点", source, "到其他节点的最短距离:")
177+
for i in range(1, n + 1):
178+
if dist[i] == float('inf'):
179+
print(f"到节点 {i} 的距离:不可达")
180+
else:
181+
print(f"到节点 {i} 的距离:{dist[i]}")
52182
```
53183

184+
代码解释:
185+
186+
1. `graph` 是一个字典,表示图的邻接表。例如,`graph[1] = {2: 3, 3: 4}` 表示从节点 1 到节点 2 的边权重为 3,到节点 3 的边权重为 4。
187+
2. `n` 是图中顶点的数量。
188+
3. `source` 是源节点的编号。
189+
4. `dist` 数组存储源点到各个节点的最短距离。
190+
5. `priority_queue` 是一个优先队列,用来选择当前距离源点最近的节点。队列中的元素是 (距离, 节点) 的元组。
191+
6. 主循环中,每次从队列中取出距离最小的节点。如果该节点的距离已经被更新过,跳过。
192+
7. 对于当前节点的每一个邻居,计算通过当前节点到达邻居的距离。如果这个距离比已知的更短,更新距离并将邻居加入队列。
193+
8. 最终,`dist` 数组中存储的就是源点到所有节点的最短距离。
194+
195+
### 3.4 堆优化的 Dijkstra 算法复杂度分析
196+
197+
- **时间复杂度**:$O((V + E) \log V)$
198+
- 每个节点最多被加入优先队列一次,每次操作的时间复杂度为 $O(\log V)$
199+
- 每条边最多被处理一次,每次处理的时间复杂度为 $O(\log V)$
200+
- 因此总时间复杂度为 $O((V + E) \log V)$
201+
202+
- **空间复杂度**:$O(V)$
203+
- 需要存储距离数组,大小为 $O(V)$。
204+
- 优先队列在最坏情况下可能存储所有节点,大小为 $O(V)$。

0 commit comments

Comments
 (0)