Theory

Dijkstra, and why it is correct

Dijkstra's algorithm rests on one claim.

Why it is correct

When you take the cheapest unprocessed node next, its distance is already final.

The proof fits in a sentence. Suppose there were a cheaper route to node v that we have not found. That route must cross the boundary between processed and unprocessed nodes — and its first unprocessed node u is cheaper than v. So u would have been taken before v, not after.

Which is impossible, provided no edge is negative. Because then "further along the route" means "not cheaper". Step 4 shows what happens when that condition is broken.

Compare with lesson 14: BFS's layers were the same argument in a simpler form. Dijkstra is BFS with cost in place of layers.

The algorithm

Dijkstra(from, to):
    dist[*] ← ∞;  dist[from] ← 0
    frontier ← {from at 0}

    while frontier is not empty:
        v, d ← the CHEAPEST element of frontier      // ← the whole point
        if done[v]: skip                             // a stale entry
        done[v] ← true
        if v = to: return the path and d
        for each edge (v → w, cost c):
            if d + c < dist[w]:
                dist[w] ← d + c
                prev[w] ← v
                put w into frontier at d + c

Compare it with lesson 14's BFS. Three things differ: a dist array instead of seen, the condition d + c < dist[w] instead of !seen[w], and the frontier hands back the cheapest rather than the oldest.

The third is the only one that needs a structure.

Two frontiers

A heap (lesson 13): Push and Pop cost O(log V).

A linear scan: no structure at all; at every step look at all V nodes and take the smallest. O(V).

Both are correct and return the same path. Step 5 measures what the difference costs.

Stale entries: why the heap holds more than there are nodes

When a cheaper route to w turns up, w may already be in the heap with an older, dearer value. What then?

There are two answers.

Decrease-key: find w in the heap and correct its value. But the heap does not know where w is — that needs a separate index, kept in step with every swap. In lesson 13 you measured 6,922 swaps for a single 10,000-element build; every one of them would get more expensive.

Lazy deletion: just push w again. The heap then holds several entries for one node; the first one popped is the cheapest, and the rest are recognised by done[v] and skipped.

The second is what transit writes and what you will write. The price: the heap holds a little more than it has nodes. How much more, you measure in step 4.

The cost

V pops at O(log V), E pushes at O(log V):

O((V + E) log V) with a heap. O(V² + E) with a linear scan.

On a sparse graph — and your mean degree is 1.49 — the first is nearly linear and the second is quadratic.