Dijkstra's Algorithm

Dijkstra's algorithm is a fundamental algorithm in computer science, used to find the shortest path between two nodes in a graph. It was conceived by Dutch computer scientist Edsger W. Dijkstra in 1956. This algorithm is widely used in various applications, such as network routing protocols, GPS navigation systems, and more.

In this blog, we'll explore the concept of Dijkstra's algorithm, its working, and provide code examples in Python, C++, and Java.

What is Dijkstra's Algorithm?

Dijkstra's algorithm is a greedy algorithm that solves the single-source shortest path problem for a graph with non-negative edge weights. The algorithm maintains a set of vertices whose shortest paths from the source have already been determined. It iteratively selects the vertex with the minimum distance from the source, updates the distances of its adjacent vertices, and repeats the process until all vertices have been processed.


Key Features of Dijkstra's Algorithm

1. Works Only on Graphs with Non-Negative Weights:

  • Dijkstra's algorithm assumes that all edge weights are non-negative. If negative weights are present, the algorithm may produce incorrect results.

2. Greedy Approach:

  • The algorithm always selects the node with the smallest tentative distance, making it a greedy algorithm.

3. Efficient Implementation:

  • It can be implemented using a priority queue (min-heap) for efficient extraction of the node with the smallest distance.

4. Time Complexity:

  • O(V²) when using an adjacency matrix.
  • O((V + E) log V) when using a priority queue (min-heap), where V is the number of vertices and E is the number of edges.

How Does Dijkstra's Algorithm Work?

1. Initialization:

  • Set the distance of the source node to 0 and the distances of all other nodes to infinity.
  • Create a priority queue (min-heap) to store nodes based on their distances.

2. Processing:

  • Extract the node with the minimum distance from the priority queue.
  • For each neighbor of the extracted node, calculate the tentative distance.
  • If the tentative distance is less than the current known distance, update the distance and add the neighbor to the priority queue.

3. Termination:

  • The algorithm terminates when the priority queue is empty, meaning all nodes have been processed.

Applications of Dijkstra's Algorithm

1. Navigation Systems:

  • Used in GPS routing (e.g., Google Maps) to find the shortest path between two locations.

2. Network Routing:

  • Used in computer networks to determine the shortest path for data packets.

3. Pathfinding in AI:

  • Used in games and robotics for pathfinding and obstacle avoidance.

4. Project Management:

  • Used in the Critical Path Method (CPM) to determine the longest path in a project schedule.

Limitations of Dijkstra's Algorithm

1. Fails with Negative Weight Edges:

  • Dijkstra's algorithm does not work correctly if the graph contains negative edge weights. In such cases, algorithms like Bellman-Ford should be used.

2. Not Ideal for Dense Graphs:

  • For dense graphs (where E ≈ V²), Dijkstra's algorithm may not be the most efficient. Algorithms like Floyd-Warshall are better suited.

Code Examples

Python

import heapq

def dijkstra(graph, start):
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    priority_queue = [(0, start)]

    while priority_queue:
        current_distance, current_node = heapq.heappop(priority_queue)

        if current_distance > distances[current_node]:
            continue

        for neighbor, weight in graph[current_node].items():
            distance = current_distance + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))

    return distances

# Example usage
graph = {
    'A': {'B': 1, 'C': 4},
    'B': {'A': 1, 'C': 2, 'D': 5},
    'C': {'A': 4, 'B': 2, 'D': 1},
    'D': {'B': 5, 'C': 1}
}

print(dijkstra(graph, 'A'))

C++

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <limits.h>

using namespace std;

typedef pair<int, int> pii;

void dijkstra(vector<vector<pii>>& graph, int start, vector<int>& distances) {
    priority_queue<pii, vector<pii>, greater<pii>> pq;
    pq.push({0, start});
    distances[start] = 0;

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

        if (current_distance > distances[current_node]) {
            continue;
        }

        for (auto& neighbor : graph[current_node]) {
            int neighbor_node = neighbor.first;
            int weight = neighbor.second;
            int distance = current_distance + weight;

            if (distance < distances[neighbor_node]) {
                distances[neighbor_node] = distance;
                pq.push({distance, neighbor_node});
            }
        }
    }
}

