Skip to main content

SPFA (Shortest Path Faster Algorithm)

SPFA is a queue-based refinement of Bellman-Ford. Instead of relaxing every edge V1V - 1 times, it keeps a FIFO queue of vertices whose tentative distance has recently decreased, and only relaxes the edges out of those vertices. A vertex that is not currently in the queue is pushed whenever its tentative distance drops. The algorithm terminates when the queue empties.

In the worst case SPFA is still O(VE)O(V E) — adversarial graphs can force every edge to be relaxed V1V - 1 times — but on typical sparse graphs with few negative edges it is often many times faster than plain Bellman-Ford. Like Bellman-Ford, it handles negative edge weights and can report a negative cycle reachable from the source: if any vertex is relaxed more than V1V - 1 times, there is one.

The solve function takes [n, edges, source] with edges as [u, v, w] triples and returns the distance array using 10**18 for unreachable vertices, or [] if a negative cycle is reachable.

Codes

from collections import deque

def solve(xs):
"""xs = [n, edges, source]. Return distances, or [] on negative cycle."""
n, edges, source = xs
INF = 10**18
adj = [[] for _ in range(n)]
for u, v, w in edges:
adj[u].append((v, w))
dist = [INF] * n
dist[source] = 0
in_queue = [False] * n
count = [0] * n
q = deque([source])
in_queue[source] = True
while q:
u = q.popleft()
in_queue[u] = False
for v, w in adj[u]:
nd = dist[u] + w
if nd < dist[v]:
dist[v] = nd
count[v] += 1
if count[v] >= n:
return [] # negative cycle reachable
if not in_queue[v]:
q.append(v)
in_queue[v] = True
return dist

Example

Loading Python runner...

Description

Run time analysis

O(VE)O(V E) worst case, same as Bellman-Ford. On random or typical sparse graphs it is usually closer to O(kE)O(k E) for a small constant kk — each vertex is only re-enqueued when its distance strictly improves — but hand-crafted adversarial inputs (grid-like graphs with specific weights) can realise the worst case.

Space analysis

O(V+E)O(V + E). The adjacency list is O(V+E)O(V + E); the distance, in-queue, and relaxation-count arrays together with the FIFO queue add O(V)O(V).

Proof of correctness

Partial correctness (no negative cycle). Let δ(s,v)\delta(s, v) denote the true shortest-path distance. We claim that when the queue empties, dist[v]=δ(s,v)dist[v] = \delta(s, v) for every vertex vv reachable from ss.

The following invariant holds throughout the run: for every vertex vv, if dist[v]dist[v] is currently greater than δ(s,v)\delta(s, v), then either vv is already in the queue, or there exists a vertex uu in the queue with dist[u]+(some path weight from u to v)=δ(s,v)dist[u] + \text{(some path weight from } u \text{ to } v\text{)} = \delta(s, v). Concretely, any vertex that still has a suboptimal tentative distance is guaranteed to be reached again, because whenever we pop a vertex uu and find a shorter route through uu to a neighbour vv, we push vv onto the queue.

More formally, SPFA performs the same relaxations as a Bellman-Ford sweep, just in a different order. By the Bellman-Ford correctness argument (induction on path length; see the Bellman-Ford page), after relaxing every edge at most V1V - 1 times on every shortest path svs \to v we reach dist[v]=δ(s,v)dist[v] = \delta(s, v). SPFA relaxes (u,v)(u, v) whenever dist[u]dist[u] decreases, so the subsequence of relaxations it performs along any fixed shortest path sv1v2vks \to v_1 \to v_2 \to \dots \to v_k is in order, and each prefix distance stabilises before the next relaxation is triggered. After at most V1V - 1 relaxations per edge in the worst case, every reachable vertex has its final value and no more changes occur, so the queue drains.

Negative-cycle detection. Every relaxation of a vertex vv strictly decreases dist[v]dist[v]. If no negative cycle is reachable from ss, then dist[v]dist[v] cannot drop below δ(s,v)\delta(s, v), and because every relaxation along any shortest path corresponds to a distinct edge on that path (a simple shortest path has at most V1V - 1 edges), vv can be relaxed at most V1V - 1 times. So if the counter reaches VV, there is no simple walk from ss to vv that could explain that many strict decreases — the only remaining possibility is that some cycle on an svs \to v walk has negative total weight. Reporting a negative cycle in that case is correct.

Extensions

Applications

References

  • Cormen, Leiserson, Rivest, Stein. Introduction to Algorithms, 3rd ed., Chapter 24 (Single-Source Shortest Paths).
  • Bellman, R. (1958). "On a routing problem." Quarterly of Applied Mathematics, 16(1), 87-90.
  • cp-algorithms — Bellman-Ford and SPFA