KyoMel / Yogyakarta, Indonesia

Term 342342 / 344

Dijkstra's Algorithm

Month
October 2026
Day
14
Source
2026/oct-term.md

Definition:

Dijkstra’s algorithm finds the shortest path from one source node to every other node in a weighted graph. Every edge carries a cost. The cost is called a weight, for example minutes, kilometers, or dollars. The algorithm returns the cheapest total cost to each node. It also returns the path that reaches that cost.

Edsger W. Dijkstra designed the algorithm in 1956 and published it in 1959. The first problem was a route between two cities in the Netherlands. The machine was the ARMAC. Dijkstra designed the method in about 20 minutes, in a cafe, with no pencil and no paper. The algorithm is now the default shortest-path engine. It runs in map services, network routers, and game pathfinding.

The algorithm is greedy. One rule controls the whole run: always work on the unfinished node with the smallest known cost. Then settle that node. A settled node never changes again.

That rule needs one condition. All edge weights must be zero or positive. With a positive weight, no later path can reach a settled node with a smaller cost. The proof is short. Every remaining path travels through the frontier. The frontier holds costs that are already larger than the settled cost. Any addition makes them larger again. A negative weight breaks that argument, and the algorithm returns a wrong answer.

THE THREE REGIONS DURING A RUN
═══════════════════════════════════════════════════════════════════
  ┌─────────────────┐   relax an edge   ┌──────────────────┐
  │   UNREACHED     │ ────────────────▶ │     FRONTIER     │
  │   dist = inf    │                   │   min-heap of    │
  │   no path yet   │ ◀──────────────── │   known costs    │
  └─────────────────┘   never goes back └────────┬─────────┘
                                                 │ pop the
                                                 │ smallest cost
                                                 ▼
                                        ┌──────────────────┐
                                        │     SETTLED      │
                                        │  dist is final   │
                                        │  never updated   │
                                        └──────────────────┘

  node count:  UNREACHED shrinks, FRONTIER floats, SETTLED grows.
  every node crosses from left to right exactly one time.

The whole algorithm is one loop and one comparison. The comparison is called a relaxation.

dist[source] = 0
dist[other]  = infinity
heap         = [ (0, source) ]

while heap is not empty:
    (d, u) = heap.pop_min()
    if d > dist[u]:                  # stale entry, skip it
        continue
    for (v, w) in edges[u]:
        if dist[u] + w < dist[v]:    # the relaxation step
            dist[v] = dist[u] + w
            heap.push( (dist[v], v) )

Two names matter in that loop. u is the node you settle. v is a neighbour of u. The heap holds pairs. A pair carries a cost and a node.

The heap explains the cost of the algorithm. A heap keeps the smallest item on top, and it keeps them in order. Order is a sort. A sort costs one log factor. Dijkstra cannot remove that factor, and that single fact stayed unbroken for 66 years.

The greedy rule gives a second property. The pop order is already sorted by cost. The algorithm sorts the nodes while it searches them. That property is a useful debugging tool, and it also explains the 2025 result at the end of this entry.

The naive alternative is BFS. BFS counts hops, not cost. On an unweighted graph the two ideas agree. On a weighted graph they disagree at the first cheap detour.

FEWEST HOPS (BFS)  vs  LOWEST COST (DIJKSTRA)
══════════════════════════════════════════════════════════════════════
 EDGES, weight in minutes
   W -> A = 4     W -> B = 1     B -> A = 2
   A -> C = 5     B -> C = 8     C -> D = 3

   W ──4──▶ A ──5──▶ C ──3──▶ D        BFS: hop count wins
   │                   ▲                 3 hops, cost 4 + 5 + 3 = 12 min
   1                   2
   │                   │
   ▼                   │                DIJKSTRA: cost wins
   B ───────8──────────┘                 4 hops, cost 1 + 2 + 5 + 3 = 11 min

 ─────────────────────────────────────────────────────────────────────
  BFS returns the route with fewer hops.  Dijkstra returns the route
  with the lower bill.  The two answers are both correct.  They answer
  different questions.

The data structure changes the running time. Nothing else does.

 TIME COMPLEXITY BY PRIORITY QUEUE
═════════════════════════════════════════════════════════════════════
 STRUCTURE          TIME                  BEST WHEN
 ─────────────────────────────────────────────────────────────────────
 binary min-heap    O((V + E) log V)      the normal default
 plain array scan   O(V^2)                E close to V^2 (dense graph)
 Fibonacci heap     O(E + V log V)        dense graph, decrease-key
 ─────────────────────────────────────────────────────────────────────
 V = nodes, E = edges.  Most libraries ship the binary heap version.
 Edge storage: adjacency list.  Space: O(V + E).
 Early exit: stop when the target node leaves the heap.

The negative weight boundary. One negative edge is enough to break the result.

WHY NEGATIVE WEIGHTS BREAK THE GREEDY RULE
═══════════════════════════════════════════════════════════════════
   edges:  S -> A = 2      S -> B = 5      B -> A = -4

   S ──2──▶ A          true cheapest path to A:
   │                   S -> B -> A  =  5 + (-4)  =  1
   5                   ─────────────────────────────────────────
   │                   Dijkstra run:
   ▼                     step 1  pop A, cost 2     settle A at 2
   B ──(-4)──▶ A         step 2  pop B, cost 5     settle B at 5
                         step 3  relax B -> A      found 1, but A
                                 is already settled
                       ─────────────────────────────────────────
                       answer from Dijkstra: 2      truth: 1
                       one negative edge, one wrong number.

Use Bellman-Ford or Johnson’s algorithm when the graph has negative weights. Bellman-Ford also detects a negative cycle.

 CHOOSING A SINGLE-SOURCE SHORTEST PATH ALGORITHM
