以下摘要由GPT-4o生成 : SPFA(Shortest Path Faster Algorithm)是一种针对最短路径问题的高效算法,特别适用于处理含有负权边的图。其核心思想是通过动态选择可能优化路径的节点进行松弛,避免冗余操作。与Dijkstra算法相比,SPFA允许节点多次入队和处理,从而能及时更新路径长度,尤其在遇到负权边时表现出更好的灵活性。然而,如果图中存在负权环,SPFA可能会出现死循环,因此需要额外判断。整体而言,SPFA通过“按需松弛”提高了效率,适应性强,但在使用时需关注负权环的问题。
题目链接[1.蓝桥公园 - 单源最短路径 ]
floyd 时间复杂度O(n ^ n ^ n) n == 100左右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <cstdio> #include <cstring> #include <iostream> int n, m, q;const int N = 410 , INF = 0x3f3f3f3f ;int g[N][N];void floyd () { for (int k = 1 ; k <= n; k++) { for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <=n; j++ ) { if (g[i][j] > g[i][k] + g[k][j]) g[i][j] = g[i][k] + g[k][j]; } } } } int main () { memset (g, 0x3f ,sizeof g); scanf ("%d%d%d" , &n, &m, &q); for (int i = 1 ; i <= n; i ++) g[i][i] = 0 ; while ( m -- ) { int a, b, c; scanf ("%d%d%d" , &a, &b, &c); if (c < g[a][b]) { g[a][b] = c; g[b][a] = c; } } floyd (); while (q -- ) { int u, v; scanf ("%d%d" , &u, &v); if (g[u][v] == INF) printf ("-1\n" ); else printf ("%d\n" , g[u][v]); } return 0 ; }
dijkstra 间复杂度O(n ^ n × log(m)),n为点,m为边数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <cstring> using namespace std;const int N = 410 ;const int INF = 0x3f3f3f3f ;int n, m, q;int g[N][N]; bool st[N]; int dist[N]; int dijkstra (int u, int v) { memset (dist, 0x3f , sizeof dist); memset (st, 0 , sizeof st); dist[u] = 0 ; for (int i = 0 ; i < n - 1 ; i++) { int t = -1 ; for (int j = 1 ; j <= n; j++) { if (!st[j] && (t == -1 || dist[j] < dist[t])) { t = j; } } if (t == -1 ) break ; st[t] = true ; for (int j = 1 ; j <= n; j++) { if (dist[j] > dist[t] + g[t][j]) { dist[j] = dist[t] + g[t][j]; } } } return dist[v] == INF ? -1 : dist[v]; } int main () { scanf ("%d%d%d" , &n, &m, &q); memset (g, 0x3f , sizeof g); for (int i = 1 ; i <= n; i++) g[i][i] = 0 ; while (m--) { int a, b, c; scanf ("%d%d%d" , &a, &b, &c); if (c < g[a][b]) { g[a][b] = c; g[b][a] = c; } } while (q--) { int u, v; scanf ("%d%d" , &u, &v); printf ("%d\n" , dijkstra (u, v)); } return 0 ; }
优化版dijkstra(priority_queue) 堆化优化后为:O(m × log(n))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std;const int N = 3e5 +10 , M = 1e6 , INF = 0x3f3f3f3f ;int n, m;int dist[N];bool st[N];int e[M], h[N], ne[M], idx, w[M];typedef pair<int , int > PII;void add (int a,int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void dijkstra () { memset (dist, 0x3f , sizeof dist); priority_queue <PII, vector<PII>, greater<PII>> pq; dist[1 ] = 0 ; pq.push ({0 , 1 }); while (!pq.empty ()) { auto t = pq.top (); pq.pop (); int ver = t.second, distance = t.first; if (st[ver]) continue ; st[ver] = true ; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; pq.push ({dist[j], j}); } } } } int main () { memset (h, -1 , sizeof h); scanf ("%d%d" , &n, &m); for (int i = 0 ; i < m; ++i) { int u, v, w; scanf ("%d%d%d" , &u, &v, &w); add (u, v, w); } dijkstra (); for (int i = 1 ; i <= n; ++i) { if (i > 1 ) printf (" " ); printf ("%d" , dist[i] == INF ? -1 : dist[i]); } printf ("\n" ); return 0 ; }
处理负权边-spfa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int n; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; int spfa () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; queue<int > q; q.push (1 ); st[1 ] = true ; while (q.size ()) { auto t = q.front (); q.pop (); st[t] = false ; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push (j); st[j] = true ; } } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
SPFA(Shortest Path Faster Algorithm)的核心思想是动态选择可能优化最短路径的节点进行松弛 ,避免全局遍历所有边的冗余操作。其核心机制与Dijkstra的贪心策略不同,具体思想可通过以下步骤拆解:
SPFA的核心思想
松弛驱动的动态更新
只处理可能缩短路径的节点 。当一个节点被松弛成功(即发现更短的路径)时,才将其加入队列等待后续处理。
与Dijkstra的“一次性确定最短路径”不同,SPFA允许节点多次入队 ,确保所有可能的路径优化都被探索。
队列维护待处理节点
使用普通队列(而非优先队列)管理待处理的节点,遵循先进先出原则。
节点出队时,尝试松弛其邻接边;若邻接点距离被更新且不在队列中,则将其入队。
处理负权边的能力
由于允许节点多次松弛,即使存在负权边导致路径反复缩短,SPFA仍能正确更新距离。
但图中若存在负权环 ,SPFA会陷入死循环(需额外判断环的存在)。
与Dijkstra算法的对比
特性
Dijkstra
SPFA
策略
贪心选择当前距离最小的节点
动态处理可能被松弛的节点
数据结构
优先队列(最小堆)
普通队列
节点处理次数
每个节点仅处理一次
节点可能多次入队
负权边支持
❌ 无法处理
✅ 支持(无负权环时)
时间复杂度
O(m log n)(堆优化)
平均O(m),最坏O(nm)
适用场景
边权非负的图
含负权边但无负权环的图
举例说明 假设节点A 的最短距离初始为5,后续发现一条通过负权边的路径使其距离变为3:
Dijkstra :已处理过A,不再更新,导致错误。
SPFA :将A重新入队,继续松弛其邻接边,最终得到正确的最短距离。
SPFA的关键优势
高效性 :通过队列减少冗余松弛,实际运行效率接近BFS。
灵活性 :支持负权边,弥补了Dijkstra的不足。
动态性 :每次只处理可能影响后续路径的节点,避免全局遍历。
总结 SPFA的思想是“按需松弛” ,通过队列动态管理需要更新的节点,确保每次操作都是必要的。与Dijkstra的“贪心固化路径”不同,SPFA的灵活性使其能处理更复杂的图结构(如含负权边),但需注意负权环的检测。两者本质区别在于是否允许节点重复参与路径优化 ,这决定了它们在不同场景下的适用性。