ART

The Shortest Path Faster Algorithm (SPFA) is an improvement of the Bellman–Ford algorithm which computes single-source shortest paths in a weighted directed graph. The algorithm is believed to work well on random sparse graphs and is particularly suitable for graphs that contain negative-weight edges.[1] However, the worst-case complexity of SPFA is the same as that of Bellman–Ford, so for graphs with nonnegative edge weights Dijkstra's algorithm is preferred. The SPFA algorithm was first published by Edward F. Moore in 1959, as a generalization of breadth first search;[2] SPFA is Moore's “Algorithm D”.

SPFADemo

A demo of SPFA based on Euclidean distance. Red lines are the shortest path covering (so far observed). Blue lines indicate where relaxing happens, i.e., connecting } v with a node u in Q Q, which gives a shorter path from the source to v.

Python 3 Code

'''
Shortest path covering (SVG) using SP Faster algorithm with queue.
Firstly use this code to generate SVG frames.
Then transform to bitmaps and convert to GIF.
'''

# range size
N = 500
margin = 20

def norm(px, py):
    return ((px[0]-py[0])**2+(px[1]-py[1])**2)**.5

def saveToSVG(nFrames, points, edges, firmed, relaxing):
    f = open('demo_'+'0'*(3-len(str(nFrames)))+str(nFrames)+'.svg', 'w')
    f.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n")
    for p in points:
        f.write("<circle cx=\"" +str(p[0]+margin)+ "\" cy=\""+ str(N-p[1]+margin) +"\" r=\"5\" fill=\"white\" stroke=\"black\"/>\n")
    for i in range(len(edges)):
        for j in edges[i]:
            f.write("<line x1=\"" +str(points[i][0]+margin)+ "\" y1=\""+ str(N-points[i][1]+margin) +"\" x2=\"" + str(points[j][0]+margin) + "\" y2=\"" + str(N-points[j][1]+margin) + "\" stroke=\"grey\" stroke-width=\".5\"/>\n")
    for L in firmed:
        f.write("<line x1=\"" +str(L[0][0]+margin)+ "\" y1=\""+ str(N-L[0][1]+margin) +"\" x2=\"" + str(L[1][0]+margin) + "\" y2=\"" + str(N-L[1][1]+margin) + "\" stroke=\"red\" stroke-width=\"5\"/>\n")
    for L in relaxing:
        f.write("<line x1=\"" +str(L[0][0]+margin)+ "\" y1=\""+ str(N-L[0][1]+margin) +"\" x2=\"" + str(L[1][0]+margin) + "\" y2=\"" + str(N-L[1][1]+margin) + "\" stroke=\"blue\" stroke-width=\"5\"/>\n")
    f.write("</svg>\n")
    f.close()

def generatePoints(n):
    import random as r
    r.seed(10)
    
    res = []
    for i in range(n):
        pt = [r.randint(0,N) for _ in [0, 1]]
        if [pt] not in res:
            res += [pt]
    return res

# heuristic: neighbor with radius e.g. N/3
def generateEdges(n, points):
    import random as r
    r.seed(10)
    edges = []
    for i in range(n):
        dst = []
        for j in range(n):
            if i!=j and norm(points[i], points[j]) < N/3:
                dst.append(j);
        edges.append(dst)
    return edges

def spfa(n, points, edges):
    nframe = 0
    dist = [float("inf") for i in range(n)]
    prev = [-1 for _ in range(n)]
    cover = []
    dist[0] = 0.0
    Q = [0]
    while len(Q)>0:
        u = Q[0]
        Q.pop(0)
        if prev[u]!=-1:
            cover.append([points[prev[u]], points[u]])
        saveToSVG(nframe, points, edges, cover, [])
        nframe+=1
        for i in edges[u]:
            if i!=u and dist[i] > dist[u] + norm(points[i], points[u]):
                dist[i] = dist[u] + norm(points[i], points[u])
                prev[i] = u
                if i not in set(Q): Q.append(i)
                for k in range(len(cover)):
                    if cover[k][1] == points[i]:
                        cover.pop(k)
                        break
                saveToSVG(nframe, points, edges, cover, [[points[u], points[i]]])
                nframe+=1
    
    return dist, prev

# test 50 points temporarily
n = 50
pts = generatePoints(n)
es = generateEdges(n, pts)
spfa(n, pts, es)

Algorithm

Given a weighted directed graph \( {\displaystyle G=(V,E)} \) and a source vertex s, the SPFA algorithm finds the shortest path from s {\displaystyle s} s, to each vertex v, in the graph. The length of the shortest path from s to v is stored in \( {\displaystyle d(v)} \) for each vertex v.

The basic idea of SPFA is the same as Bellman–Ford algorithm in that each vertex is used as a candidate to relax its adjacent vertices. The improvement over the latter is that instead of trying all vertices blindly, SPFA maintains a queue of candidate vertices and adds a vertex to the queue only if that vertex is relaxed. This process repeats until no more vertex can be relaxed.

