最短路径算法总结
最短路径算法总结
以下是常见的最短路径算法及其时间复杂度和核心思路的整理:
1. Dijkstra(朴素版)
时间复杂度: O ( n 2 ) O(n^2) O(n2)
适用场景:稠密图(边数 m m m 接近 n 2 n^2 n2),且无负权边。
算法步骤:
-
初始化距离数组
dist[]
,dist[1] = 0
,其余为+∞
。 -
循环 n n n 次,每次确定一个未访问的最近节点
t
:-
找到当前未在集合 S S S 中且
dist
最小的点t
。 -
将
t
加入集合 $S`(已确定最短路径)。 -
用
t
更新其他点的距离:for (int j = 1; j <= n; j++)dist[j] = min(dist[j], dist[t] + g[t][j]);
-
2. Dijkstra(堆优化版)
时间复杂度: O ( m log m ) O(m \log m) O(mlogm)
适用场景:稀疏图(边数 m ≪ n 2 m \ll n^2 m≪n2),且无负权边。
算法步骤:
-
使用优先队列(小根堆)存储
{距离, 节点}
。 -
每次取出堆顶节点
t
,若已访问则跳过,否则标记为已访问。 -
遍历
t
的所有邻接边,松弛更新距离并加入堆:priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push({0, 1}); while (!heap.empty()) {auto [d, t] = heap.top(); heap.pop();if (st[t]) continue;st[t] = true;for (auto [j, w] : adj[t]) {if (dist[j] > dist[t] + w) {dist[j] = dist[t] + w;heap.push({dist[j], j});}} }
3. Bellman-Ford
时间复杂度: O ( n m ) O(nm) O(nm)
适用场景:允许负权边,并可检测负权回路(最多松弛 n − 1 n-1 n−1 次)。
算法步骤:
-
初始化
dist[]
,dist[1] = 0
,其余为+∞
。 -
松弛所有边 k k k 次(若求不超过 k k k 条边的最短路):
for (int i = 0; i < k; i++) {memcpy(backup, dist, sizeof dist); // 备份防止串联for (int j = 0; j < m; j++) {int a = edges[j].a, b = edges[j].b, w = edges[j].w;dist[b] = min(dist[b], backup[a] + w);} }
4. SPFA(队列优化的 Bellman-Ford)
时间复杂度:平均 O ( n ) O(n) O(n),最坏 O ( n m ) O(nm) O(nm)
适用场景:负权边但无负权回路,稀疏图效率较高。
算法步骤:
-
使用队列存储待松弛的节点。
-
每次取出队首节点
t
,遍历其邻接边并松弛:queue<int> q; q.push(1); st[1] = true; // 标记是否在队列中 while (!q.empty()) {int t = q.front(); q.pop();st[t] = false;for (auto [j, w] : adj[t]) {if (dist[j] > dist[t] + w) {dist[j] = dist[t] + w;if (!st[j]) q.push(j), st[j] = true;}} }
5. Floyd(动态规划)
时间复杂度: O ( n 3 ) O(n^3) O(n3)
适用场景:多源最短路,允许负权边(不能有负权回路)。
算法步骤:
-
初始化邻接矩阵
d[i][j]
(存储边权,对角线为 0 0 0)。 -
三重循环枚举中间点 k k k,更新所有点对的最短距离:
for (int k = 1; k <= n; k++)for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
对比总结
算法 | 时间复杂度 | 负权边 | 负权回路检测 | 适用场景 |
---|---|---|---|---|
Dijkstra-朴素 | O ( n 2 ) O(n^2) O(n2) | ❌ | ❌ | 稠密图 |
Dijkstra-堆 | O ( m log m ) O(m \log m) O(mlogm) | ❌ | ❌ | 稀疏图 |
Bellman-Ford | O ( n m ) O(nm) O(nm) | ✔️ | ✔️ | 限制边数最短路 |
SPFA | O ( n ) ∼ O ( n m ) O(n) \sim O(nm) O(n)∼O(nm) | ✔️ | ✔️ | 稀疏图或负权边 |
Floyd | O ( n 3 ) O(n^3) O(n3) | ✔️ | ❌ | 多源最短路 |
注:
- 负权回路检测:Bellman-Ford 和 SPFA 在松弛 n n n 次后若仍能更新
dist
,则存在负权回路。 - Dijkstra 的局限性:无法处理负权边,因贪心策略可能导致错误结果。