DISCRETE MATH • GRAPH THEORY

Shortest path algorithms (BFS/Dijkstra conceptually)

How BFS and Dijkstra's algorithm efficiently determine optimal paths through graphs, powering navigation, networking, and beyond.

Historical Context & Motivation

The problem of finding the shortest route between two locations is among the oldest computational challenges in mathematics. Long before digital computers, mathematicians and engineers grappled with route optimization in transportation networks, telegraph systems, and military logistics. The formalization of graph theory by Leonhard Euler in 1736—through his celebrated analysis of the Königsberg bridge problem—laid the conceptual groundwork for modeling such problems as traversals over vertices and edges. Yet it was not until the mid-twentieth century, with the advent of electronic computing, that efficient algorithms for shortest paths were developed and analyzed rigorously.

1736
Euler and the Königsberg Bridges
Leonhard Euler formalized graph theory by proving no walk could cross all seven bridges of Königsberg exactly once, introducing the abstraction of vertices and edges that underpins all shortest path reasoning.
1945
BFS Formalized by Moore
Edward F. Moore described breadth-first search as a systematic method for exploring graphs layer by layer, originally motivated by finding shortest paths in unweighted maze-like networks.
1956
Dijkstra's Algorithm Conceived
Edsger W. Dijkstra, while contemplating how to demonstrate the capabilities of the ARMAC computer, devised his shortest path algorithm in roughly twenty minutes at a café in Amsterdam. He published it in 1959.
1984
Priority Queue Optimizations
Fredman and Tarjan introduced Fibonacci heaps, reducing Dijkstra's time complexity to O(|E| + |V| log |V|) and sparking a rich line of research on efficient priority queue implementations for graph algorithms.
2000s
Modern Navigation & Networking
Shortest path algorithms became the backbone of GPS navigation systems, internet routing protocols (OSPF, IS-IS), and social network analysis, processing graphs with billions of nodes in real time.

The central question these algorithms address is deceptively simple: given a graph G = (V, E) with a designated source vertex, what is the minimum-cost path to every other reachable vertex? The answer depends critically on whether the graph's edges carry uniform weights (or no weights at all) versus non-negative variable weights—a distinction that separates BFS from Dijkstra's algorithm and shapes their respective complexities and use cases.

Core Principles & Definitions

Before diving into the algorithms themselves, it is essential to establish several foundational concepts from graph theory. A graph G = (V, E) consists of a set V of vertices (or nodes) and a set E of edges connecting pairs of vertices. A weighted graph augments each edge (u, v) ∈ E with a numerical value w(u, v) representing cost, distance, or time. Both BFS and Dijkstra's algorithm solve the single-source shortest path (SSSP) problem, but they differ in the assumptions they require about edge weights and the data structures they employ.

1

Shortest Path

A path from vertex s to vertex t whose total edge weight (or edge count, in unweighted graphs) is minimized over all s–t paths. When multiple shortest paths exist, we typically seek any one of them.
2

Relaxation

The fundamental operation shared by shortest path algorithms: if reaching vertex v through u yields a shorter path than the current best, update the distance estimate d(v) ← d(u) + w(u, v). Correctness hinges on performing relaxations in the right order.
3

Greedy Strategy

Dijkstra's algorithm is a greedy algorithm: at each step it permanently finalizes the unvisited vertex with the smallest tentative distance. This greedy choice is provably optimal when all edge weights are non-negative.
4

Layer-by-Layer Exploration

BFS explores all vertices at hop-distance k before examining any vertex at distance k + 1. This level-order traversal guarantees shortest paths in unweighted (or unit-weight) graphs without needing a priority queue.
5

Predecessor Tracking

Both algorithms maintain a predecessor array π such that π(v) records the vertex immediately before v on the shortest path from the source. The actual path is reconstructed by backtracking through π from the target to the source.
KEY TAKEAWAY
Think of relaxation like updating a GPS recalculation. When your navigation app discovers that an alternative route through a particular intersection yields a shorter travel time, it overwrites the previous estimate and reroutes you accordingly. BFS performs this update implicitly (each newly discovered vertex is guaranteed optimal at the moment of discovery), while Dijkstra's algorithm performs explicit relaxation, always selecting the next vertex whose distance estimate is globally smallest—analogous to a GPS that always expands the frontier of exploration from the closest point first.

Visual Explanation — BFS Layer Expansion

The following diagram illustrates how breadth-first search propagates outward from a source vertex S in an unweighted graph. Vertices are colored according to the layer (hop-distance) at which they are first discovered. Because BFS exhausts every vertex at distance k before processing any vertex at distance k + 1, the first time a vertex is dequeued, its distance is already optimal. The FIFO queue guarantees this ordering: vertices enter the queue in the exact order of their distance from S, and since all edges have unit weight, no later discovery can improve upon the initial one.

