Dijkstra's Algorithm
Overview
Now that we have a very solid understanding of Graphs, we can dive into path finding.
Path finding in graphs (and in real life) simply means finding the optimal/shortest path from point A to point B or from point A to multiple or all different points.
Dijkstra's Algorithm is an incredibly famous and useful algorithm for finding shortest path in Graphs. Not only is it famous but the name 'Dijkstra' somehow sounds sophisticated (I felt super smart when I first learned it) and unlike the spelling of the word, the pronounciation is super memorable. So it would be a shame to not mention where that name comes from!
The Dijkstra's algorithm is named after the Dutch computer scientist Edsger Dijkstra, originated in 1956 when he was in the University of Amsterdam (Netherlands). His goal was to find the shortest path in a weighted graph, with applications in transportation and communication networks.
In 1959, Dijkstra published the algorithm in "A Note on Two Problems in Connexion with Graphs". He implemented it on the Electrologica X1 computer in 1960, demonstrating its practicality. The algorithm gained recognition after his presentation at the 1961 International Conference on Information Processing in Paris.
Obviously, he knew what he was doing and the algorithm found its path to glory. Since then, it has become a fundamental algorithm in computer science, influencing network routing and transportation planning, maps and so on.
It has different variations as it can be used to find either the minimum path between 2 nodes or from a single source node to all others.
That being said, I'm sure that you are already impatient to get straight to the explanation of how Dijkstra's algorithm actually works!
First of all and as always - try to watch the animation carefully when you consider the following:
- The graph in the animation is a simple weighted graph. The grey numbers on the edges are the weights.
- Node 'A' is chosen as the source node so the minimum path to it is 0, of course.
- Red color marks already visited nodes.
- The color purple marks the currently explored nodes and the traversal to its neighbours.
- The green popping green numbers on each nodes are the 'current' minimum path value to that node.
- The final green color for the nodes and arrows represent the constructed shortest path from node A to node F
With the graph knowledge you have, I'm sure that you already have a very good idea how the algorithm works. But let's now describe each of its steps.
- Initial step - all nodes are marked as unvisited
- Selection - select the next node with smallest assigned distance so far (In the first step it's the source node with '0' distance).
This step is generally the reason why Dijkstra is considered to be a greedy algorithm. If, for the next selection, there are few options available, the algorithm will always take the most optimal decision (the one with the shortest path available). - Explore and Update - explore the neighbours of the selected node and calculate the culumative distance to them from the currently selected node.
- If they already have an assigned distance (not the initial infinity), compare the newly calculated distance and assign the shortest of the two. For example - look at the assigned distance to 'F' node in the animation. First we assign it '8' but then we find a shorter path, so we re-assign it with '6' and we are sure it's the shortest possible path so far.
- Otherwise, replace the infinity with the newly calculated distance. - Mark as visited - after we have finished the neighbours exploration, we mark the current node as visited (red color in animation). Visited nodes will not be selected again!
- Continue and repeat - If the destination or all nodes have been already visited, stop the algorithm. Otherwise, go to step 2 to select the next node with the smallest assigned distance.
- Path reconstruction - Finally, after the algorithm has finished, we can reconstruct the shortest path between the desired nodes.
Dijkstra's algorithm can be used for directed as well as undirected graphs. But it cannot be used for graphs with negative weighted edges. This is because Dijkstra's algorithm 'presumes' that all edges have positive weights - this makes it faster compared to algorithms that would have to take into consideration negative weights too.
Generally, the algorithm provides a time complexity of O(V^2) where 'V' is simply the number of nodes. But it's important to know that there are many different implementations of the algorithm and that subject alone is incredibly vast.
For the following code examples, we've picked the easiest to understand and most illustrative example rather than the most optimized. For instance, the selection of next node could be optimized by using a min-priority queue (which internally uses a heap). In this way, we would have a quick and easy access to the next unvisited node with shortest distance (remember how easy we can access min/max values from Heaps?) and it will result in a much better time complexity of O(V + ElogV)
Code
def dijkstra(graph, start):
#Initialize - start node should be assgined '0' and all other nodes - infinity
distances = {node: float('infinity') for node in graph}
distances[start] = 0
visited = set()
while len(visited) < len(graph):
#For the next selection - find the unvisited node with the smallest cumulative distance so far
current_node = min((node for node in graph if node not in visited), key=lambda x: distances[x])
#Then update the distance for the neighbours of the currently selection node
for neighbor, weight in graph[current_node].items():
distance = distances[current_node] + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
visited.add(current_node)
return distances
graph = {
'A': {'B': 1, 'C': 2},
'B': {'A': 1, 'C': 3, 'D': 3, 'E': 4},
'C': {'A': 2, 'B': 3, 'E': 5},
'D': {'B': 3, 'E': 3, 'F': 4},
'E': {'C': 5, 'B': 4, 'D': 3, 'F': 1},
'F': {'D': 4, 'E': 1},
}
start_node = 'A'
result = dijkstra(graph, start_node)
print(f"Shortest distances from node {start_node}: {result}")
function dijkstra(graph, start) {
const distances = {};
const visited = new Set();
// Initialize - start node should be assgined '0' and all other nodes - infinity
for (const node in graph) {
distances[node] = node === start ? 0 : Infinity;
}
while (visited.size < Object.keys(graph).length) {
let current = null;
// For the next selection - find the unvisited node with the smallest cumulative distance so far
for (const node in graph) {
if (!visited.has(node) && (current === null || distances[node] < distances[current])) {
current = node;
}
}
// Then update the distance for the neighbours of the currently selection node
for (const neighbor in graph[current]) {
const distance = distances[current] + graph[current][neighbor];
if (distance < distances[neighbor]) {
distances[neighbor] = distance;
}
}
visited.add(current);
}
return distances;
}
// Example usage:
const graph = {
'A': {'B': 1, 'C': 2},
'B': {'A': 1, 'C': 3, 'D': 3, 'E': 4},
'C': {'A': 2, 'B': 3, 'E': 5},
'D': {'B': 3, 'E': 3, 'F': 4},
'E': {'C': 5, 'B': 4, 'D': 3, 'F': 1},
'F': {'D': 4, 'E': 1},
};
const startNode = 'A';
const result = dijkstra(graph, startNode);
console.log(`Shortest distances from node ${startNode}:`, result);
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DijkstraAlgorithm {
public static Map<String, Integer> dijkstra(Map<String, Map<String, Integer>> graph, String start) {
Map<String, Integer> distances = new HashMap<>();
Set<String> visited = new HashSet<>();
// Initialize - start node should be assgined '0' and all other nodes - infinity
for (String node : graph.keySet()) {
distances.put(node, node.equals(start) ? 0 : Integer.MAX_VALUE);
}
while (visited.size() < graph.size()) {
String current = null;
// For the next selection - find the unvisited node with the smallest cumulative distance so far
for (String node : graph.keySet()) {
if (!visited.contains(node) && (current == null || distances.get(node) < distances.get(current))) {
current = node;
}
}
// Then update the distance for the neighbours of the currently selection node
for (Map.Entry<String, Integer> entry : graph.get(current).entrySet()) {
String neighbor = entry.getKey();
int weight = entry.getValue();
int distance = distances.get(current) + weight;
if (distance < distances.get(neighbor)) {
distances.put(neighbor, distance);
}
}
visited.add(current);
}
return distances;
}
public static void main(String[] args) {
Map<String, Map<String, Integer>> graph = new HashMap<>();
graph.put("A", Map.of("B", 1, "C", 2));
graph.put("B", Map.of("A", 1, "C", 3, "D", 3, "E", 4));
graph.put("C", Map.of("A", 2, "B", 3, "E", 5));
graph.put("D", Map.of("B", 3, "E", 3, "F", 4));
graph.put("E", Map.of("C", 5, "B", 4, "D", 3, "F", 1));
graph.put("F", Map.of("D", 4, "E", 1));
String startNode = "A";
Map<String, Integer> result = dijkstra(graph, startNode);
System.out.println("Shortest distances from node " + startNode + ": " + result);
}
}
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MAX_NODES 6
void dijkstra(int graph[MAX_NODES][MAX_NODES], int start, int distances[MAX_NODES]) {
int visited[MAX_NODES] = {0};
// Initialize - start node should be assgined '0' and all other nodes - infinity
for (int i = 0; i < MAX_NODES; i++) {
distances[i] = (i == start) ? 0 : INT_MAX;
}
for (int count = 0; count < MAX_NODES - 1; count++) {
// For the next selection - find the unvisited node with the smallest cumulative distance so far
int minDistance = INT_MAX, minIndex;
for (int i = 0; i < MAX_NODES; i++) {
if (!visited[i] && distances[i] < minDistance) {
minDistance = distances[i];
minIndex = i;
}
}
visited[minIndex] = 1;
// Then update the distance for the neighbours of the currently selection node
for (int i = 0; i < MAX_NODES; i++) {
if (!visited[i] && graph[minIndex][i] && (distances[minIndex] != INT_MAX) &&
(distances[minIndex] + graph[minIndex][i] < distances[i])) {
distances[i] = distances[minIndex] + graph[minIndex][i];
}
}
}
}
int main() {
//
int graph[MAX_NODES][MAX_NODES] = {
// A, B, C, D, E, F
{0, 1, 2, 0, 0, 0}, // A
{1, 0, 3, 3, 4, 0}, // B
{2, 3, 0, 0, 5, 0}, // C
{0, 3, 0, 0, 3, 4}, // D
{0, 4, 5, 3, 0, 1}, // E
{0, 0, 0, 4, 1, 0} // F
};
int startNode = 0;
int distances[MAX_NODES];
dijkstra(graph, startNode, distances);
printf("Shortest distances from node %d:\n", startNode);
for (int i = 0; i < MAX_NODES; i++) {
printf("To node %d: %d\n", i, distances[i]);
}
return 0;
}
<?php
function dijkstra($graph, $start)
{
$distances = [];
$visited = [];
// Initialize - start node should be assgined '0' and all other nodes - infinity
foreach ($graph as $node => $neighbors) {
$distances[$node] = ($node === $start) ? 0 : INF;
$visited[$node] = false;
}
while (count(array_filter($visited)) < count($graph)) {
$current = null;
// For the next selection - find the unvisited node with the smallest cumulative distance so far
foreach ($graph as $node => $neighbors) {
if (!$visited[$node] && ($current === null || $distances[$node] < $distances[$current])) {
$current = $node;
}
}
// Then update the distance for the neighbours of the currently selection node
foreach ($graph[$current] as $neighbor => $weight) {
$distance = $distances[$current] + $weight;
if ($distance < $distances[$neighbor]) {
$distances[$neighbor] = $distance;
}
}
$visited[$current] = true;
}
return $distances;
}
$graph = [
'A' => ['B' => 1, 'C' => 2],
'B' => ['A' => 1, 'C' => 3, 'D' => 3, 'E' => 4],
'C' => ['A' => 2, 'B' => 3, 'E' => 5],
'D' => ['B' => 3, 'E' => 3, 'F' => 4],
'E' => ['C' => 5, 'B' => 4, 'D' => 3, 'F' => 1],
'F' => ['D' => 4, 'E' => 1],
];
$startNode = 'A';
$result = dijkstra($graph, $startNode);
echo "Shortest distances from node $startNode:\n";
foreach ($result as $node => $distance) {
echo "To node $node: $distance\n";
}