...

/

Best-First: Dijkstra’s Algorithm

Best-First: Dijkstra’s Algorithm

Learn about Dijkstra’s algorithm and its applications in solving the shortest paths problem.

Proof of tenseness property in Dijkstra’s algorithm

If we replace the FIFO queue in breadth-first search with a priority queue, where the key of a vertex vv is its tentative distance dist(v)dist(v), we obtain an algorithm first published in 1957 by a team of researchers at the Case Institute of Technology led by Michael Leyzorek, in an annual project report for the Combat Development Department of the US Army Electronic Proving Ground. The same algorithm was independently discovered by Edsger Dijkstra in 1956 (but not published until 1959), again by George Minty sometime before 1960, and again by Peter Whiting and John Hillier in 1960. A nearly identical algorithm was also described by George Dantzig in 1958. Although several early sources called it “Minty’s algorithm,” this approach is now universally known as “Dijkstra’s algorithm,” in full accordance with Stigler’s Law. The pseudocode for this algorithm is shown below.

An easy induction proof implies that at all times during the execution of this algorithm, an edge uvu\rightarrow v is tense if, and only if, vertex uu is either in the priority queue or is the vertex most recently ExtractedExtracted from the priority queue. Thus, Dijkstra’s algorithm is an instance of Ford’s general strategy, which implies that it correctly computes shortest paths, provided there are no negative cycles in GG.

The images below show computing shortest paths in a dag by relaxing outgoing edges in topological order. In each iteration, bold edges indicate predecessors, and the bold vertex is about to be scanned.

Algorithm


Dijkstra(s):InitSSSP(s)Insert(s,0)while the priority queue is not emptyuExtractMin()for all edges uvif uv is tenseRelax(uv)if v is in the priority queueDecreaseKey(v,dist(v))elseInsert(v,dist(v))\underline{Dijkstra(s):} \\ \hspace{0.4cm} InitSSSP(s) \\ \hspace{0.4cm} Insert(s, 0) \\ \hspace{0.4cm} while\space the\space priority\space queue\space is\space not\space empty \\ \hspace{1cm} u ← ExtractMin( ) \\ \hspace{1cm} for\space all\space edges\space u\rightarrow v \\ \hspace{1.7cm} if \space u\rightarrow v\space is\space tense \\ \hspace{2.3cm} Relax(u\rightarrow v) \\ \hspace{2.3cm} if\space v\space is\space in\space the\space priority\space queue \\ \hspace{2.7cm} DecreaseKey(v, dist(v)) \\ \hspace{2.3cm} else \\ \hspace{2.7cm} Insert(v, dist(v))


Implementation

Press + to interact
#include <iostream>
#include <vector>
#include <queue>
#include <limits.h>
using namespace std;
// Edge data structure
struct Edge
{
int v; // destination vertex
int weight; // weight of the edge
Edge(int _v, int _w): v(_v), weight(_w) {}
};
// Graph data structure
struct Graph
{
int V; // number of vertices
vector<vector < Edge>> adj; // adjacency list
Graph(int _V): V(_V)
{
adj.resize(V);
}
void addEdge(int u, int v, int w)
{
adj[u].push_back(Edge(v, w));
}
};
// Initialize single source shortest path
void initSSSP(int s, vector<int> &dist)
{
fill(dist.begin(), dist.end(), INT_MAX);
dist[s] = 0;
}
// Dijkstra's algorithm
void dijkstra(int s, Graph &G)
{
priority_queue<pair<int, int>, vector< pair<int, int>>, greater<pair<int, int>>> pq;
vector<int> dist(G.V);
initSSSP(s, dist);
pq.push({ 0, s });
while (!pq.empty())
{
int u = pq.top().second;
pq.pop();
for (auto e: G.adj[u])
{
int v = e.v;
int w = e.weight;
if (dist[u] != INT_MAX && dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
pq.push({ dist[v], v });
}
}
}
// Print the shortest path distances
for (int i = 0; i < G.V; ++i)
{
cout << "Shortest path from " << s << " to " << i << " is " << dist[i] << endl;
}
}
// Driver code
int main()
{
int V = 5;
Graph G(V);
G.addEdge(0, 1, 10);
G.addEdge(0, 2, 5);
G.addEdge(1, 2, 2);
G.addEdge(1, 3, 1);
G.addEdge(2, 1, 3);
G.addEdge(2, 3, 9);
G.addEdge(2, 4, 2);
G.addEdge(3, 4, 4);
G.addEdge(4, 3, 6);
dijkstra(0, G);
return 0;
}

Explanation

  • Line 4: The limits.h header file is included to use INT_MAX.

  • Lines 8–13: The struct Edge has two data members, v, which is the destination vertex, and weight, which is the weight of the edge.

  • Line 12: The constructor uses an initialization list to initialize the member variables, which is a cleaner and more efficient way of initializing member variables compared to using assignment statements in the constructor body. The colon (:) separates the initialization list from the constructor body.

  • Lines 16–23: The constructor takes one parameter V and initializes the adj vector with size V.

  • Lines 25–29: The addEdge function adds this edge to the adjacency list of vertex u.

  • Lines 32–36: The initSSSP function initializes all the distances to INT_MAX, except the distance from the source vertex, which is set to 0.

  • Line 41: The code declares a priority_queue data structure named pq.

  • Line 44: The code initializes the priority queue pq with a pair of values {0, s}, where s is the source vertex.

  • Lines 49–58: The for loop computes the shortest path distances from a given source vertex to all other vertices in the graph. It iterates over all adjacent vertices of the current vertex u, updates the shortest distance to reach each of these adjacent vertices v, if it can be improved by going through u, and adds the updated v with its new shortest distance to the priority queue pq.

  • Lines 72–81: The code initializes a directed graph with 5 vertices and adds weighted edges between them.

No negative edges

Dijkstra’s algorithm is particularly well-behaved when the input graph has no negative-weight edges. In this setting, the algorithm intuitively expands a wavefront outward from the source vertex ss, passing over vertices in increasing order of their distance from ss, similar to breadth-first search. The figure below shows the algorithm in action.

The images below show the first four iterations of Dijkstra’s algorithm on a graph with no negative edges. In each iteration, bold edges indicate predecessors; shaded vertices are in the priority queue; and the bold vertex is about to be scanned. The remaining iterations don’t change the distances or the shortest path tree.

We can derive a self-contained proof of correctness for Dijkstra’s algorithm in this setting by formalizing this wavefront intuition. For each integer ii, let uiu_i denote the vertex returned by the iith call to ExtractMinExtractMin, and let did_i be the value of dist(ui)dist(u_i) just after this ExtractionExtraction. In particular, we have u1=su_1 = s and d1=0d_1 = 0. We can’t assume at this point that the vertices uiu_i ...