Where do the log factors in Dijkstra's O((V + E) log V) binary-heap bound come from?
answer
- count the queue operations first
- how many times is a vertex extracted?
- how many times is an edge relaxed?
- each queue touch costs the heap's height
- sum V log V and E log V
basics
~20 sEach vertex is extracted from the min-heap once at O(log V), giving V log V. Each edge triggers at most one priority update, also O(log V), giving E log V. The two terms sum to O((V + E) log V).
solid answer
~50 sThere are exactly two heap-touching operations, and both cost O(log V). Every vertex is extracted as the minimum exactly once, so extraction contributes `V log V`. Every directed edge is relaxed once, from its settled tail, and a successful relaxation performs one priority update, so relaxation contributes `E log V`. Summing gives O((V + E) log V); on a connected graph E >= V - 1, so it is usually written O(E log V). The log is the heap's height, not a property of the graph - a straight chain of vertices pays it too. Two variants matter: scanning a plain array for the minimum gives O(V^2), which wins on dense graphs, and a Fibonacci heap's amortized O(1) decrease-key gives O(E + V log V), better asymptotically but usually beaten by binary heaps in practice.
go deeper
Know the headline bound and be able to say that a vertex is popped once and an edge relaxed once. You are not expected to derive the variants, but do not quote O(V log V) as the answer.
Derive the bound live: V extractions at log V, E relaxations at log V, summed. Explain that the log is the heap's height, and name the array and Fibonacci-heap variants with their bounds.
Choose the queue for the workload - array scan on dense graphs, binary heap on sparse ones - and back it with measurements rather than asymptotics, since constants decide at realistic sizes.
Frame the cost against the product budget: how much per-query latency the route service can afford, whether memory for the queue scales across the fleet, and when the answer is to change the query shape rather than the queue.
## Count operations, then price them The cost of Dijkstra is entirely determined by what it does to the priority queue, so the derivation is a two-line inventory. **Extractions.** Each vertex is settled exactly once, so extract-min runs V times. In a binary min-heap, extract-min moves the last element to the root and sifts it down through a tree of height `log V`, so each extraction is O(log V). Total: **O(V log V)**. **Relaxations.** Each directed edge `(u, v)` is examined exactly once - when its tail `u` is settled. If the test `dist[u] + w < dist[v]` succeeds, `v`'s key in the queue must drop, which is a decrease-key (sift up) or an insert, again O(log V). Every edge triggers at most one such update. Total: **O(E log V)**. Sum: **O((V + E) log V)**. Because a connected graph has E >= V - 1, the edge term dominates and the bound is commonly written **O(E log V)**. Note also that `log E <= log(V^2) = 2 log V`, so it makes no asymptotic difference whether the heap holds up to V entries or up to E entries - both are O(log V) per operation. ## What the logarithm is, and is not The log is the **height of the heap**, a data-structure property. It is not the depth of the graph, not the number of hops in the longest shortest path, and not related to how branchy the map is. Run Dijkstra on a straight chain of intersections and the log is still there, because the heap still has to reorder itself. ## The variants worth naming | Priority queue | Extract-min | Decrease-key | Total | |---|---|---|---| | Unsorted array, scan for min | O(V) | O(1) | O(V^2 + E) = O(V^2) | | Binary min-heap | O(log V) | O(log V) | O((V + E) log V) | | Fibonacci heap | O(log V) amortized | O(1) amortized | O(E + V log V) | The array row is not a curiosity. On a **dense** graph, where E is on the order of V^2, the heap version is O(V^2 log V) while the array version is O(V^2) - the simpler structure wins outright, and it wins on constants too. Sparse road networks, where each intersection has a handful of roads and E is roughly a small multiple of V, are the case the heap is built for: O(V log V) instead of O(V^2) is the difference between seconds and hours at a million intersections. The Fibonacci-heap row is the standard "do you know the theory" follow-up. Its bound is genuinely better asymptotically, and it is genuinely slower than a binary heap on almost every real input, because its constant factors and pointer chasing dominate at realistic sizes. This is the general shape of the trap: an asymptotic improvement promises nothing at any particular n, and here the crossover is well past the sizes anyone runs. ## Two claims to keep straight **"It's O(V log V)"** drops the edges. That would only be right if each vertex were relaxed a constant number of times, which is false - a vertex with a hundred incoming roads can have its key lowered many times. **"It's O(E log E)"** is a legitimate way to count a lazy implementation that pushes a new heap entry per successful relaxation rather than updating in place: the heap grows to O(E) entries and each operation is O(log E). Since log E and log V differ by at most a factor of two, this collapses to the same bound; say so rather than treating it as a different result. ## Where the non-heap work goes Initializing distances and building the adjacency structure is O(V + E) - linear, and swallowed by the log terms. Recovering a route from predecessor pointers is O(path length), also swallowed. There is no hidden sort of the edges anywhere; a candidate who mentions sorting edges by weight has confused the algorithm with a spanning-structure method. ## Space O(V) for distances, predecessors and the settled marks, plus the queue - O(V) with decrease-key, up to O(E) with lazy insertion. Adjacency storage is O(V + E). On a continental road graph with hundreds of millions of edges, that queue distinction stops being academic and turns into a memory-ceiling conversation.
- Your graph is dense, with E close to V^2. Is the binary heap still the right choice?No. The heap version is O((V + E) log V), which on a dense graph is O(V^2 log V), while scanning an unsorted array for the minimum is O(V^2) with a much smaller constant. Dense graphs make the log a pure loss, since you touch nearly every pair anyway. Heaps pay off on sparse graphs like road networks, where E is a small multiple of V.
- A Fibonacci heap gives O(E + V log V). Why do most implementations not use one?Because the bound is amortized and the constants are large: pointer-heavy node structures, poor memory locality, and expensive consolidation. At realistic graph sizes a flat binary heap - or even a bucket-based queue when weights are small integers - beats it in wall-clock time. Asymptotic superiority promises nothing at a given n; the crossover here sits beyond the inputs people actually run.
- Does the log factor depend on how deep or branchy the graph is?No. It is the height of the priority queue, which holds at most V (or with lazy insertion at most E) entries regardless of graph shape. A straight chain of vertices and a densely cross-linked mesh pay the same per-operation log. Graph shape shows up in E, not in the log.
saying these in an interview costs you the question
- Says O(V log V) and forgets the edge relaxations
- Claims the log is the depth of the graph
- Insists a heap always beats an array scan
- Thinks edges get sorted by weight somewhere
- Treats the Fibonacci-heap bound as the practical choice