🦀 Functional Rust

354: BinaryHeap — Priority Queue

Difficulty: 2 Level: Intermediate Always pops the largest element first. O(1) peek, O(log n) push/pop — the go-to for Dijkstra's, task scheduling, and top-K problems.

The Problem This Solves

You have a set of tasks where each has a priority, and you always want to process the highest-priority task next. You could use a sorted `Vec` — but every insertion requires finding the right position (O(log n) search + O(n) shift). You could re-sort the whole `Vec` after every insertion — that's O(n log n) just to add one item. `BinaryHeap` gives you a smarter answer: insertions are O(log n) and the maximum element is always at the top, accessible in O(1). You never sort — the heap property (every parent ≥ its children) is maintained automatically on every push and pop. The second use case is top-K selection: given a stream of a billion numbers, find the 10 largest. A max-heap of size 10 lets you push each new number and pop the minimum if the heap overflows — at O(log 10) = O(1) effective cost per element.

The Intuition

Python's `heapq` is a min-heap (smallest first). Rust's `BinaryHeap` is a max-heap (largest first). For min-heap behavior in Rust, wrap your type in `std::cmp::Reverse<T>` — it flips the comparison. The mental model: imagine a queue where instead of "first in, first out", the rule is "highest priority out first". The heap data structure maintains this guarantee efficiently without sorting.

How It Works in Rust

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

// Max-heap: largest comes out first
let mut heap: BinaryHeap<i32> = BinaryHeap::new();
heap.push(3);
heap.push(1);
heap.push(4);
heap.push(1);
heap.push(5);

// Peek without removing — O(1)
println!("{:?}", heap.peek()); // Some(5)

// Pop largest first — O(log n)
while let Some(val) = heap.pop() {
 println!("{val}"); // 5, 4, 3, 1, 1
}

// Min-heap: wrap in Reverse to flip ordering
let mut min_heap: BinaryHeap<Reverse<i32>> = BinaryHeap::new();
min_heap.push(Reverse(5));
min_heap.push(Reverse(1));
min_heap.push(Reverse(3));

while let Some(Reverse(val)) = min_heap.pop() {
 println!("{val}"); // 1, 3, 5
}

// Priority queue with custom priority
#[derive(Eq, PartialEq)]
struct Task { priority: i32, name: &'static str }

// Implement Ord so highest priority value comes out first
impl Ord for Task {
 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
     self.priority.cmp(&other.priority)
 }
}
impl PartialOrd for Task {
 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
     Some(self.cmp(other))
 }
}

let mut tasks: BinaryHeap<Task> = BinaryHeap::new();
tasks.push(Task { priority: 1, name: "low" });
tasks.push(Task { priority: 10, name: "critical" });
tasks.push(Task { priority: 5, name: "normal" });

// Processes "critical" first
while let Some(task) = tasks.pop() {
 println!("processing: {} (priority {})", task.name, task.priority);
}

What This Unlocks

Key Differences

ConceptOCamlRust
Priority queuenot in stdlib`BinaryHeap<T>`
Heap typemax or min depending on implmax-heap by default
Min-heapinvert comparison manually`BinaryHeap<Reverse<T>>`
Peek maxmanual access`.peek()` O(1)
Pop maxmanual`.pop()` O(log n)
Python equivalent`heapq` (min-heap)`BinaryHeap<Reverse<T>>` for min
use std::collections::BinaryHeap;
use std::cmp::Reverse;

fn top_k(items: &[i32], k: usize) -> Vec<i32> {
    let mut heap: BinaryHeap<Reverse<i32>> = BinaryHeap::new();
    for &x in items {
        heap.push(Reverse(x));
        if heap.len() > k { heap.pop(); }
    }
    heap.into_sorted_vec().into_iter().map(|Reverse(v)| v).collect()
}

fn main() {
    // Max-heap
    let mut heap = BinaryHeap::new();
    for x in [3,1,4,1,5,9,2,6] { heap.push(x); }
    println!("Max: {:?}", heap.peek());
    let sorted: Vec<_> = heap.into_sorted_vec();
    println!("Sorted ascending: {sorted:?}");

    // Min-heap using Reverse
    let mut min_heap: BinaryHeap<Reverse<i32>> = BinaryHeap::new();
    for x in [3,1,4,1,5,9] { min_heap.push(Reverse(x)); }
    while let Some(Reverse(x)) = min_heap.pop() { print!("{x} "); }
    println!();

    // Top-3
    println!("Top-3 of [1..10]: {:?}", top_k(&(1..=10).collect::<Vec<_>>(), 3));
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn max_heap() {
        let h: BinaryHeap<i32>=[3,1,4,1,5].into();
        assert_eq!(*h.peek().unwrap(), 5);
    }
    #[test] fn min_heap() {
        let mut h: BinaryHeap<Reverse<i32>> = [3,1,4,1,5].iter().map(|&x| Reverse(x)).collect();
        assert_eq!(h.pop(), Some(Reverse(1)));
    }
    #[test] fn top_k_correct() {
        assert_eq!(top_k(&[5,1,3,2,4], 3), vec![3,4,5]);
    }
}
(* OCaml: priority queue via sorted list (simple demo) *)

module PQ = struct
  type 'a t = { mutable heap: 'a list }
  let empty () = { heap=[] }
  let push pq x = pq.heap <- List.sort compare (x :: pq.heap)
  let pop pq = match List.rev pq.heap with
    | [] -> None
    | x::xs -> pq.heap <- List.rev xs; Some x
end

let () =
  let pq = PQ.empty () in
  List.iter (PQ.push pq) [3;1;4;1;5;9;2;6];
  let rec drain () = match PQ.pop pq with
    | None -> () | Some x -> Printf.printf "%d " x; drain ()
  in drain (); print_newline ()