Graph connections

Draft

Dijkstra's Algorithm

Find lowest-cost paths in a nonnegative weighted graph by always settling the cheapest tentative node next.

algorithm intermediate graphsalgorithmsshortest-paths

Hook problem

BFS works when every edge costs one step. But roads, links, or choices often have different costs. Now the question is not “how many edges?” but “what is the smallest total cost from A?”

The map is now weighted: one edge can cost more than two edges.Weighted graph before Dijkstra starts.
412158361AA: tentative, dist 0CC: unseen, dist InfinityBB: unseen, dist InfinityDD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity
Tracestart
HeapA:0
Current-
Edge-
RuleWeights are explicit labels; visual length is not cost.
Final distances from A
Nodedistparent
A0-
B3C
C1A
D4B
E7D
F8E

First naive idea

Try BFS anyway. It would discover B directly from A and keep the one-edge path A -> B, even though A -> C -> B costs less.

BFS would keep A-B first, but A-C-B costs less.BFS failure on weighted graph.
412158361AA: current, dist 0CC: unseen, dist InfinityBB: tentative, dist 4DD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity
Tracerelax-A-B
HeapB:4
CurrentA
EdgeA-B
RuleFirst discovered by edge count is not lowest cost.

Where it breaks

In an unweighted graph, first discovery is enough. In a weighted graph, first discovery by edge count can freeze the wrong parent or cost.

A-B costs 4; A-C-B costs 3.Route cost comparison for B.
412158361AA: settled, dist 0CC: current, dist 1BB: tentative, dist 3DD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity
Tracerelax-C-B
HeapB:3, B:4*
CurrentC
EdgeC-B
RuleCompare total costs, not edge counts.

The core invention

Dijkstra keeps a frontier like BFS, but the frontier is ordered by tentative total cost. A FIFO queue pops the oldest entry; a priority queue pops the smallest label.

The priority queue replaces FIFO order.Tentative distance table and heap after C updates B.
412158361AA: settled, dist 0CC: current, dist 1BB: tentative, dist 3DD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity
Tracerelax-C-B
HeapB:3, B:4*
CurrentC
EdgeC-B
RulePriority comes from the smallest tentative distance.

Relaxation scaffold

Relaxation asks whether going through the current node improves a neighbor. For C -> B, the old dist[B] is 4, while dist[C] + 2 = 3, so B improves and its parent changes to C.

C -> B improves B from 4 to 3 and changes parent to C.Relaxation from C to B.
412158361AA: settled, dist 0CC: current, dist 1BB: tentative, dist 3DD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity
Tracerelax-C-B
HeapB:3, B:4*
CurrentC
EdgeC-B
RuleUpdate when candidate < current tentative distance.

Interactive visual demo

412158361AA: tentative, dist 0CC: unseen, dist InfinityBB: unseen, dist InfinityDD: unseen, dist InfinityEE: unseen, dist InfinityFF: unseen, dist Infinity

Dijkstra trace

Initialize A at distance 0; every other node is Infinity.

Current-
Active edge-
Popped-
Settled-
Priority queue:A:0

Shown sorted for reading; a real heap only promises the next minimum.

NodeDistanceParent
A0-
BInfinity-
CInfinity-
DInfinity-
EInfinity-
FInfinity-

Formal version

For every edge (u, v), Dijkstra requires w(u, v) >= 0. It keeps a tentative distance dist[v], a parent[v], a settled set of finalized nodes, and a priority queue of candidate entries.

Formal state names the current node, active edge, candidate, heap, and tables.Formal Dijkstra state while scanning D-E.
412158361AA: settled, dist 0CC: settled, dist 1BB: settled, dist 3DD: current, dist 4EE: tentative, dist 9FF: unseen, dist Infinity
Tracescan-D-E
HeapD:6*, E:9
CurrentD
EdgeD-E
Rulecandidate = dist[current] + weight(current, neighbor).

The algorithm computes distances to all reachable nodes. We inspect F later as one recovered path, not because Dijkstra is only a single-target algorithm.

Implementation sketch

function dijkstra(start: NodeId, graph: WeightedGraph) {
  const dist = new Map<NodeId, number>([[start, 0]]);
  const parent = new Map<NodeId, NodeId>();
  const settled = new Set<NodeId>();
  const heap = new MinHeap([{ node: start, distance: 0 }]);

  while (!heap.isEmpty()) {
    const entry = heap.popMin();
    if (entry.distance !== dist.get(entry.node) || settled.has(entry.node)) continue;

    settled.add(entry.node);
    for (const edge of graph[entry.node]) {
      const candidate = entry.distance + edge.weight;
      if (settled.has(edge.to)) continue;
      if (candidate < (dist.get(edge.to) ?? Infinity)) {
        dist.set(edge.to, candidate);
        parent.set(edge.to, entry.node);
        heap.push({ node: edge.to, distance: candidate });
      }
    }
  }

  return { dist, parent };
}

