|
1 |
| -## 1. 单源最短路径的定义 |
| 1 | +## 1. 单源最短路径简介 |
2 | 2 |
|
3 | 3 | > **单源最短路径(Single Source Shortest Path)**:对于一个带权图 $G = (V, E)$,其中每条边的权重是一个实数。另外,给定 $v$ 中的一个顶点,称之为源点。则源点到其他所有各个顶点之间的最短路径长度,称为单源最短路径。
|
4 | 4 |
|
|
20 | 20 |
|
21 | 21 | > **Dijkstra 算法的算法思想**:通过逐步选择距离起始节点最近的节点,并根据这些节点的路径更新其他节点的距离,从而逐步找到最短路径。
|
22 | 22 |
|
| 23 | +Dijkstra 算法是一种用来解决单源最短路径问题的算法。这个算法适用于没有负权边的图。算法的核心思想是从源点出发,逐步找到到其他所有点的最短路径。它通过不断选择当前距离源点最近的节点,并更新与该节点相邻的节点的距离,最终得到所有节点的最短路径。 |
| 24 | + |
| 25 | +Dijkstra 算法使用贪心的策略。它每次选择当前未处理的节点中距离源点最近的节点,认为这个节点的最短路径已经确定。然后,它用这个节点的最短路径去更新其他相邻节点的距离。这个过程重复进行,直到所有节点的最短路径都被确定。 |
| 26 | + |
| 27 | +Dijkstra 算法的一个重要特点是它不能处理有负权边的图。因为负权边可能导致已经确定的最短路径被破坏。如果图中存在负权边,应该使用 Bellman-Ford 算法或 SPFA 算法。 |
| 28 | + |
23 | 29 | ### 2.2 Dijkstra 算法的实现步骤
|
24 | 30 |
|
| 31 | +1. 初始化距离数组,将源节点 $source$ 的距离设为 $0$,其他节点的距离设为无穷大。 |
| 32 | +2. 维护一个访问数组 $visited$,记录节点是否已经被访问。 |
| 33 | +3. 每次从未访问的节点中找到距离最小的节点,标记为已访问。 |
| 34 | +4. 更新该节点的所有相邻节点的距离。 |
| 35 | +5. 重复步骤 $3 \sim 4$,直到所有节点都被访问。 |
| 36 | +6. 最后返回所有节点中最大的距离值,如果存在无法到达的节点则返回 $-1$。 |
| 37 | + |
| 38 | + |
| 39 | + |
25 | 40 | ### 2.3 Dijkstra 算法的实现代码
|
26 | 41 |
|
27 | 42 | ```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]}") |
29 | 93 | ```
|
30 | 94 |
|
31 |
| -## 3. Bellman-Ford 算法 |
| 95 | +### 2.4 Dijkstra 算法复杂度分析 |
32 | 96 |
|
33 |
| -### 3.1 Bellman-Ford 算法的算法思想 |
| 97 | +- **时间复杂度**:$O(V^2)$ |
| 98 | + - 外层循环需要遍历所有节点,时间复杂度为 $O(V)$ |
| 99 | + - 内层循环需要遍历所有未访问的节点来找到距离最小的节点,时间复杂度为 $O(V)$ |
| 100 | + - 因此总时间复杂度为 $O(V^2)$ |
34 | 101 |
|
35 |
| -### 3.2 Bellman-Ford 算法的实现步骤 |
| 102 | +- **空间复杂度**:$O(V)$ |
| 103 | + - 需要存储距离数组 `dist`,大小为 $O(V)$ |
| 104 | + - 需要存储访问集合 `visited`,大小为 $O(V)$ |
| 105 | + - 因此总空间复杂度为 $O(V)$ |
36 | 106 |
|
37 |
| -### 3.3 Bellman-Ford 算法的实现代码 |
38 | 107 |
|
39 |
| -```python |
| 108 | +## 3. 堆优化的 Dijkstra 算法 |
40 | 109 |
|
41 |
| -``` |
| 110 | +### 3.1 堆优化的 Dijkstra 算法思想 |
| 111 | + |
| 112 | +> **堆优化的 Dijkstra 算法**:通过使用优先队列(堆)来优化选择最小距离节点的过程,从而降低算法的时间复杂度。 |
42 | 113 |
|
43 |
| -## 4. SPFA 算法 |
| 114 | +在原始的 Dijkstra 算法中,每次都需要遍历所有未访问的节点来找到距离最小的节点,这个过程的时间复杂度是 $O(V)$。通过使用优先队列(堆)来维护当前已知的最短距离,我们可以将这个过程的时间复杂度优化到 $O(\log V)$。 |
44 | 115 |
|
45 |
| -### 4.1 SPFA 算法的算法思想 |
| 116 | +堆优化的主要思想是: |
| 117 | +1. 使用优先队列存储当前已知的最短距离 |
| 118 | +2. 每次从队列中取出距离最小的节点进行处理 |
| 119 | +3. 当发现更短的路径时,将新的距离加入队列 |
| 120 | +4. 通过优先队列的特性,保证每次取出的都是当前最小的距离 |
46 | 121 |
|
47 |
| -### 4.2 SPFA 算法的实现步骤 |
| 122 | +### 3.2 堆优化的 Dijkstra 算法实现步骤 |
48 | 123 |
|
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 算法实现代码 |
50 | 135 |
|
51 | 136 | ```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]}") |
52 | 182 | ```
|
53 | 183 |
|
| 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