Stage 5: Interview Simulation·Rust-Specific Practice
Practice9 min readDijkstraBinaryHeapShortest Path

Rewrite: BinaryHeap Dijkstra

Dijkstra with BinaryHeap + Reverse

Use std::cmp::Reverse for min-heap. Handle the Rust BinaryHeap idioms.

Published Apr 17, 2026

Exercise

Implement Dijkstra's shortest-path algorithm in idiomatic Rust:

fn dijkstra(n: usize, edges: &Vec<(usize, usize, u64)>, src: usize) -> Vec<Option<u64>>

Return a vector of length n where position i is Some(dist) if i is reachable from src, else None. Edges are directed; non-negative weights only.

The pedagogical target: use BinaryHeap<Reverse<(u64, usize)>> correctly. TypeScript gets the same API with a hand-rolled binary min-heap, because the language has no priority queue in the standard library.

Intuition

Dijkstra visits vertices in order of finalized distance. The priority queue is the heart of it: we always extract the vertex whose tentative distance is smallest, because for non-negative edge weights its tentative distance equals its final distance.

Rust's std::collections::BinaryHeap is a max-heap. To make it a min-heap over (distance, vertex) pairs, wrap items in std::cmp::Reverse. That flips the comparator without rewriting the heap.

Classic lazy variant: don't try to decrease-key in the heap. When you relax a neighbor, just push a new entry; when you pop an entry whose stored distance is stale (greater than the current best), skip it. This makes the algorithm O((n + m) log n) in both the wall clock and the heap size.

Approach

O(n²) Array Version

TimeO(n²)
SpaceO(n)

No heap: at each iteration, linearly scan the dist array for the unvisited node with smallest tentative distance. Faster than heap variant only on dense graphs where m . Included as a sanity check.

pub fn dijkstra(n: usize, edges: &Vec<(usize, usize, u64)>, src: usize) -> Vec<Option<u64>> {
    let mut adj: Vec<Vec<(usize, u64)>> = vec![Vec::new(); n];
    for &(u, v, w) in edges { adj[u].push((v, w)); }
    let mut dist: Vec<Option<u64>> = vec![None; n];
    let mut done = vec![false; n];
    dist[src] = Some(0);
    for _ in 0..n {
        let mut u: Option<usize> = None;
        for i in 0..n {
            if done[i] || dist[i].is_none() { continue; }
            if u.map_or(true, |j| dist[i].unwrap() < dist[j].unwrap()) { u= Some(i); }
        }
        let u= match u { Some(u)=> u, None=> break };
        done[u]= true;
        let du= dist[u].unwrap();
        for &(v, w) in &adj[u] {
            let nd= du + w;
            if dist[v].map_or(true, |cv| nd < cv) { dist[v]= Some(nd); }
        }
    }
    dist
}
Rust · runnable

Dijkstra with BinaryHeap + Reverse

TimeO((n + m) log n)
SpaceO(n + m)
Recommended
  1. Build adjacency list.
  2. Seed dist[src] = Some(0), push Reverse((0, src)) onto the heap.
  3. Loop: pop Reverse((d, u)). If d > dist[u], skip (stale). Else relax each neighbor (v, w): if d + w < dist[v], update and push.
  4. Return dist.

The lazy-stale-skip is what keeps the algorithm simple. Explicit decrease-key would need an indexed heap, which isn't in Rust's std and isn't worth it for most problems.

use std::cmp::Reverse;
use std::collections::BinaryHeap;

pub fn dijkstra(n: usize, edges: &Vec<(usize, usize, u64)>, src: usize) -> Vec<Option<u64>> {
    let mut adj: Vec<Vec<(usize, u64)>> = vec![Vec::new(); n];
    for &(u, v, w) in edges { adj[u].push((v, w)); }

    let mut dist: Vec<Option<u64>> = vec![None; n];
    dist[src] = Some(0);

    let mut heap: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();
    heap.push(Reverse((0, src)));

    while let Some(Reverse((d, u))) = heap.pop() {
        if let Some(cur) = dist[u] {
            if d > cur { continue; }
        }
        for &(v, w) in &adj[u] {
            let nd = d + w;
            if dist[v].map_or(true, |cv| nd < cv) {
                dist[v]= Some(nd);
                heap.push(Reverse((nd, v)));
            }
        }
    }

    dist
}
Rust · runnable

Trace

On n = 4, edges [(0,1,5), (0,2,1), (2,1,2), (1,3,1), (2,3,10)], src = 0:

poprelaxdist after
(0, 0)(1, 5), (2, 1)[0, 5, 1, -]
(1, 2)(1, 3), (3, 11)[0, 3, 1, 11]
(3, 1) stale? no(3, 4)[0, 3, 1, 4]
(4, 3)none[0, 3, 1, 4]
(5, 1)stale, skip[0, 3, 1, 4]
(11, 3)stale, skip[0, 3, 1, 4]

Final distances: [0, 3, 1, 4] — path 0 2 1 3 with weights 1 + 2 + 1 = 4.

Edge cases

  • Unreachable vertex — stays None in the result. Never relaxed, never popped.
  • Self loops (u u with weight w) — the relaxation dist[u] + w < dist[u] fails for non-negative w, so the loop is benign.
  • Multiple edges between the same pair — lazy relaxation handles this: the cheapest push "wins", duplicates are discarded as stale.
  • Overflow — using u64 for distances, u64::MAX is ~1.8 × 10^19. For typical w 10^9 and n 10^5 the product is safe; keep distances as u64 not u32.
⚠️
Warning

Never use Dijkstra on graphs with negative weights — it will produce wrong answers silently. Switch to Bellman–Ford (O(n · m)) or SPFA. Negative self-loops are the sneakiest case because they break the "popped vertex has final distance" invariant at step one.

Takeaway

BinaryHeap<Reverse<T>> is the idiomatic min-heap pattern in Rust. You'll reach for it whenever you need "best so far under some total order" — Dijkstra, Prim, K-smallest, event simulations. Reverse changes nothing about the heap's mechanics; it only flips the comparator.

For trading-systems interviews, Dijkstra shows up in routing problems, price-graph arbitrage, and queue-latency analysis. Being able to write it cleanly — lazy stale-skip, no decrease-key — is a concrete signal of systems fluency.

More in Rust-Specific Practice