13.9 Dijkstra’s shortest-path algorithm
We will now present an algorithm to solve the single-source shortest paths problem. Given vertex in Graph , find a shortest path from to every other vertex in . We might want only the shortest path between two vertices, and . However in the worst case, finding the shortest path from to requires us to find the shortest paths from to every other vertex as well. So there is no better algorithm (in the worst case) for finding the shortest path to a single vertex than to find shortest paths to all vertices. The algorithm described here will only compute the distance to every such vertex, rather than recording the actual path. Recording the path requires only simple modifications to the algorithm.
As mentioned in the previous section, the shortest path between two vertices can be found using a simple breadth-first search, for unweighted graphs (or whenever all edges have the same cost). However, when weights are added, BFS will not give the correct answer.
One approach to solving this problem when the edges have differing weights might be to process the vertices in a fixed order. Label the vertices to , with . When processing vertex , we take the edge connecting and . When processing , we consider the shortest distance from to and compare that to the shortest distance from to to . When processing vertex , we consider the shortest path for vertices through that have already been processed. Unfortunately, the true shortest path to might go through vertex for . Such a path will not be considered by this algorithm. However, the problem would not occur if we process the vertices in order of distance from . Assume that we have processed in order of distance from to the first vertices that are closest to ; call this set of vertices . We are now about to process the th closest vertex; call it .
A shortest path from to must have its next-to-last vertex in . Thus,
In other words, the shortest path from to is the minimum over all paths that go from to , then have an edge from to , where is some vertex in .
This solution is usually referred to as Dijkstra’s algorithm. It works by maintaining a distance estimate for all vertices in . The elements of are initialised to the value (positive infinity). Vertices are processed in order of distance from . Whenever a vertex is processed, is updated for every neighbour of .
Dijkstra’s algorithm is quite similar to Prim’s algorithm for finding the minimum spanning tree (Section 13.6). The primary difference is that we are seeking, not the next vertex which is closest to the MST, but rather the the next vertex which is closest to the start vertex. Thus the following lines in Prim’s algorithm:
if e.weight < distances.get(e.end):
put(e.end, e.weight) distances.
are replaced with the following lines in Dijkstra’s algorithm:
= distances.get(v) + e.weight
dist if dist < distances.get(e.end):
put(e.end, dist) distances.
Here is an implementation for Dijkstra’s algorithm. At the end,
distances
will contain the shortest distance from the start
to each reachable vertex.
function dijkstra(graph, start):
= new Set() of vertices
visited = new Map() of vertices to their distance from start
distances put(start, 0) // The distance from start to start is 0
distances.
repeat graph.size times:
= minVertex(graph, distances, visited) // Find next-closest vertex
v add(v)
visited.for each e in graph.outgoingEdges(v):
= e.end
w = distances.get(v) + e.weight
dist if e.end not in distances or dist < distances.get(e.end):
// If the new distance to the endpoint is shorter...
put(e.end, dist) // ...update the endpoint with the new distance
distances.return distances
Here is a visualisation of Dijkstra’s algorithm.
Finding the minimum vertex
Just as for Prim’s algorithm, there are two ways of finding the next-closest vertex. We can scan through all vertices searching for the minimum value (note that this code is exactly the same as for Prim’s algorithm):
function minVertex(graph, distances, visited):
= null
minV for each v in graph.vertices():
if v not in visited:
if minV is null or distances.get(v) < distances.get(minV):
= v
minV return minV
And just as for Prim’s algorithm, this will give us a complexity which is quadratic in the number of vertices, .
13.9.1 Using a priority queue
An alternative approach is to store unprocessed vertices in a minimum priority queue, such as a binary heap, ordered by their distance from the processed vertices. The next-closest vertex can be found in the heap in time. Every time we modify , we could reorder in the heap by deleting and reinserting it. This is an example of a priority queue with priority update. However, to implement true priority updating, we would need to store with each vertex its position within the heap so that we can remove its old distances whenever it is updated by processing new edges.
A simpler approach is to add the new (always smaller) distance value for a given vertex as a new record in the heap. The smallest value for a given vertex currently in the heap will be found first, and greater distance values found later will be ignored because the vertex will already be marked as visited. The only disadvantage to repeatedly inserting distance values in this way is that it will raise the number of elements in the heap from to in the worst case. But in practice this only adds a slight increase to the depth of the heap. The time complexity is , because for each edge that we process we must reorder the heap.
Here is the implementation for Dijkstra’s algorithm using a priority queue.
function dijkstraPQ(graph, start):
= new Set() of vertices
visited = new Map() of vertices to their distance from start
distances = new PriorityQueue() of vertices ordered by their distance from start
agenda // The distance from start to start is 0:
put(start, 0)
distances.add(start) with priority 0
agenda.
while not agenda.isEmpty():
= agenda.removeMin()
v if v not in visited:
add(v)
visited.for each e in graph.outgoingEdges():
= distances.get(v) + e.weight
dist if w not in distances or dist < distances.get(e.end):
// If the new distance to the endpoint is shorter,
// update the endpoint with the new distance, and add it to the agenda
put(e.end, dist)
distances.add(e.end) with priority dist
agenda.return distances
Which version is the best?
Using minVertex
to scan the vertex list for the minimum
value is more efficient when the graph is dense, that is, when
approaches
.
Using a priority is more efficient when the graph is sparse because its
cost is
.
When the graph is dense, this cost can become as great as
.
In practice, most graphs are sparse so unless you know that the graph is dense you should use the priority queue version.
Now you can practice using Dijkstra’s algorithm.