═══════════════════════════════════════════════════════════════════════
 ALGORITHM      WEIGHTS ALLOWED     KEY PROPERTY        TYPICAL USE
 ──────────────────────────────────────────────────────────────────────
 BFS            all equal           fewest hops         unweighted graph
                                    queue, not heap
 Dijkstra       zero or positive    settles each node   routing tables,
                                    one time            maps, dispatch
 Bellman-Ford   negative allowed    detects negative    finance, price
                                    cycles              arbitrage
 A*             zero or positive    needs a heuristic   one target,
                                    to cut the search   map navigation
 ──────────────────────────────────────────────────────────────────────
 Dijkstra is the default.  Leave it only when a column forces you out.

2025 update: the sorting barrier falls. For 66 years the O(m + n log n) bound held on sparse graphs. People treated it as a law of nature. In April 2025, Duan, Mao, Mao, Shu and Yin published a deterministic algorithm for directed graphs with real non-negative weights. The bound is O(m log^(2/3) n). It is the first result to beat Dijkstra on sparse graphs. The method drops the full sort. It uses a partial order and a small set of “pivots”, and it tolerates a bounded amount of guesswork.

THE SORTING BARRIER, BEFORE AND AFTER
═══════════════════════════════════════════════════════════════════════
 1959 - 2025   O(m + n log n)          Dijkstra and friends
 2025          O(m log^(2/3) n)        Duan, Mao, Mao, Shu, Yin

 n = 1e6, m = 1e6 (sparse)
   log2 n        = 19.93     Dijkstra term cost  ≈ 2.1e7 unit steps
   log2^(2/3) n  = 7.35      new algorithm cost  ≈ 7.4e6 unit steps
   ratio 2.85x fewer steps, in theory
 ───────────────────────────────────────────────────────────────────────
 n = 1e6, m = 1e7 (denser)
   Dijkstra      ≈ 3.0e7     new algorithm       ≈ 7.4e7
   ratio 0.41x              the new bound LOSES here
 ───────────────────────────────────────────────────────────────────────
  Read the second block first.  The new bound wins when the graph is
  sparse, and it loses when the edge count grows.  The unit-step counts
  are asymptotics, not a benchmark.  Constant factors and memory access
  decide real timings.

Honest status in 2026: this is a theory result. Production code still runs Dijkstra, A*, and contraction hierarchies. The new method is complex to implement, and it optimises the asymptotic step count, not the cache. That does not reduce the result. It removes a barrier that people believed was permanent.

Example:

A courier route in a dispatch engine.

A delivery platform routes one courier from warehouse W to customer D. Edge weights are travel minutes. The graph has one trap: the shortest hop route is not the fastest route.

THE INPUT GRAPH (minutes, directed)
═══════════════════════════════════════════════════════════════════════
        W ──4──▶ A ──5──▶ C ──3──▶ D
        │                  ▲
        1                  2
        │                  │
        ▼                  │
        B ────────8────────┘

The engine runs Dijkstra from W. Each row shows one settled node. A star marks a stale heap entry, created when a cheaper path arrived later.

TRACE OF THE RUN
════════════════════════════════════════════════════════════════════════
 STEP  SETTLED   HEAP AFTER RELAXATION         dist  W    A    B    C    D
 ────────────────────────────────────────────────────────────────────────
 init    -        W(0)                           W 0    inf  inf  inf  inf
   1     W        B(1), A(4)                     W 0     4    1   inf  inf
   2     B        A(3), A(4)*, C(9)              W 0     3    1    9   inf
   3     A        C(8), C(9)*, A(4)*             W 0     3    1    8   inf
   4     C        D(11), C(9)*, A(4)*            W 0     3    1    8    11
   5     D        empty, stale entries skipped   W 0     3    1    8    11
 ────────────────────────────────────────────────────────────────────────
 * stale entry.  the heap keeps the old value, the code skips it at pop.
   pop order: 0, 1, 3, 8, 11  -  strictly increasing cost, by construction.

Two relaxations changed a value that was already in the heap. Step 2 found a cheaper path to A, cost 3 instead of 4. Step 3 found a cheaper path to C, cost 8 instead of 9. Both old entries stayed in the heap, and both were skipped. This is the lazy deletion style. It costs extra heap space and removes the need for a decrease-key operation. Most production libraries prefer it.

The engine reconstructs the path from the prev pointer of each node.

THE TWO ROUTES THE ENGINE COULD RETURN
═══════════════════════════════════════════════════════════════════════
 BFS route      W ─▶ A ─▶ C ─▶ D            3 hops     12 minutes
 DIJKSTRA route W ─▶ B ─▶ A ─▶ C ─▶ D       4 hops     11 minutes
 ───────────────────────────────────────────────────────────────────────
 the extra hop pays for itself:  W -> B costs 1 minute, not 4.
 1 minute saved on the courier, multiplied by 4,000 runs a day,
 4,000 minutes saved a day.  The dispatch engine ships Dijkstra.

Production adds three accelerations on top of the plain algorithm. A* replaces the heap order with dist + heuristic, so the search leans toward the target. Contraction hierarchies precompute shortcuts, because the road graph changes rarely and queries arrive constantly. Bidirectional search runs one Dijkstra from each end and stops at the meeting point. OSPF routers use the plain algorithm, under the name SPF, and recompute it after every topology change.

One boundary worth naming. Dijkstra is a single-source method. All-pairs queries need one run per source, or a different algorithm, as in Floyd-Warshall. For the courier case the single-source form is the correct shape, because the warehouse stays the same and the customer changes all day.

Keep reading