Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 56

Dijkstra's Algorithm: Finding the Shortest Path

Recommended resources

Hello! Welcome back to your study of graph algorithms.

In our last lesson, we saw how to apply traversal algorithms like BFS and DFS to implicit graphs, such as 2D grids, to solve problems like "Number of Islands". A key takeaway was that BFS, by its nature, finds the shortest path in an unweighted graph, where "shortest" means the fewest number of edges.

But what if the path has a cost? What if traveling between two cities has a toll, or sending data between two servers has a latency? This is where weighted graphs come in.

Weighted Graph with Shortest Paths
CAPTION: This image shows a graph where the shortest path in terms of the number of edges (1 -> 6 -> 7) is different from the shortest path in terms of total edge weight (1 -> 2 -> 3 -> 4 -> 7). Dijkstra's algorithm is designed to find the latter.

Today, your goal is to implement Dijkstra's algorithm to find the shortest path in a weighted graph. This is one of the most fundamental and frequently asked-about algorithms in technical interviews, with applications from network routing to logistics.

1. The Core Idea: A Greedy Approach

Dijkstra's algorithm is a greedy algorithm. At each step, it makes a locally optimal choice: it always visits the "closest" unvisited node from the source. This greedy strategy works because all edge weights are non-negative, guaranteeing that once we finalize the shortest path to a node, we'll never find a shorter one later.

The algorithm maintains a set of tentative distances from the source to every other node. Initially, the source's distance is 0, and all others are infinity. The core operation is called relaxation:

  1. Select the unvisited node u with the smallest known distance from the source.
  2. For each neighbor v of u, check if the path through u is shorter than the currently known path to v.
  3. If distance(source, u) + weight(u, v) < distance(source, v), we update distance(source, v).

To see this process in action, let's watch a classic conceptual explanation.

3.6 Dijkstra Algorithm - Single Source Shortest Path - Greedy Method

This video from Abdul Bari provides an excellent, intuitive walkthrough of Dijkstra's algorithm. It clearly explains the greedy nature, the relaxation step, and works through an example manually.

Watch from the beginning to 07:42. Focus on understanding: The single-source shortest path problem. The concept of 'relaxation'. The step-by-step process of selecting the minimum-distance vertex and updating its neighbors.

As the video explains, a straightforward implementation of this logic involves scanning all nodes at each step to find the one with the minimum distance. This leads to a time complexity of , where is the number of vertices. For your interviews, you'll be expected to know a more efficient approach.


2. An Efficient Implementation with a Priority Queue

To optimize the process of finding the unvisited node with the minimum distance, we can use a priority queue (or a min-heap). Your experience with Python's heapq module will be useful here.

Here's how the data structures work together:

  • A distance array (or dictionary), dist, stores the shortest distance from the source to every other node found so far. It's initialized to infinity for all nodes except the source (which is 0).
  • A priority queue, pq, stores (distance, node) tuples. It allows us to fetch the node with the smallest current distance in time, which is much better than the scan of the naive approach.

This video from "take U forward" demonstrates this efficient, heap-based implementation, which is the standard for modern applications and interviews.

G-32. Dijkstra's Algorithm - Using Priority Queue - C++ and Java - Part 1

This video walks through the algorithm using a priority queue and a distance array, showing how they are updated at each step. Although the code is in C++/Java, the logic is directly applicable to Python.

Watch from 04:49 to 14:22. Pay close attention to how the priority queue always provides the next node to visit and how the distance array is updated only when a shorter path is found.

With this optimized approach, the time complexity improves significantly to , where is the number of edges. This is because we process each edge once, and each push/pop operation on the heap takes time.


3. Python Implementation Template

Now, let's translate this logic into a clean Python template, which is exactly what you'd want to have ready for an interview. The following reading provides an excellent, reusable code snippet and a step-by-step breakdown.

Dijkstra’s Algorithm — Templates and Problem Solving

This article from 'CoderRaj07' on Medium provides a fantastic, interview-ready Python template for Dijkstra's algorithm using an adjacency list and heapq.

Please read the following sections from the article: 'Dijkstra Template — Adjacency List': Study the main Python function. 'Step-by-step explanation': Read this to connect each line of the code back to the algorithm's logic. 'Why heap is used': This section reinforces the performance benefits.

Here is the core logic from the article, which you should aim to understand deeply:

import heapq