int main() {
    int nodes = 4;
    vector<vector<pii>> graph(nodes);
    graph[0].push_back({1, 1});
    graph[0].push_back({2, 4});
    graph[1].push_back({0, 1});
    graph[1].push_back({2, 2});
    graph[1].push_back({3, 5});
    graph[2].push_back({0, 4});
    graph[2].push_back({1, 2});
    graph[2].push_back({3, 1});
    graph[3].push_back({1, 5});
    graph[3].push_back({2, 1});

    vector<int> distances(nodes, INT_MAX);
    dijkstra(graph, 0, distances);

    for (int i = 0; i < nodes; i++) {
        cout << "Distance from 0 to " << i << " is " << distances[i] << endl;
    }

    return 0;
}

Java

import java.util.*;

public class Dijkstra {
    static class Edge {
        int node, weight;
        Edge(int node, int weight) {
            this.node = node;
            this.weight = weight;
        }
    }

    public static void dijkstra(List<List<Edge>> graph, int start, int[] distances) {
        PriorityQueue<Edge> pq = new PriorityQueue<>(Comparator.comparingInt(e -> e.weight));
        pq.add(new Edge(start, 0));
        distances[start] = 0;

        while (!pq.isEmpty()) {
            Edge current = pq.poll();
            int currentNode = current.node;
            int currentDistance = current.weight;

            if (currentDistance > distances[currentNode]) {
                continue;
            }

            for (Edge neighbor : graph.get(currentNode)) {
                int neighborNode = neighbor.node;
                int weight = neighbor.weight;
                int distance = currentDistance + weight;

                if (distance < distances[neighborNode]) {
                    distances[neighborNode] = distance;
                    pq.add(new Edge(neighborNode, distance));
                }
            }
        }
    }

    public static void main(String[] args) {
        int nodes = 4;
        List<List<Edge>> graph = new ArrayList<>();
        for (int i = 0; i < nodes; i++) {
            graph.add(new ArrayList<>());
        }

        graph.get(0).add(new Edge(1, 1));
        graph.get(0).add(new Edge(2, 4));
        graph.get(1).add(new Edge(0, 1));
        graph.get(1).add(new Edge(2, 2));
        graph.get(1).add(new Edge(3, 5));
        graph.get(2).add(new Edge(0, 4));
        graph.get(2).add(new Edge(1, 2));
        graph.get(2).add(new Edge(3, 1));
        graph.get(3).add(new Edge(1, 5));
        graph.get(3).add(new Edge(2, 1));

        int[] distances = new int[nodes];
        Arrays.fill(distances, Integer.MAX_VALUE);
        dijkstra(graph, 0, distances);

        for (int i = 0; i < nodes; i++) {
            System.out.println("Distance from 0 to " + i + " is " + distances[i]);
        }
    }
}

Dijkstra's Algorithm Interview Questions

Basic Questions

  1. What is Dijkstra’s Algorithm?
  2. How does Dijkstra’s Algorithm work? Explain step by step.
  3. What are the real-world applications of Dijkstra’s Algorithm?
  4. What data structures are used in Dijkstra’s Algorithm?
  5. What is the time complexity of Dijkstra’s Algorithm?
  6. Can Dijkstra’s Algorithm work with negative weights? Why or why not?
  7. What is the difference between Dijkstra’s Algorithm and the Bellman-Ford Algorithm?
  8. What is the difference between Dijkstra’s Algorithm and Floyd-Warshall Algorithm?
  9. What is the difference between Dijkstra’s Algorithm and A* Algorithm?
  10. Can Dijkstra’s Algorithm handle graphs with cycles?

Intermediate Questions

  1. How can we implement Dijkstra’s Algorithm efficiently?
  2. Explain Dijkstra’s Algorithm using an adjacency list vs. adjacency matrix.
  3. Why do we use a priority queue (min-heap) in Dijkstra’s Algorithm?
  4. What happens if we replace the priority queue with a normal queue?
  5. How do we modify Dijkstra’s Algorithm to handle undirected graphs?
  6. What are the limitations of Dijkstra’s Algorithm?
  7. How does Dijkstra’s Algorithm perform on a dense vs. sparse graph?
  8. Can we use Dijkstra’s Algorithm for a directed acyclic graph (DAG)?
  9. How does Dijkstra’s Algorithm behave in a disconnected graph?
  10. What will be the output if we apply Dijkstra’s Algorithm on an unweighted graph?

