Hello! Welcome to your first lesson in the "Core Graph Algorithms" module.
In the last module, we focused on graph traversal and structural properties, culminating in topological sorting for Directed Acyclic Graphs (DAGs). You learned how to find a linear ordering of tasks based on dependencies. Today, we shift our focus from simple ordering to optimization: finding the most efficient path through a network.
This lesson addresses the learning outcome: Implement Dijkstra's algorithm to find the shortest path in a weighted graph.
Dijkstra's algorithm is a cornerstone of computer science and a perfect example of a greedy algorithm. It's the engine behind many real-world applications, from GPS navigation finding the quickest route to a destination, to network routers forwarding data packets along the fastest links. Mastering this algorithm is a significant step in developing the architectural mindset you're aiming for, as it's fundamental to solving a wide class of optimization problems.
We will cover the core logic of the algorithm, see it in action, discuss its implementation with a crucial data structure—the priority queue—and analyze its performance and limitations.
The Single-Source Shortest Path Problem
Before diving into the algorithm, let's define the problem it solves: the Single-Source Shortest Path (SSSP) problem.
Given:
- A weighted graph, where edges have a numerical value (cost, distance, time, etc.). For now, we'll assume these weights are non-negative.
- A designated source vertex,
s.
The goal is to find the shortest path from the source vertex s to every other vertex in the graph.
To get a formal understanding of the SSSP problem and the terminology we'll be using, the VisuAlgo resource is an excellent starting point.
Single-Source Shortest Paths (Dijkstra/+ve Weighted, BFS/ ...
This page from VisuAlgo provides an interactive introduction to the SSSP problem, its real-world applications, and the data structures typically used to store the results (a distance array and a predecessor array).
Read the first few slides in the e-Lecture mode (by clicking the '→' arrow). Focus on understanding the inputs (weighted graph, source vertex) and outputs (distance array 'D' and predecessor array 'p').
The Core Idea: A Greedy Strategy
Dijkstra's algorithm works by progressively finding the shortest path to the "closest" vertices first. It's a greedy algorithm because at each step, it makes the locally optimal choice: it explores the unvisited vertex that has the smallest known distance from the source.
To do this, the algorithm maintains a few key pieces of information:
- A set of visited vertices whose shortest path from the source is considered final.
- A distance array/map, let's call it
dist, wheredist[v]stores the length of the shortest path currently known from the source to vertexv. Initially,dist[source] = 0and all other distances are set to infinity. - A priority queue containing unvisited vertices, prioritized by their current value in the
distarray. This is the crucial data structure that allows us to efficiently select the "closest" unvisited vertex at each step.
The algorithm proceeds as follows:
- Initialize
distfor all vertices and add the source vertex to the priority queue with a distance of 0. - While the priority queue is not empty:
a. Extract the vertexuwith the minimum distance from the priority queue.
b. Markuas visited. Its shortest path is now considered final.
c. For each neighborvofu, perform a "relaxation" step.
The "Relaxation" Step
Relaxation is the heart of the algorithm. For an edge from u to v with weight w, we check if we've found a shorter path to v by going through u. The logic is simple:
If dist[u] + w < dist[v], it means the path from the source to v via u is shorter than any path to v we've found so far. So, we update dist[v] to this new, shorter distance: dist[v] = dist[u] + w.

Visualizing Dijkstra's Algorithm
Theory is one thing, but seeing the algorithm unfold is the best way to build intuition. The following video provides a clear, step-by-step visualization.
Dijkstra's Shortest Path Algorithm Visually Explained | How it Works | With Examples
The ByteQuest channel has an excellent visual explanation of Dijkstra's algorithm. It shows how the distance table and priority queue evolve as the algorithm progresses.
Watch from the beginning to 08:32. Pay close attention to these three things: How the node with the minimum cost is always chosen from the priority queue. How the 'cost' and 'previous node' columns in the table are updated during the relaxation step. The moment a path is not updated because a shorter one already exists (e.g., at 03:00, reaching node F via B is more expensive than the existing path via A).
From Concept to Code
As a software engineer, you know the gap between a conceptual algorithm and a working implementation. Let's bridge that gap.
A key implementation detail concerns the priority queue. During the relaxation step, if we find a shorter path to a vertex v that's already in the priority queue, we need to update its priority (its distance). This operation is often called decrease-key. However, many standard library priority queue implementations (like Python's heapq or Java's PriorityQueue) don't offer a direct and efficient decrease-key method.
The common and practical solution is a "lazy update" approach:
- Instead of trying to update an existing entry in the priority queue, we simply add a new
(new_distance, vertex)pair. - This means the queue might temporarily contain multiple entries for the same vertex with different distances.
- When we extract an item
(distance, vertex)from the queue, we first check if we have already found a shorter path to that vertex. If the extracteddistanceis greater than the value we have stored in ourdistarray for thatvertex, it's an outdated entry, and we simply ignore it and extract the next item.
This video from NeetCode demonstrates this practical implementation style.
Implement Dijkstra's Algorithm
This video shows a concise, practical implementation of Dijkstra's algorithm in Python, using the 'lazy update' method with a min-heap.
Watch from 01:23 to 05:21. The first part covers setting up the adjacency list, which you are familiar with. The main part (from 02:08) implements the core logic. Notice how he pushes new pairs to the heap without deleting old ones and adds a check to skip outdated entries.
For your reference, here is a resource with full code examples in multiple languages. You can review the language of your choice to solidify your understanding.
This GeeksforGeeks article provides complete, runnable code for Dijkstra's algorithm in several languages, which is great for seeing the full implementation context.
Skim through the code for your preferred language (e.g., Python or JavaScript). Note how the priority queue (or min-heap) is used and how the logic matches what you saw in the videos.
Test your understanding!
Consider the graph below, with A as the source vertex.

Trace the first few steps of Dijkstra's algorithm.
- What is the state of the
distarray and the priority queue after initialization? - Which node is extracted first? What updates are made?
- Which node is extracted second? What updates are made?
Show answer
Let's use the node numbers from the image (1=A, 2=B, etc.), with Node 1 as the source.
-
Initialization:
distarray:[0, ∞, ∞, ∞, ∞, ∞, ∞, ∞]- Priority Queue
pq:[(0, 1)]
-
First Extraction:
- Extract
(0, 1)frompq. - Neighbors of 1 are 2 and 3.
- Relax edge (1, 2) with weight 2:
dist[1] + 2 < dist[2](0+2 < ∞). Updatedist[2] = 2. Push(2, 2)topq. - Relax edge (1, 3) with weight 4:
dist[1] + 4 < dist[3](0+4 < ∞). Updatedist[3] = 4. Push(4, 3)topq. distarray is now:[0, 2, 4, ∞, ∞, ∞, ∞, ∞]pqis now:[(2, 2), (4, 3)]
- Extract
-
Second Extraction:
- Extract
(2, 2)frompq(since 2 is the minimum distance). - Neighbors of 2 are 3, 4, and 5.
- Relax edge (2, 3) with weight 3:
dist[2] + 3 < dist[3](2+3 < 4) is FALSE. No update for node 3. - Relax edge (2, 4) with weight 7:
dist[2] + 7 < dist[4](2+7 < ∞). Updatedist[4] = 9. Push(9, 4)topq. - Relax edge (2, 5) with weight 5:
dist[2] + 5 < dist[5](2+5 < ∞). Updatedist[5] = 7. Push(7, 5)topq. distarray is now:[0, 2, 4, 9, 7, ∞, ∞, ∞]pqis now:[(4, 3), (9, 4), (7, 5)](ordered by distance)
- Extract
Complexity and a Critical Limitation
As requested, let's analyze the complexity. The efficiency of Dijkstra's hinges on the priority queue.
-
Time Complexity:
- We extract each of the vertices from the priority queue once. Each extraction (
extract-min) costs . Total: . - In the worst case, we might relax every edge. Each relaxation could lead to an insertion into the priority queue (
pushordecrease-key), which costs . Total for all edges: . - Combining these gives a total time complexity of , which is often written as . For connected graphs, is at least , so this is commonly simplified to .
- We extract each of the vertices from the priority queue once. Each extraction (
-
Space Complexity:
- We need space for the
distandpredecessorarrays, which is . - The adjacency list representation of the graph takes space.
- The priority queue can, in the worst case (with the lazy update method), hold an entry for each edge, taking space.
- We need space for the
Limitation: Negative Edge Weights
Dijkstra's greedy approach relies on a critical assumption: all edge weights are non-negative. This guarantees that once a vertex is extracted from the priority queue and marked as visited, we have found its true shortest path.
If a graph has a negative edge, this assumption breaks. A path that currently looks longer might eventually lead to a negative edge, making it shorter in the end. Dijkstra's algorithm, having already finalized a "shorter" path greedily, would miss this.
This GeeksforGeeks section and the VisuAlgo page explain precisely why the greedy choice fails with negative weights.
Read the section 'Why Does Not Work with Negative Weights'. This is a crucial piece of knowledge for any architect choosing an algorithm.
Conclusion
You have now learned one of the most important graph algorithms in computer science. Dijkstra's algorithm provides an efficient way to solve the single-source shortest path problem, a task that underlies many modern technologies.
Key Takeaways:
- Dijkstra's algorithm finds the shortest path from a source to all other nodes in a non-negatively weighted graph.
- It uses a greedy strategy, always exploring the unvisited node with the smallest known distance.
- A priority queue is essential for an efficient implementation, with a time complexity of .
- The "lazy update" method is a common way to implement Dijkstra's without needing a complex
decrease-keyoperation in the priority queue. - The algorithm's greedy nature is its weakness: it fails on graphs with negative edge weights.
Preview of the Next Lesson:
The limitation we just discussed sets the stage perfectly for our next topic. We will explore the Bellman-Ford algorithm. While slower than Dijkstra's, it is more robust as it can handle graphs with negative edge weights and has the added ability to detect negative-weight cycles, a scenario where the "shortest path" is undefined.