BFS discovers vertices in strict layer order. The source S (cyan, d = 0) is dequeued first; its neighbors A and B (violet, d = 1) are enqueued next. Only after both are processed do the pink layer-2 vertices C, D, E, F enter the queue, followed by the amber layer-3 vertices G and H.

Observe that every vertex within a given layer has the same shortest-path distance from S. This property is precisely what makes BFS correct for unweighted graphs: the FIFO discipline of the queue ensures that vertices are finalized in non-decreasing order of their distance. No relaxation step is needed because the first time a vertex is reached, the path used to reach it is already a shortest path. This invariant breaks, however, when edges carry differing weights—a scenario that motivates Dijkstra's algorithm.

Mathematical Framework

Both BFS and Dijkstra's algorithm maintain a distance estimate d(v) for every vertex v ∈ V. Initially, d(s) = 0 for the source s, and d(v) = ∞ for all other vertices. The algorithms progressively tighten these estimates via the relaxation operation until each d(v) equals the true shortest-path distance δ(s, v). We formalize the key operations and complexity results below.

RELAXATION OPERATION
if d(u) + w(u, v) < d(v) then d(v) ← d(u) + w(u, v), π(v) ← u
d(v) = current shortest-distance estimate for vertex v; w(u, v) = weight of edge (u, v); π(v) = predecessor of v on the shortest path.
BFS TIME COMPLEXITY
T_BFS = O(|V| + |E|)
|V| = number of vertices; |E| = number of edges. Each vertex is enqueued and dequeued at most once, and each edge is examined at most once (twice for undirected graphs), yielding linear time.
DIJKSTRA TIME COMPLEXITY (BINARY HEAP)
T_Dijkstra = O((|V| + |E|) log |V|)
Each vertex is extracted from the priority queue once (O(|V| log |V|)), and each edge may trigger a decrease-key operation (O(|E| log |V|)). With a Fibonacci heap, the bound improves to O(|E| + |V| log |V|).
SHORTEST-PATH OPTIMALITY CONDITION
δ(s, v) ≤ δ(s, u) + w(u, v) for all edges (u, v) ∈ E
The triangle inequality for shortest paths. When all edge weights are non-negative, Dijkstra's greedy selection—always extracting the minimum d(v) vertex—guarantees that each extracted vertex's distance estimate equals its true shortest-path distance δ(s, v). This fails if negative edges exist.
⚠️ Why non-negative weights?
Dijkstra's correctness proof relies on the invariant that when a vertex u is extracted from the priority queue, d(u) = δ(s, u). If a negative-weight edge (u, v) existed, a path through a later-extracted vertex could retroactively lower d(u), violating the invariant. For graphs with negative edges (but no negative cycles), the Bellman–Ford algorithm relaxes all edges |V| − 1 times in O(|V| × |E|) time.

Dijkstra's Algorithm — Step-by-Step Trace

To build intuition for how Dijkstra's algorithm operates on a weighted graph, consider the following diagram. The source is vertex S, and each edge is labeled with its positive weight. The algorithm maintains a priority queue of vertices ordered by tentative distance; at each iteration it extracts the vertex with the smallest tentative distance, finalizes it, and relaxes all its outgoing edges. The diagram below traces the finalization order and the evolving distance estimates.

Dijkstra's algorithm on a weighted graph with source S and target T. Edge weights are shown in amber. The algorithm finalizes vertices in order of non-decreasing distance: S (d = 0), B (d = 2), A (d = 4), C (d = 5), D (d = 5), T (d = 7). The shortest path S → B → D → T has total weight 7.

A critical observation from this trace is that vertex A's tentative distance changes during the algorithm's execution—it starts at ∞, drops to 4 when the edge S → A is relaxed, and remains at 4 because no shorter path through B emerges (the path S → B → A would cost 2 + 5 = 7 > 4). This illustrates the decrease-key operation that is central to the algorithm's efficiency. When using a binary min-heap, each decrease-key runs in O(log |V|) time; with a Fibonacci heap, it is amortized O(1), which is why the Fibonacci heap variant achieves the superior bound of O(|E| + |V| log |V|).

Worked Example — BFS & Dijkstra Side by Side

Consider a small graph with vertices {S, A, B, C, D} and the following edges: S–A (weight 1), S–B (weight 4), A–B (weight 2), A–C (weight 6), B–D (weight 3), C–D (weight 1). We wish to find the shortest path from S to D. We will solve this problem first using BFS (treating the graph as unweighted) and then using Dijkstra's algorithm (respecting edge weights) to highlight the difference.

Part A: BFS (Unweighted)