Duplicate heap entries are allowed. The stale-entry guard skips an old entry when its distance no longer matches the best known dist[node].

The stale-entry guard keeps duplicate heap entries simple.Code to trace mapping for stale skip.
412158361AA: settled, dist 0CC: settled, dist 1BB: current, dist 3DD: tentative, dist 4EE: tentative, dist 9FF: unseen, dist Infinity
Traceskip-stale-B-4
HeapD:4, D:6*, E:9
CurrentB
Edge-
RuleSkip when the popped entry is stale.

Correctness intuition

Just before settling B, the heap minimum is (B, 3) and every other tentative entry is at least 4. Any hidden alternative path to B through unsettled nodes must first reach the frontier at cost >= 4, then add a nonnegative edge. It cannot beat 3.

Before settling B, the heap minimum is (B,3); other tentative entries are at least 4.Correctness frontier before settling B.
412158361AA: settled, dist 0CC: settled, dist 1BB: current, dist 3DD: tentative, dist 6EE: tentative, dist 9FF: unseen, dist Infinity
Tracepop-B-3
HeapB:4*, D:6, E:9
CurrentB
Edge-
RuleAny hidden alternative starts at cost >= 4 and then adds a nonnegative edge.

Complexity

With a binary heap and duplicate entries, each successful relaxation pushes a heap entry. Each heap operation costs O(log V), so the usual bound is O((V + E) log V).

9 undirected edges become 18 directed neighbor scans; duplicate pushes create stale skips.Dijkstra complexity counts for the fixture.
412158361AA: settled, dist 0CC: settled, dist 1BB: settled, dist 3DD: settled, dist 4EE: settled, dist 7FF: settled, dist 8
Tracedone
Heap-
Current-
Edge-
RuleEdge scans plus heap operations give O((V + E) log V).

9 undirected edges produce 18 neighbor scans, 10 heap pushes, and 4 stale skips in this fixture.

Common confusions

A priority queue is shown sorted here for reading, but a real heap only promises the next minimum. Old entries can remain in the heap and become stale.

The old (B,4) entry is stale because dist[B] is already 3.Stale priority queue entry for B.
412158361AA: settled, dist 0CC: settled, dist 1BB: current, dist 3DD: tentative, dist 4EE: tentative, dist 9FF: unseen, dist Infinity
Traceskip-stale-B-4
HeapD:4, D:6*, E:9
CurrentB
Edge-
RuleShown sorted for reading; real heap only promises the next minimum.

Heap entries are shown sorted for reading; a real heap only promises the next minimum.

Zero-weight edges are allowed. Equal tentative-distance ties can use a deterministic display order, but tie order is not what makes Dijkstra correct.

An unreachable node would keep distance Infinity and no parent.

An isolated G would have dist[G] = Infinity and no parent.Unreachable isolated node case.
412158361AA: settled, dist 0CC: settled, dist 1BB: settled, dist 3DD: settled, dist 4EE: settled, dist 7FF: settled, dist 8
Tracedone
Heap-
Current-
Edge-
RuleUnreachable nodes stay Infinity and never enter the heap.

If an isolated G were added, it would remain dist[G] = Infinity, parent[G] = none, and never enter the heap.

Negative edges break the settled-state promise. This warning uses a separate directed graph, not the main undirected map.

A later negative edge contradicts the settled-state promise.Directed negative edge warning for Dijkstra.
25-4SABdirected warning, not the main map
Warning stepnegative-contradiction
Candidate1
RuleThis is a directed warning variant, not the main map.

Path recovery

Distances tell the cost. Parent pointers recover a concrete path by following arrows backward from the target and reversing the result.

F recovers path A -> C -> B -> D -> E -> F.Path reconstruction from F.
412158361AA: settled, dist 0CC: settled, dist 1BB: settled, dist 3DD: settled, dist 4EE: settled, dist 7FF: settled, dist 8
Tracedone
Heap-
Current-
Edge-
RuleFollow parent arrows backward, then reverse the result.
Final distances from A
Nodedistparent
A0-
B3C
C1A
D4B
E7D
F8E

Follow parents backward from F: F <- E <- D <- B <- C <- A; reverse to get A -> C -> B -> D -> E -> F.

Connections in the graph

Dijkstra generalizes BFS’s frontier-and-distance idea. BFS uses FIFO order because every edge costs one; Dijkstra uses tentative total cost because edges can have different nonnegative weights.

Exercises

  1. After A settles, which heap entry should pop next?
  2. Why does C -> B update B, but the later stale (B, 4) is skipped?
  3. Reconstruct the path to F from the parent table.
  4. In the directed negative-edge warning, what settled-state promise fails?

Graph connections : Dijkstra's Algorithm