|
| 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