āš™ļøAlgorithmIntermediate

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 definitions

01Overview

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

Given a directed weighted graph G = (V, E) with edge weights w(u, v), the single-source shortest path problem is to compute distances d(v) such that d(v) = w(x, y), for a fixed source s, provided no negative-weight cycle is reachable from s. SPFA maintains an array d(v) initialized with d(s)=0 and d(v)=+ for v s, a queue Q of candidate vertices, and a Boolean in\_Q(v) indicating membership in Q. While Q is non-empty, SPFA extracts u from Q and relaxes all edges (u,v) E: if d(u) + w(u,v) < d(v) then set d(v) := d(u) + w(u,v). If v is not already in Q, push v into Q and increment a counter cnt(v). If for some v, cnt(v) , then a negative cycle is reachable and the algorithm reports failure. This process terminates when no further relaxation is possible, guaranteeing optimal distances identical to Bellman–Ford’s result if no negative cycle exists. The algorithm can be implemented with FIFO queue or a deque with heuristics; its worst-case time is O() and space is O( + ).

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

Let n = |V| and m = |E|. SPFA maintains a queue of active vertices. Each time a vertex u is popped, we scan all outgoing edges (u, v), attempting relaxations. An edge relaxation is O(1), so processing one vertex costs O(outdeg(u)). The total cost equals the sum over all times vertices are popped. In the best and typical sparse-case scenarios, each vertex is enqueued and popped only a few times (often constant on average), so the total time is near O(n + m). This happens when distance labels settle quickly and local improvements do not repeatedly ripple through long cycles. However, in the worst case (e.g., carefully constructed graphs with long, nearly non-increasing cycles), some vertices can be re-enqueued Θ(n) times, causing up to Θ(nm) relaxations, which matches Bellman–Ford’s O(nm) upper bound. Space complexity is O(n + m): we store the adjacency list (O(m)), the distance array (O(n)), the in-queue flags and counters (O(n)), and the queue itself (up to O(n) elements). Heuristics such as SLF (Small Label First) and LLL (Large Label Last) aim to reduce the average number of queue operations by prioritizing vertices with promising (smaller) distance labels and deferring those with larger labels. These do not change worst-case bounds but often improve constants significantly in practice. Negative cycle detection is integrated by counting enqueues: if any cnt[v] reaches n, we can terminate early and report a negative cycle, potentially saving work compared to running full Bellman–Ford passes.

Code Examples

Basic SPFA with Negative Cycle Detection (FIFO queue)
1#include <bits/stdc++.h>
2using 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
8struct Edge {
9 int to;
10 long long w;
11};
12
13const long long INF = (1LL << 60);
14
15int 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.

Time: O(VE) worst-case; often O(V + E) on sparse graphs in practiceSpace: O(V + E)
SPFA with SLF and LLL Heuristics Using a Deque
1#include <bits/stdc++.h>
2using 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
9struct Edge { int to; long long w; };
10const long long INF = (1LL << 60);
11
12int 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.

Time: O(VE) worst-case; often significantly faster in practiceSpace: O(V + E)
Feasibility of Difference Constraints via SPFA (with Super-Source)
1#include <bits/stdc++.h>
2using 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
9struct Edge { int to; long long w; };
10const long long INF = (1LL<<60);
11
12int 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.

Time: O(VE) worst-case on the constructed graph (V = n+1, E = m + n)Space: O(V + E)