def dijkstra_adj_list(n, graph, start):
    # 1. Initialize distances and the priority queue
    dist = [float('inf')] * n
    dist[start] = 0
    pq = [(0, start)]  # (distance, node)

    while pq:
        # 2. Get the node with the smallest distance
        d, u = heapq.heappop(pq)

        # 3. Skip if we've already found a shorter path to u
        if d > dist[u]:
            continue

        # 4. Relax all neighbors of u
        for v, w in graph[u]: # v = neighbor, w = weight
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                heapq.heappush(pq, (dist[v], v))
    
    return dist

The line if d > dist[u]: continue is a crucial optimization. Since a priority queue can contain multiple entries for the same node with different (outdated) distances, this check ensures we only process a node using its shortest path found so far.

Test your understanding!

Imagine you are solving a "shortest path" problem on a standard 2D grid where you can move up, down, left, or right. Each move has a 'cost' defined by the value in the cell you are moving into. How would you adapt the graph representation and the relaxation loop to apply Dijkstra's algorithm?

Show answer

You wouldn't pre-build an adjacency list. Instead, you'd generate neighbors on the fly, just like we did for BFS/DFS on grids.

  1. Graph Representation: The grid itself is the implicit graph.
  2. Relaxation Loop: Instead of for v, w in graph[u], you would have a loop over the four directions (e.g., directions = [(0,1), (1,0), (0,-1), (-1,0)]). For each direction, you'd calculate the neighbor's coordinates (nr, nc).
    • v becomes the neighbor (nr, nc).
    • w (the weight) would be grid[nr][nc].
    • The check would be: if dist[r][c] + grid[nr][nc] < dist[nr][nc], where dist is now a 2D array.

The article "Dijkstra’s Algorithm — Templates and Problem Solving" (LINK) you just reviewed provides a template for this exact scenario under the "Dijkstra Template — 2D Grid" section if you want to see a full implementation.


4. A Critical Limitation: Negative Weights

Dijkstra's greedy approach relies on a key assumption: adding an edge never makes a path shorter (since weights are non-negative). If a graph has a negative edge weight, this assumption breaks. The algorithm might prematurely finalize a path to a node, only to later discover a "cheaper" path that uses a negative edge.

3.6 Dijkstra Algorithm - Single Source Shortest Path - Greedy Method

Let's return to the Abdul Bari video, which has an excellent, clear example of exactly why Dijkstra's fails with negative weights.

Watch the section on the drawback of Dijkstra's algorithm from 13:39 to 18:18. The presenter shows how making one edge negative can cause the algorithm to produce an incorrect result.

For graphs with negative weights, other algorithms like the Bellman-Ford algorithm must be used. We won't cover Bellman-Ford in this lesson, but it's important to know when Dijkstra's is not the right tool for the job.


5. Reconstructing the Shortest Path

Dijkstra's algorithm gives you the length of the shortest path, but often you need the path itself. This is a common follow-up question in interviews. You can find the path by storing the "predecessor" of each node—the node that came just before it on the shortest path.

You can modify the algorithm to do this:

  1. Create a predecessors dictionary, initialized to None.
  2. Inside the relaxation step, when you update dist[v], also update predecessors[v] = u.
  3. After the algorithm finishes, you can backtrack from the target node to the source using the predecessors dictionary to reconstruct the path.

Implementing the Dijkstra Algorithm in Python: A Step-by-Step ...

This DataCamp article provides a clear explanation and Python code for reconstructing the path using a predecessors dictionary.

Read the section titled '5. Constructing the shortest path'. Focus on the logic of backtracking from the target node using the predecessors dictionary.


Conclusion

You have now mastered one of the most important algorithms for your interview toolkit. Dijkstra's algorithm is the definitive method for finding the shortest path in graphs with non-negative weights, a problem that appears in many forms.

Key Takeaways:

  • Dijkstra's Algorithm finds the single-source shortest path in a weighted graph with non-negative edge weights.
  • It is a greedy algorithm that iteratively finalizes the shortest path to the closest nodes.
  • The efficient interview-standard implementation uses a priority queue (min-heap) to achieve a time complexity of .
  • The core steps are: initialize distances, loop while the priority queue is not empty, pop the minimum distance node, and relax its neighbors.
  • Limitation: Dijkstra's fails if the graph contains negative edge weights.
  • The actual path can be reconstructed by storing predecessors during the relaxation step.

Preview of the Next Lesson:

Now that we have covered the fundamental graph traversal and shortest path algorithms (BFS, DFS, and Dijkstra's), we will move on to identifying more advanced graph properties. In the next lesson, you will learn how to detect cycles in both undirected and directed graphs using DFS. This is a crucial skill for a wide range of problems, from checking for deadlocks in systems to validating dependency graphs (like course prerequisites).