Intuitively, you can think of SPFA as a variation on the Bellman-Ford algorithm. As a reminder, Bellman-Ford works like this. Here, d[v] denotes our current guess of the distance from the start node s to node v, and dist(u, v) denotes the length of edge (u, v):
set d[v] = infinity for all nodes v
set d[s] = 0
for l = 1 to n-1, where n is the number of nodes:
for each node u:
for each node v adjacent to u:
if d[u] + dist(u, v) < d[v]:
set d[v] = d[u] + dist(u, v)
The idea is that the loop on l guarantees that at the start of iteration l, the value of d[v] is the length of the shortest path from s to v using at most l edges.
The optimization SPFA uses works by noticing that the inner loop on u will not have any effect unless d[u] changed in the previous iteration of the outer loop. We therefore can save some work by rewriting Bellman-Ford as follows:
set d[v] = infinity for all nodes v
set d[s] = 0
for l = 1 to n-1, where n is the number of nodes:
for each node u where d[u] changed:
for each node v adjacent to u:
if d[u] + dist(u, v) < d[v]:
set d[v] = d[u] + dist(u, v)
Now, how should we implement this idea? To loop over all nodes that were updated on the previous iteration, and no others, we can maintain a queue containing the nodes we need to process on the next iteration. That queue shouldn’t contain any duplicates, since we just need to know whether we do or don’t process it next time. That gives this code:
set d[v] = infinity for all nodes v
set d[s] = 0
set q = {s}
for l = 1 to n-1, where n is the number of nodes:
set q’ = {}
for each node u in q:
for each node v adjacent to u:
if d[u] + dist(u, v) < d[v]:
set d[v] = d[u] + dist(u, v)
add v to q’ if it’s not already there
set q = q’
The last step to make this look like the final version of SPFA is to unify the roles of q’ and q. We can do that under the assumption that there are no negative cycles as follows:
set d[v] = infinity for all nodes v
set d[s] = 0
set q = {s}
while q isn’t empty;
remove a node u from q
for each node v adjacent to u:
if d[u] + dist(u, v) < d[v]:
set d[v] = d[u] + dist(u, v)
add v to q if it’s not already there