BFS: Shortest Path S → D (Unweighted)
1
Step 1 — InitializeSet d(S) = 0. All other distances are ∞. Initialize a FIFO queue with S. Mark S as visited.
Queue: [S], d(S) = 0
2
Step 2 — Dequeue S, explore neighborsDequeue S. Its unvisited neighbors are A and B. Set d(A) = 1, d(B) = 1, π(A) = S, π(B) = S. Mark both as visited and enqueue them.
Queue: [A, B], d(A) = 1, d(B) = 1
3
Step 3 — Dequeue A, explore neighborsDequeue A. Neighbor S is already visited. Neighbors B (already visited) and C (unvisited). Set d(C) = 2, π(C) = A. Enqueue C.
Queue: [B, C], d(C) = 2
4
Step 4 — Dequeue B, explore neighborsDequeue B. Neighbor D is unvisited. Set d(D) = 2, π(D) = B. Enqueue D.
Queue: [C, D], d(D) = 2
5
Step 5 — Read the pathTarget D has been reached. Backtrack through predecessors: D ← B ← S. The BFS shortest path is S → B → D with hop-distance 2.
BFS path: S → B → D (2 hops)

Part B: Dijkstra's Algorithm (Weighted)

Dijkstra: Shortest Path S → D (Weighted)
1
Step 1 — InitializeSet d(S) = 0, d(A) = d(B) = d(C) = d(D) = ∞. Insert all vertices into a min-priority queue keyed by d-value.
Priority queue min: S (d = 0)
2
Step 2 — Extract S (d = 0)Relax S → A: d(A) = min(∞, 0 + 1) = 1. Relax S → B: d(B) = min(∞, 0 + 4) = 4. Update π(A) = S, π(B) = S.
d(A) = 1, d(B) = 4
3
Step 3 — Extract A (d = 1)Relax A → B: d(B) = min(4, 1 + 2) = 3. Update π(B) = A. Relax A → C: d(C) = min(∞, 1 + 6) = 7. Update π(C) = A.
d(B) updated to 3, d(C) = 7
4
Step 4 — Extract B (d = 3)Relax B → D: d(D) = min(∞, 3 + 3) = 6. Update π(D) = B. No other unvisited neighbors.
d(D) = 6
5
Step 5 — Extract D (d = 6)D has neighbor C: d(C) = min(7, 6 + 1) = 7 (via D gives 7 as well, so we check: actually it's the same). Wait—the edge is C–D with weight 1, so we also check D → C: d(C) = min(7, 6 + 1) = 7. No improvement. D is finalized.
d(D) = 6, finalized
6
Step 6 — Reconstruct pathBacktrack through predecessors from D: π(D) = B, π(B) = A, π(A) = S. The shortest weighted path is S → A → B → D with total weight 1 + 2 + 3 = 6.
Dijkstra path: S → A → B → D (total weight 6)
💡 Key Contrast
BFS found S → B → D (2 hops, total weight 4 + 3 = 7 if weights were considered), whereas Dijkstra found S → A → B → D (3 hops, total weight 1 + 2 + 3 = 6). This illustrates a fundamental lesson: the fewest-hops path and the least-weight path are generally different. BFS minimizes the number of edges, while Dijkstra minimizes the sum of edge weights.

BFS vs. Dijkstra — Strengths & Limitations

BFS and Dijkstra's algorithm are not competing solutions to the same problem; rather, they are specialized tools whose domains of applicability overlap only in the special case of unit-weight graphs. Understanding their respective strengths and limitations is essential for selecting the right algorithm in practice.

Side-by-side comparison of BFS and Dijkstra's algorithm
CriterionBFSDijkstra
Graph typeUnweighted or unit-weightNon-negative weighted
Data structureFIFO queueMin-priority queue (binary heap, Fibonacci heap)
Time complexityO(|V| + |E|)O((|V| + |E|) log |V|) with binary heap
Space complexityO(|V|) for queue and visited arrayO(|V|) for priority queue and distance array
Negative edges?Not applicable (no weights)No — requires non-negative weights for correctness
Optimality metricMinimum number of edges (hops)Minimum sum of edge weights
Typical applicationsSocial network degrees of separation, web crawling, puzzle solvingGPS routing, network routing (OSPF), airline flight cost optimization
KEY TAKEAWAY
Choosing between BFS and Dijkstra is analogous to choosing between a subway map and a driving GPS. On a subway system where every ride between adjacent stations takes the same time, you only care about minimizing the number of stops—BFS does this perfectly. But on a road network where different roads have different lengths and speed limits, you need a GPS that accounts for variable travel times—that is Dijkstra's algorithm. Using a subway-style approach on a weighted road network would give you the fewest turns, but not necessarily the fastest route.

Connection to Advanced Shortest-Path Theory

BFS and Dijkstra's algorithm are foundational, but real-world applications often demand extensions that handle negative weights, heuristic guidance, or all-pairs queries. The table below situates these two algorithms within the broader landscape of shortest-path methods, providing a roadmap for further study.

Shortest-path algorithms in the broader algorithmic landscape
AlgorithmEdge Weight RequirementComplexityKey Idea
BFSUnweighted / unit-weightO(|V| + |E|)FIFO queue ensures level-order traversal
DijkstraNon-negativeO((|V|+|E|) log |V|)Greedy extraction of minimum-distance vertex
Bellman–FordAny (detects negative cycles)O(|V| × |E|)Relaxes all edges |V| − 1 times
A* SearchNon-negative with admissible heuristicO(|E|) best case, variesDijkstra + heuristic estimate to target
Floyd–WarshallAny (no negative cycles)O(|V|³)All-pairs via dynamic programming on intermediate vertices

The A* algorithm deserves special mention as a natural extension of Dijkstra's algorithm. A* augments Dijkstra's priority key with a heuristic function h(v) that estimates the remaining distance from v to the target. When h is admissible (never overestimates) and consistent (satisfies the triangle inequality), A* explores fewer vertices than Dijkstra while still guaranteeing optimality. This makes it the algorithm of choice for pathfinding in video games, robotics, and geospatial navigation. The Bellman–Ford algorithm, meanwhile, trades speed for generality: by relaxing every edge in |V| − 1 passes, it handles negative edge weights and can detect negative-weight cycles—a capability neither BFS nor Dijkstra possesses.

🔭 Looking Ahead
In courses on algorithms and data structures, you will encounter advanced priority queue implementations (Fibonacci heaps, pairing heaps) that improve Dijkstra's theoretical bounds, as well as techniques like contraction hierarchies and transit nodes that enable real-time shortest-path queries on continental-scale road networks with millions of vertices.

Practice Problems

PROBLEM 1CONCEPTUAL
Explain why BFS is guaranteed to find the shortest path in an unweighted graph but not in a weighted graph with varying edge weights. What specific invariant of the FIFO queue makes this guarantee possible?
PROBLEM 2BASIC CALCULATION
Consider a directed graph with vertices {S, A, B, C} and edges: S → A (weight 3), S → B (weight 1), B → A (weight 1), A → C (weight 2), B → C (weight 6). Apply Dijkstra's algorithm from source S to find δ(S, C). State the finalization order and the shortest path.
PROBLEM 3INTERMEDIATE
An undirected graph has vertices {1, 2, 3, 4, 5, 6} and edges: (1,2) weight 7, (1,3) weight 9, (1,6) weight 14, (2,3) weight 10, (2,4) weight 15, (3,4) weight 11, (3,6) weight 2, (4,5) weight 6, (5,6) weight 9. Run Dijkstra from vertex 1 and determine the shortest-path tree. For each vertex, state its shortest distance and predecessor.
PROBLEM 4APPLIED
A network engineer models a five-router network as a weighted directed graph, where edge weights represent link latency in milliseconds. The routers are R1 through R5 with links: R1→R2 (2ms), R1→R3 (5ms), R2→R3 (1ms), R2→R4 (7ms), R3→R4 (3ms), R3→R5 (8ms), R4→R5 (1ms). Using Dijkstra's algorithm, determine the minimum-latency path from R1 to R5 and explain how the OSPF routing protocol would use this result.
PROBLEM 5CRITICAL THINKING
Prove that if all edge weights in a connected graph G are distinct positive integers, then the shortest path between any two vertices is unique. Then construct a counterexample showing that uniqueness can fail when two or more edges share the same weight.

Summary

The shortest path problem asks for the minimum-cost route from a source vertex to all other vertices in a graph. Breadth-first search (BFS) solves this problem in O(|V| + |E|) time for unweighted graphs by exploring vertices in layer-by-layer order using a FIFO queue, guaranteeing that the first discovery of any vertex is optimal. Dijkstra's algorithm extends this idea to graphs with non-negative edge weights by replacing the FIFO queue with a min-priority queue and employing a greedy strategy that always finalizes the vertex with the smallest tentative distance.

Both algorithms rely on the relaxation operation—updating a vertex's distance estimate when a shorter path is found—and maintain a predecessor array for path reconstruction. Dijkstra's algorithm achieves O((|V| + |E|) log |V|) time with a binary heap and O(|E| + |V| log |V|) with a Fibonacci heap. For graphs with negative edges, the Bellman–Ford algorithm is required, and for heuristic-guided single-target search, A* search offers superior practical performance.

Varsity Tutors • Discrete Math • Shortest path algorithms (BFS/Dijkstra conceptually)