Below is the pseudo-code of the algorithm.[3] Here Q is a first-in, first-out queue of candidate vertices, and \( {\displaystyle w(u,v)} \) is the edge weight of (u,v).
A demo of SPFA based on Euclidean distance. Red lines are the shortest path covering (so far observed). Blue lines indicate where relaxing happens, i.e., connecting v with a node u in Q, which gives a shorter path from the source to v.

 procedure Shortest-Path-Faster-Algorithm(G, s)
  1    for each vertex vs in V(G)
  2        d(v) := ∞
  3    d(s) := 0
  4    push s into Q
  5    while Q is not empty do
  6        u := poll Q
  7        for each edge (u, v) in E(G) do
  8            if d(u) + w(u, v) < d(v) then
  9                d(v) := d(u) + w(u, v)
 10                if v is not in Q then
 11                    push v into Q

The algorithm can also be applied to an undirected graph by replacing each undirected edge with two directed edge of opposite directions.
Proof of correctness

We will prove that the algorithm never computes incorrect shortest path lengths.

Lemma: Whenever the queue is checked for emptiness, any vertex currently capable of causing relaxation is in the queue.
Proof: We want to show that if \( {\displaystyle dist[w]>dist[u]+wt(u,w)} \) for any two vertices u and w at the time the condition is checked, u {\displaystyle u} u is in the queue. We do so by induction on the number of iterations of the loop that have already occurred. First we note that this certainly holds before the loop is entered: if \( {\displaystyle u\not =v} \), then relaxation is not possible; relaxation is possible from u=v , and this is added to the queue immediately before the while loop is entered. Now, consider what happens inside the loop. A vertex u is popped, and is used to relax all its neighbors, if possible. Therefore, immediately after that iteration of the loop, u {\displaystyle u} u is not capable of causing any more relaxations (and does not have to be in the queue anymore). However, the relaxation by x might cause some other vertices to become capable of causing relaxation. If there exists some \( {\displaystyle dist[x]>dist[w]+wt(w,x)} \) such that before the current loop iteration, then w {\displaystyle w} w is already in the queue. If this condition becomes true duringthe current loop iteration, then either \( {\displaystyle dist[x]} \) increased, which is impossible, or \( {\displaystyle dist[w]} \) decreased, implying that w {\displaystyle w} w was relaxed. But after w is relaxed, it is added to the queue if it is not already present.

Corollary: The algorithm terminates when and only when no further relaxations are possible.
Proof: If no further relaxations are possible, the algorithm continues to remove vertices from the queue, but does not add any more into the queue, because vertices are added only upon successful relaxations. Therefore the queue becomes empty and the algorithm terminates. If any further relaxations are possible, the queue is not empty, and the algorithm continues to run.

The algorithm fails to terminate if negative-weight cycles are reachable from the source. See here for a proof that relaxations are always possible when negative-weight cycles exist. In a graph with no cycles of negative weight, when no more relaxations are possible, the correct shortest paths have been computed (proof). Therefore in graphs containing no cycles of negative weight, the algorithm will never terminate with incorrect shortest path lengths[4].
Running time

The worst-case running time of the algorithm is \( O(|V|\cdot|E|) \) , just like the standard Bellman-Ford algorithm.[1] Experiments suggest that the average running time is O(|E|) , and indeed this is true on random graphs, but it is possible to construct sparse graphs where SPFA runs in time \( {\displaystyle \Omega (|V|^{2})} \) like the usual Bellman-Ford algorithm[5].
Optimization techniques

The performance of the algorithm is strongly determined by the order in which candidate vertices are used to relax other vertices. In fact, if Q is a priority queue, then the algorithm pretty much resembles Dijkstra's. However, since a priority queue is not used here, two techniques are sometimes employed to improve the quality of the queue, which in turn improves the average-case performance (but not the worst-case performance). Both techniques rearranges the order of elements in } Q so that vertices closer to the source are processed first. Therefore, when implementing these techniques, Q is no longer a first-in, first-out queue, but rather a normal doubly linked list or double-ended queue.

Small Label First (SLF) technique. In line 11, instead of always pushing vertex v {\displaystyle v} v to the end of the queue, we compare \( {\displaystyle d(v)} \) to \( {\displaystyle d{\big (}{\text{front}}(Q){\big )}} \), and insert v to the front of the queue if \( {\displaystyle d(v)} \) is smaller. The pseudo-code for this technique is (after pushing } v to the end of the queue in line 11):

procedure Small-Label-First(G, Q)
    if d(back(Q)) < d(front(Q)) then
        u := pop back of Q
        push u into front of Q

Large Label Last (LLL) technique. After line 11, we update the queue so that the first element is smaller than the average, and any element larger than the average is moved to the end of the queue. The pseudo-code is:

procedure Large-Label-Last(G, Q)
    x := average of d(v) for all v in Q
    while d(front(Q)) > x
        u := pop front of Q
        push u to back of Q

References

About the so-called SPFA algorithm
Moore, Edward F. (1959). "The shortest path through a maze". Proceedings of the International Symposium on the Theory of Switching. Harvard University Press. pp. 285–292.
http://codeforces.com/blog/entry/16221
"Shortest Path Faster Algorithm". wcipeg.
Ke, Yang. "Worst test case for SPFA".

Undergraduate Texts in Mathematics

Graduate Texts in Mathematics

Graduate Studies in Mathematics

Mathematics Encyclopedia

World

Index

Hellenica World - Scientific Library

Retrieved from "http://en.wikipedia.org/"
All text is available under the terms of the GNU Free Documentation License