Dijkstra's Algorithm


Dijkstra's Algorithm is one of the most famous and widely used algorithms in computer science and graph theory. It solves the single-source shortest path problem in a weighted graph. Developed by Edsger W. Dijkstra in 1956, this algorithm finds the shortest path between a starting node and all other nodes in a graph. It works on graphs with non-negative edge weights and is highly efficient for various real-world applications, including routing in networking, navigation systems, and optimization problems.

Problem Definition

Given a weighted, directed graph with a starting node (also known as the source), Dijkstra's Algorithm finds the shortest path from the source node to all other nodes in the graph. It ensures that each node is visited in order of increasing distance from the source.

How Dijkstra's Algorithm Works

The algorithm maintains a set of visited nodes and iteratively explores the nearest unvisited node. The key idea is to use a greedy approach, where at each step, the node with the smallest known distance is selected for exploration. The algorithm updates the distances of the neighboring nodes, and once all nodes are visited, the shortest paths are found.

Steps of Dijkstra's Algorithm:

  1. Initialization:

    • Set the distance to the source node to 0 and the distance to all other nodes to infinity.
    • Mark all nodes as unvisited.
  2. Selection of the Minimum Distance Node:

    • From the set of unvisited nodes, select the node with the smallest tentative distance.
  3. Update Neighboring Nodes:

    • For the selected node, update the distances of its neighboring nodes. The new distance to each neighbor is the current node's distance plus the edge weight between the current node and the neighbor.
  4. Mark the Current Node as Visited:

    • Once the distances are updated, mark the current node as visited. A visited node will not be checked again.
  5. Repeat:

    • Repeat steps 2–4 until all nodes have been visited or the minimum distance for the remaining unvisited nodes is infinity (meaning no path exists).
  6. Result:

    • Once all nodes are visited, the shortest paths from the source node to all other nodes are found.

Dijkstra's Algorithm Example

Consider a graph with the following edges and weights:

     2         3
A --------> B --------> C
|           |           |
| 1         | 4         | 5
|           |           |
v           v           v
D --------> E --------> F
     6         1
  • Start from node A.
  • The shortest paths from A to all other nodes are computed by Dijkstra's Algorithm.

Dijkstra’s Algorithm Implementation

Let's now look at how to implement Dijkstra's Algorithm in both Python and C++.


Dijkstra's Algorithm in Python

import heapq

def dijkstra(graph, start):
    # Distance table: all nodes initially have infinite distance
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    
    # Priority queue to explore the node with the smallest tentative distance
    priority_queue = [(0, start)]
    
    while priority_queue:
        current_distance, current_node = heapq.heappop(priority_queue)
        
        # If this distance is already greater than the recorded distance, skip it
        if current_distance > distances[current_node]:
            continue
        
        # Explore neighbors
        for neighbor, weight in graph[current_node]:
            distance = current_distance + weight
            
            # If a shorter path to the neighbor is found, update it
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))
    
    return distances

# Sample usage
graph = {
    'A': [('B', 2), ('D', 1)],
    'B': [('C', 3), ('E', 4)],
    'C': [('F', 5)],
    'D': [('E', 6)],
    'E': [('F', 1)],
    'F': []
}

start_node = 'A'
print(f"Shortest distances from {start_node}: {dijkstra(graph, start_node)}")

Explanation:

  • We use a min-heap (priority queue) to always select the node with the smallest distance to explore next.
  • The graph is represented as a dictionary of nodes, where each node points to a list of tuples. Each tuple represents a neighboring node and the edge weight.

This Python implementation efficiently calculates the shortest distances from the source node to all other nodes in the graph.


Dijkstra's Algorithm in C++

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <unordered_map>

using namespace std;

typedef pair<int, int> pii;

void dijkstra(unordered_map<char, vector<pii>>& graph, char start) {
    unordered_map<char, int> distances;
    
    // Initialize distances
    for (auto& pair : graph) {
        distances[pair.first] = INT_MAX;
    }
    distances[start] = 0;

    // Min-heap to pick the node with the smallest distance
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    pq.push({0, start});

    while (!pq.empty()) {
        int current_distance = pq.top().first;
        char current_node = pq.top().second;
        pq.pop();

        // Skip if the current distance is greater than the known shortest distance
        if (current_distance > distances[current_node]) continue;

        // Explore neighbors
        for (auto& neighbor : graph[current_node]) {
            char next_node = neighbor.first;
            int weight = neighbor.second;
            int new_distance = current_distance + weight;

            // Update if a shorter path is found
            if (new_distance < distances[next_node]) {
                distances[next_node] = new_distance;
                pq.push({new_distance, next_node});
            }
        }
    }

    // Print the results
    for (auto& pair : distances) {
        cout << "Distance from " << start << " to " << pair.first << " is " << pair.second << endl;
    }
}

int main() {
    unordered_map<char, vector<pii>> graph;

    graph['A'] = {{'B', 2}, {'D', 1}};
    graph['B'] = {{'C', 3}, {'E', 4}};
    graph['C'] = {{'F', 5}};
    graph['D'] = {{'E', 6}};
    graph['E'] = {{'F', 1}};
    graph['F'] = {};

    char start = 'A';
    dijkstra(graph, start);

    return 0;
}

Explanation:

  • The C++ implementation also uses a priority queue (min-heap) to always select the node with the smallest distance.
  • We use an unordered map to store the graph and the distances for each node.

This C++ code works similarly to the Python version, calculating the shortest path from a start node to all other nodes in the graph.


Performance Analysis of Dijkstra's Algorithm

  • Time Complexity: The time complexity of Dijkstra's Algorithm is O((V + E) log V), where:

    • V is the number of vertices.
    • E is the number of edges.
    • The logarithmic factor comes from the priority queue operations (insertion and extraction).

    If we use an adjacency matrix to represent the graph, the time complexity becomes O(V^2), but using an adjacency list with a priority queue makes it more efficient.

  • Space Complexity: The space complexity is O(V + E), as we need to store the graph and the distances of all vertices.


Applications of Dijkstra's Algorithm

Dijkstra's Algorithm has wide applications in solving real-world problems:

  1. Shortest Path in Maps: Used in GPS systems to find the shortest route between two locations.
  2. Network Routing: Dijkstra’s Algorithm helps routers find the most efficient route for data packets in a network.
  3. Flight Route Optimization: Airlines use it to find the shortest flight routes between airports.
  4. Robotics: For path planning in autonomous robots.
  5. Telecommunications: Used in the design of efficient communication networks.