SPFA (Shortest Path Faster Algorithm)
Key Points
- ā¢SPFA is a queue-based optimization of BellmanāFord that only relaxes edges from vertices whose distance just improved.
- ā¢It often runs in O(V + E) on sparse graphs in practice but has a worst-case of O(VE).
- ā¢Use SPFA when you have negative edge weights but no negative cycles and need single-source shortest paths.
- ā¢Detect a negative cycle if any vertex is enqueued (relaxed) at least V times during the algorithm.
- ā¢Using a deque with Small Label First (SLF) and Large Label Last (LLL) heuristics can significantly speed up SPFA in practice.
- ā¢SPFA is also a standard tool to solve systems of difference constraints by adding a super-source.
- ā¢Careful implementation detailsālike preventing multiple enqueues and using 64-bit distancesāimprove correctness and performance.
- ā¢On dense graphs or adversarial inputs, prefer BellmanāFord or Dijkstra with reweighting (Johnsonās algorithm).
Prerequisites
- āGraphs and adjacency lists ā SPFA operates on directed weighted graphs typically stored as adjacency lists.
- āSingle-source shortest paths ā Understanding the goal of computing distances from one source clarifies SPFAās purpose.
- āBellmanāFord algorithm ā SPFA is a queue-optimized variant; knowing BellmanāFord helps with correctness and worst-case bounds.
- āQueues and deques ā SPFAās efficiency relies on managing an active set via FIFO or double-ended queues.
- āRelaxation and triangle inequality ā Core operation in all shortest path algorithms; correctness relies on these concepts.
- āNegative cycles ā Their presence invalidates finite shortest paths and must be detected by SPFA.
- āTime complexity analysis ā To judge when SPFA is appropriate and to compare against alternatives like Dijkstra.
- āDifference constraints ā A classic application of SPFA; modeling constraints as edges requires this background.
Detailed Explanation
Tap terms for definitions01Overview
The Shortest Path Faster Algorithm (SPFA) is a practical improvement over the classic BellmanāFord algorithm for computing single-source shortest paths on graphs that may contain negative edge weights (but no negative cycles). Instead of repeatedly relaxing all edges in fixed rounds, SPFA places only the vertices whose distances have just decreased into a queue, then relaxes outgoing edges from those vertices. This targeted relaxation strategy avoids unnecessary work when many vertices are already at their optimal distances. In many real-world sparse graphs (e.g., road networks with some negative adjustments, scheduling with constraints), relatively few vertices keep changing their distances, so the queue remains small and processing can be close to linear in the size of the graph. However, SPFA does not improve the worst-case theoretical bound: in carefully constructed adversarial graphs it can still take O(VE) time, just like BellmanāFord. Despite that, SPFA is popular in programming contests and engineering applications due to its simplicity, ease of negative-cycle detection, and excellent average performance.
02Intuition & Analogies
Imagine youāre spreading news (shortest distance information) across a network of friends. BellmanāFord is like shouting the news to every person in the network Vā1 times, even if they already know itāinefficient but guaranteed. SPFA is more polite and strategic: you only tap the shoulder of people whose information actually changed, and ask them to inform their direct friends. If your news settles quickly, you end up telling far fewer people overall. The queue in SPFA represents the current frontier of people whose knowledge just improved; pulling someone from the queue lets them update their neighbors. If no oneās knowledge improves, the queue empties and youāre done. But thereās a catch: in some nasty rumor networks, small cycles keep causing slight improvements, making you revisit the same people many timesāthis is the worst-case where SPFA becomes slow. To mitigate that, seasoned rumor spreaders use tricks: SLF (put the most promising people at the front) and LLL (kick temporarily unpromising people to the back). These heuristics often keep the queue short. If you ever find that someone had to be informed more than V times, thatās a red flag: the gossip circle is so reinforcing that improvements never endāanalogous to a negative-weight cycle in a graph where costs keep decreasing along a loop.
03Formal Definition
04When to Use
Use SPFA when your graph may contain negative edges but is sparse, and you need single-source shortest paths or feasibility checks. Typical scenarios include: (1) difference constraints systems of the form x_v ā x_u \le w, where feasibility reduces to detecting negative cycles and any feasible solution can be extracted from distances; (2) scheduling and timing analysis where edges encode precedence with slacks (possibly negative); (3) shortest paths in road-like graphs after applying adjustments (e.g., discounts, penalties) that can be negative; (4) as a subroutine in Johnsonās algorithm to compute vertex potentials when you suspect negative edges but want to speed up over plain BellmanāFord. SPFA is a strong contest choice for average cases within time limits, particularly for CF 1500ā1900 problems on sparse graphs up to around 2e5 edges, provided there are no adversarial structures that trigger worst-case behavior. Avoid SPFA on dense graphs or when adversarial cases are likely; prefer BellmanāFord (predictable O(VE)) or Dijkstra with reweighting when all edges can be made non-negative.
ā ļøCommon Mistakes
Common pitfalls include: (1) Using 32-bit int for distances when path sums can overflow; always use 64-bit (long long) and define INF carefully. (2) Forgetting to prevent multiple enqueues of the same vertex, which bloats the queue; track in_Q[v]. (3) Incorrect negative-cycle detection: you must count how many times each vertex is enqueued (or relaxed) and declare a negative cycle if any count reaches |V|; counting relax attempts alone can be misleading. (4) Mixing 0-indexed and 1-indexed vertices when reading input, causing out-of-bounds or wrong answers. (5) Not handling disconnected graphs; unreachable vertices should remain at INF. (6) Applying SPFA blindly to dense or adversarial graphs where it can time out; know the worst-case O(VE). (7) Omitting heuristics or early exits like skipping relaxations when d[u] is INF. (8) Using an inappropriate queue policy; for many cases, a deque with SLF (push front when d[v] becomes smaller than the current frontās distance) and an optional LLL check greatly reduces runtime. (9) In difference constraints, mixing inequality directions; ensure you build edge uāv with weight w for constraint x_v ā x_u ⤠w, and add a super-source linked to all nodes with 0-weight edges.
Key Formulas
Shortest path definition
Explanation: The shortest distance to v equals the minimum total weight over all paths from s to v. SPFA aims to compute these values when no negative cycle is reachable.
Triangle inequality (edge form)
Explanation: For any edge (u, v), the best-known distance to v cannot exceed going through u then taking (u, v). Relaxation enforces this inequality to become tight on optimal paths.
Relaxation update
Explanation: Each relaxation tries to improve d(v) using the path that goes through u. If it gets smaller, v becomes a candidate for further propagation in SPFA.
BellmanāFord complexity
Explanation: BellmanāFord relaxes all m edges over nā1 passes, which leads to O(nm) time. This is the predictable upper bound SPFA shares in the worst case.
SPFA average-case
Explanation: Empirically on many sparse graphs, each vertex enters the queue only a few times and most edges are relaxed a small number of times, giving near-linear time.
SPFA worst-case
Explanation: On adversarial graphs, vertices can be re-enqueued many times due to small improvements around cycles, matching BellmanāFordās O(nm) time.
Negative cycle detection in SPFA
Explanation: If any vertex has been enqueued at least n times, then a negative cycle is reachable from the source. Distances would decrease indefinitely.
Difference constraints encoding
Explanation: A system of inequalities of the form ā reduces to shortest paths on a graph with edges weighted by w. Infeasibility corresponds to a negative cycle.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Basic SPFA implementation using a FIFO queue. 5 // Reads: n m s, then m edges (u v w) 1-indexed. 6 // Prints shortest distances or INF if unreachable; prints NEGATIVE CYCLE if detected. 7 8 struct Edge { 9 int to; 10 long long w; 11 }; 12 13 const long long INF = (1LL << 60); 14 15 int main() { 16 ios::sync_with_stdio(false); 17 cin.tie(nullptr); 18 19 int n, m, s; 20 if (!(cin >> n >> m >> s)) { 21 return 0; 22 } 23 24 vector<vector<Edge>> g(n + 1); 25 for (int i = 0; i < m; ++i) { 26 int u, v; long long w; 27 cin >> u >> v >> w; 28 g[u].push_back({v, w}); 29 } 30 31 vector<long long> dist(n + 1, INF); 32 vector<int> cnt(n + 1, 0); // enqueue counts per vertex 33 vector<char> inq(n + 1, 0); // whether currently in queue 34 35 queue<int> q; 36 dist[s] = 0; 37 q.push(s); 38 inq[s] = 1; 39 cnt[s] = 1; 40 41 bool negCycle = false; 42 43 while (!q.empty() && !negCycle) { 44 int u = q.front(); q.pop(); 45 inq[u] = 0; 46 47 if (dist[u] == INF) continue; // unreachable; skip 48 49 for (const auto &e : g[u]) { 50 int v = e.to; 51 long long nd = dist[u] + e.w; 52 if (nd < dist[v]) { 53 dist[v] = nd; 54 if (!inq[v]) { 55 q.push(v); 56 inq[v] = 1; 57 if (++cnt[v] >= n) { 58 // A vertex entered queue n times: negative cycle reachable 59 negCycle = true; 60 break; 61 } 62 } 63 } 64 } 65 } 66 67 if (negCycle) { 68 cout << "NEGATIVE CYCLE\n"; 69 return 0; 70 } 71 72 for (int v = 1; v <= n; ++v) { 73 if (dist[v] >= INF/2) cout << "INF\n"; 74 else cout << dist[v] << "\n"; 75 } 76 77 return 0; 78 } 79
This program constructs an adjacency list, initializes distances, and runs SPFA from source s. Vertices are pushed into a FIFO queue only when their distance decreases. The cnt array counts how many times a vertex has been enqueued; reaching n implies a negative cycle is reachable. After termination, distances remain INF for unreachable vertices.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // SPFA with two practical heuristics: 5 // - SLF (Small Label First): push_front if new label is smaller than front's label. 6 // - LLL (Large Label Last): if popped vertex has larger label than queue average, defer it. 7 // Input: n m s, then m edges (u v w) 1-indexed. Output distances or NEGATIVE CYCLE. 8 9 struct Edge { int to; long long w; }; 10 const long long INF = (1LL << 60); 11 12 int main(){ 13 ios::sync_with_stdio(false); 14 cin.tie(nullptr); 15 16 int n, m, s; 17 if(!(cin >> n >> m >> s)) return 0; 18 19 vector<vector<Edge>> g(n+1); 20 for(int i=0;i<m;++i){ 21 int u,v; long long w; cin >> u >> v >> w; 22 g[u].push_back({v,w}); 23 } 24 25 vector<long long> dist(n+1, INF); 26 vector<int> cnt(n+1, 0); 27 vector<char> inq(n+1, 0); 28 29 deque<int> dq; 30 dist[s]=0; dq.push_back(s); inq[s]=1; cnt[s]=1; 31 32 // Maintain approximate average of labels currently in deque for LLL 33 long double sumInQ = 0.0L; sumInQ += 0; // dist[s] = 0 34 35 bool negCycle = false; 36 37 auto push_vertex = [&](int v){ 38 // SLF: compare with front 39 if (!dq.empty() && dist[v] < dist[dq.front()]) { 40 dq.push_front(v); 41 } else { 42 dq.push_back(v); 43 } 44 inq[v]=1; 45 sumInQ += (long double)dist[v]; 46 }; 47 48 while(!dq.empty() && !negCycle){ 49 int u = dq.front(); dq.pop_front(); 50 inq[u]=0; 51 sumInQ -= (dist[u] >= INF/2 ? 0.0L : (long double)dist[u]); 52 53 // LLL: if dist[u] is larger than average of current queue, defer it 54 if(!dq.empty()){ 55 long double avg = sumInQ / (long double)dq.size(); 56 if ((long double)dist[u] > avg) { 57 // Defer u 58 if (!dq.empty() && dist[u] < dist[dq.front()]) dq.push_front(u); 59 else dq.push_back(u); 60 inq[u]=1; 61 sumInQ += (dist[u] >= INF/2 ? 0.0L : (long double)dist[u]); 62 continue; 63 } 64 } 65 66 if (dist[u] == INF) continue; 67 68 for(const auto &e: g[u]){ 69 int v = e.to; 70 long long nd = dist[u] + e.w; 71 if(nd < dist[v]){ 72 dist[v] = nd; 73 if(!inq[v]){ 74 push_vertex(v); 75 if(++cnt[v] >= n){ negCycle = true; break; } 76 } 77 } 78 } 79 } 80 81 if(negCycle){ cout << "NEGATIVE CYCLE\n"; return 0; } 82 83 for(int v=1; v<=n; ++v){ 84 if(dist[v] >= INF/2) cout << "INF\n"; else cout << dist[v] << "\n"; 85 } 86 return 0; 87 } 88
This variant uses a deque and two classic heuristics. SLF tries to process smaller distances earlier by pushing improved vertices to the front. LLL defers processing vertices whose current distance is larger than the average inside the queue. Both reduce unnecessary relaxations in many practical inputs without changing correctness or worst-case bounds.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Solve system of constraints: x_v - x_u <= w for m constraints. 5 // Build edges u -> v with weight w, add super-source 0 -> i with 0. 6 // If a negative cycle is reachable, the system is infeasible. 7 // Otherwise, dist[i] gives one feasible assignment for x_i. 8 9 struct Edge { int to; long long w; }; 10 const long long INF = (1LL<<60); 11 12 int main(){ 13 ios::sync_with_stdio(false); 14 cin.tie(nullptr); 15 16 int n, m; // variables 1..n, constraints count 17 if(!(cin >> n >> m)) return 0; 18 19 vector<vector<Edge>> g(n+1); // we will use nodes 0..n (0 is super-source) 20 21 for(int i=0;i<m;++i){ 22 int u, v; long long w; // constraint: x_v - x_u <= w 23 cin >> u >> v >> w; 24 g[u].push_back({v, w}); 25 } 26 27 // Add super-source 0 with 0-weight edges to all variables 28 for(int i=1;i<=n;++i) g[0].push_back({i, 0}); 29 30 vector<long long> dist(n+1, INF); 31 vector<char> inq(n+1, 0); 32 vector<int> cnt(n+1, 0); 33 34 queue<int> q; 35 dist[0] = 0; q.push(0); inq[0]=1; cnt[0]=1; 36 37 bool negCycle=false; 38 39 while(!q.empty() && !negCycle){ 40 int u = q.front(); q.pop(); inq[u]=0; 41 if(dist[u] == INF) continue; 42 for(const auto &e: g[u]){ 43 int v = e.to; long long nd = dist[u] + e.w; 44 if(nd < dist[v]){ 45 dist[v] = nd; 46 if(!inq[v]){ 47 q.push(v); inq[v]=1; 48 if(++cnt[v] >= n+1){ // including super-source 49 negCycle = true; break; 50 } 51 } 52 } 53 } 54 } 55 56 if(negCycle){ 57 cout << "INFEASIBLE\n"; 58 } else { 59 cout << "FEASIBLE\n"; 60 // One feasible assignment: x_i = dist[i] 61 for(int i=1;i<=n;++i){ 62 if(dist[i] >= INF/2) cout << "UNREACHABLE\n"; // should not happen with super-source 63 else cout << dist[i] << "\n"; 64 } 65 } 66 return 0; 67 } 68
Each constraint x_v ā x_u ⤠w becomes an edge uāv with weight w. A super-source 0 connects to all variables with 0-weight edges to allow a uniform start. SPFA detects infeasibility via negative cycles. If feasible, the distances provide one assignment satisfying all constraints because dist[v] ā dist[u] ⤠w holds for every edge.