Advanced Questions

  1. How can we modify Dijkstra’s Algorithm to find the second shortest path?
  2. Can we modify Dijkstra’s Algorithm to find all shortest paths from a source?
  3. How does Fibonacci Heap improve Dijkstra’s Algorithm?
  4. What is the amortized time complexity of Dijkstra’s Algorithm with a Fibonacci Heap?
  5. How does bidirectional Dijkstra’s Algorithm improve performance?
  6. How can Dijkstra’s Algorithm be used in real-time traffic navigation systems?
  7. Explain how Dijkstra’s Algorithm is used in network routing protocols.
  8. How can we modify Dijkstra’s Algorithm to find the longest path in a graph?
  9. What are the memory and space complexities of Dijkstra’s Algorithm?
  10. How does Dijkstra’s Algorithm behave with floating-point weights?

Implementation-Based Questions

  1. Implement Dijkstra’s Algorithm using a priority queue (min-heap).
  2. Implement Dijkstra’s Algorithm without using a priority queue.
  3. Implement Dijkstra’s Algorithm using an adjacency list representation.
  4. Implement Dijkstra’s Algorithm using an adjacency matrix representation.
  5. Implement Dijkstra’s Algorithm for a weighted undirected graph.
  6. Implement Dijkstra’s Algorithm for a weighted directed graph.
  7. Implement a function to return the path from the source to the destination in Dijkstra’s Algorithm.
  8. Modify the implementation to handle multiple sources.
  9. Modify Dijkstra’s Algorithm to return all shortest paths from the source.
  10. Write a program to apply Dijkstra’s Algorithm in a maze/grid traversal problem.

Scenario-Based & Problem-Solving Questions

  1. Given a city map where intersections are nodes and roads are weighted edges, find the shortest path between two intersections.
  2. You are given a list of flight connections with different costs. Find the cheapest way to travel between two cities.
  3. Modify Dijkstra’s Algorithm to account for tolls and road congestion.
  4. How would you optimize Dijkstra’s Algorithm for a GPS-based car navigation system?
  5. Given a set of cities and highways, determine the shortest path between two cities with possible road closures.
  6. How would you use Dijkstra’s Algorithm to determine the optimal delivery route for a logistics company?
  7. You are developing a video game where characters move on a grid. How would you use Dijkstra’s Algorithm to find the shortest path between two points?
  8. A railway network consists of stations and train routes with different travel times. Find the shortest travel time from one station to another.
  9. Given a weighted graph of computer networks, determine the shortest path for data packet transmission.
  10. You are given an underground metro network with different travel times. Find the fastest route between two stations.

Bonus Questions

  1. What is the worst-case scenario for Dijkstra’s Algorithm?
  2. How do different priority queue implementations affect the performance of Dijkstra’s Algorithm?
  3. Explain how Dijkstra’s Algorithm can be parallelized.
  4. Can Dijkstra’s Algorithm be implemented recursively? Why or why not?
  5. How does Dijkstra’s Algorithm handle dynamically changing edge weights?
  6. In which cases is Bellman-Ford preferred over Dijkstra’s Algorithm?
  7. Why does Dijkstra’s Algorithm use a greedy approach?
  8. How does A* Algorithm improve upon Dijkstra’s Algorithm?
  9. What are some real-world optimizations for Dijkstra’s Algorithm in navigation systems?
  10. How does Google Maps or Uber implement shortest path algorithms like Dijkstra’s?

Conclusion

Dijkstra's algorithm is a highly effective method for determining the shortest path in a graph where all edge weights are non-negative. It is widely used in applications like navigation systems, network routing, and AI pathfinding. However, it does have some drawbacks, including its inability to manage negative weights and its reduced efficiency in dense graphs.

By grasping how it operates and applying it in different programming languages, you can utilize this algorithm to tackle a variety of real-world challenges. Happy coding!

DSA roadmap

Complete DSA Free Resources

Join our WhatsApp Channel for more resources