Senior · Algorithms

Dijkstra and stale heap entries

How do you find cheapest routes with nonnegative edge weights?

Try explaining this aloud before opening the answer. State your assumptions and trace a boundary case.

ALGORITHM LAB · STEP THROUGH THE INVARIANT

0→[(1, 4), (2, 1)]; 1→[]; 2→[(1, 2)]

STEP 1 / 5

Start at distance zero

Distances
00
∞1
∞2
Min-heap
0:00

Heap entries are distance:node; weights must be nonnegative.

Computed traces for these inputs. Indexes identify cell positions; active cells are labeled as well as highlighted. Compare a boundary case using the scenario selector. No code is sent to a server.

EXAMPLE · FRAGMENT UNLESS A COMPLETE PROGRAM IS SHOWN

go
type Edge struct{ To, Weight int }
type candidate struct{ Node, Cost int }
type candidates []candidate

func (h candidates) Len() int           { return len(h) }
func (h candidates) Less(i, j int) bool { return h[i].Cost < h[j].Cost }
func (h candidates) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *candidates) Push(x any)        { *h = append(*h, x.(candidate)) }
func (h *candidates) Pop() any {
	old := *h
	x := old[len(old)-1]
	*h = old[:len(old)-1]
	return x
}

// Requires container/heap. Caller validates node IDs, nonnegative
// weights and that every path cost fits int. -1 means unreachable.
func ShortestDistances(adj [][]Edge, start int) []int {
	d := make([]int, len(adj))
	for i := range d {
		d[i] = -1
	}
	d[start] = 0
	h := &candidates{{Node: start, Cost: 0}}
	for h.Len() > 0 {
		current := heap.Pop(h).(candidate)
		if current.Cost != d[current.Node] {
			continue
		}
		for _, e := range adj[current.Node] {
			cost := current.Cost + e.Weight
			if d[e.To] < 0 || cost < d[e.To] {
				d[e.To] = cost
				heap.Push(h, candidate{Node: e.To, Cost: cost})
			}
		}
	}
	return d
}
Reveal a strong answer
  1. Keep the best known distance to every node and use a min-heap of candidate distances. Pop the smallest candidate, skip it if its distance is stale, and relax outgoing edges.

  2. With nonnegative weights, a current minimum cannot later be improved through a more expensive unsettled path. Negative edges invalidate that argument; choose an appropriate algorithm rather than quietly accepting them.

  3. A simple Go implementation can push a new entry for each improvement instead of decrease-key. Stale entries use more heap space but simplify ownership. Guard distance addition against overflow and define unreachable results explicitly.

Common trap

Marking visited when enqueuing is correct for BFS, but can discard a later cheaper route in Dijkstra.

Complexity & use

With duplicate heap entries: O((V+E) log(V+E)) time and O(V+E) storage; tighter bounds depend on the heap strategy.

Try it

A→B costs4, A→C costs1, C→B costs2. Which B entry becomes stale?

Worked solution

The heap first receives B4 and C1. Expanding C improves B to3; B3 is processed, and the later B4 entry is skipped. Returning as soon as B4 is inserted would produce the wrong route.

Change the constraint

Why does adding a heuristic require an admissibility/consistency argument in A*?

How did you do?

Self-assessment, not a compiler pass or interview score. Explain the reasoning unaided before marking understood.

Not marked. Progress stays on this browser, separate from course completion.

Primary references

Checked 2026-10-20. Use documentation for your pinned runtime, driver and tool version.