ExamplesBy LevelBy TopicLearning Paths
354 Advanced

354: BinaryHeap Priority Queue

Functional Programming

Tutorial

The Problem

Many algorithms need to repeatedly extract the element with the highest (or lowest) priority: Dijkstra's shortest path, A* search, Huffman coding, task schedulers, and event-driven simulations. A priority queue with O(log n) insert and O(log n) extract-max is the standard data structure for these needs. Rust's BinaryHeap implements a max-heap — the largest element is always at the top. The Reverse<T> wrapper flips the comparison, turning the max-heap into a min-heap. This covers both "top N largest" and "top N smallest" queries efficiently.

🎯 Learning Outcomes

  • • Build a BinaryHeap<T> from a slice and extract elements with heap.pop() (always max)
  • • Use Reverse<T> to create a min-heap without a separate data structure
  • • Use into_sorted_vec() for O(n log n) heap sort producing ascending order
  • • Understand that building a heap from n elements is O(n) via heapify
  • • Recognize when BinaryHeap is appropriate vs BTreeMap/sort
  • • Use BinaryHeap as a k-way merge structure for merging sorted streams
  • Code Example

    #![allow(clippy::all)]
    //! # BinaryHeap Priority Queue
    //! Max-heap for priority queue operations.
    
    use std::cmp::Reverse;
    use std::collections::BinaryHeap;
    
    pub fn top_n<T: Ord + Clone>(items: &[T], n: usize) -> Vec<T> {
        let mut heap: BinaryHeap<_> = items.iter().cloned().collect();
        (0..n).filter_map(|_| heap.pop()).collect()
    }
    
    pub fn bottom_n<T: Ord + Clone>(items: &[T], n: usize) -> Vec<T> {
        let mut heap: BinaryHeap<Reverse<T>> = items.iter().cloned().map(Reverse).collect();
        (0..n)
            .filter_map(|_| heap.pop().map(|Reverse(x)| x))
            .collect()
    }
    
    pub fn heap_sort<T: Ord>(items: Vec<T>) -> Vec<T> {
        let heap: BinaryHeap<_> = items.into_iter().collect();
        heap.into_sorted_vec()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn top_3() {
            let top = top_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(top, vec![9, 6, 5]);
        }
        #[test]
        fn bottom_3() {
            let bottom = bottom_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(bottom, vec![1, 1, 2]);
        }
        #[test]
        fn sorted() {
            assert_eq!(heap_sort(vec![3, 1, 4, 1, 5]), vec![1, 1, 3, 4, 5]);
        }
    }

    Key Differences

    AspectRust BinaryHeapOCaml (functional heap)
    Default orderMax-heapDepends on leq comparator
    Min-heapReverse<T> wrapperFlip leq
    Sorted outputinto_sorted_vec() ascendingfold with pop
    Build costO(n) heapifyO(n log n) for functional
    MutabilityIn-placePersistent (new structure per op)

    OCaml Approach

    OCaml's standard library lacks a binary heap; functional priority queues use a leftist tree or pairing heap:

    (* Using the heap module from the containers library *)
    module H = CCHeap.Make(struct type t = int let leq a b = a <= b end)
    
    let top_n items n =
      let h = List.fold_left (fun h x -> H.add h x) H.empty items in
      let rec pop h acc = function
        | 0 -> List.rev acc
        | k -> match H.pop h with
          | None -> List.rev acc
          | Some (x, h') -> pop h' (x :: acc) (k - 1)
      in
      pop h [] n
    

    For imperative use, Array-based heap sort is common in competitive programming. The psq (priority search queue) package provides both priority and key-based access.

    Full Source

    #![allow(clippy::all)]
    //! # BinaryHeap Priority Queue
    //! Max-heap for priority queue operations.
    
    use std::cmp::Reverse;
    use std::collections::BinaryHeap;
    
    pub fn top_n<T: Ord + Clone>(items: &[T], n: usize) -> Vec<T> {
        let mut heap: BinaryHeap<_> = items.iter().cloned().collect();
        (0..n).filter_map(|_| heap.pop()).collect()
    }
    
    pub fn bottom_n<T: Ord + Clone>(items: &[T], n: usize) -> Vec<T> {
        let mut heap: BinaryHeap<Reverse<T>> = items.iter().cloned().map(Reverse).collect();
        (0..n)
            .filter_map(|_| heap.pop().map(|Reverse(x)| x))
            .collect()
    }
    
    pub fn heap_sort<T: Ord>(items: Vec<T>) -> Vec<T> {
        let heap: BinaryHeap<_> = items.into_iter().collect();
        heap.into_sorted_vec()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn top_3() {
            let top = top_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(top, vec![9, 6, 5]);
        }
        #[test]
        fn bottom_3() {
            let bottom = bottom_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(bottom, vec![1, 1, 2]);
        }
        #[test]
        fn sorted() {
            assert_eq!(heap_sort(vec![3, 1, 4, 1, 5]), vec![1, 1, 3, 4, 5]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn top_3() {
            let top = top_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(top, vec![9, 6, 5]);
        }
        #[test]
        fn bottom_3() {
            let bottom = bottom_n(&[3, 1, 4, 1, 5, 9, 2, 6], 3);
            assert_eq!(bottom, vec![1, 1, 2]);
        }
        #[test]
        fn sorted() {
            assert_eq!(heap_sort(vec![3, 1, 4, 1, 5]), vec![1, 1, 3, 4, 5]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Binary Heap Priority

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • K-way merge: Merge 3 sorted Vec<i32> streams efficiently using BinaryHeap<(i32, usize)> where the usize is the stream index; produce one merged sorted output.
  • Task scheduler: Implement a priority task queue where tasks have priority: u8 and name: String; process tasks in priority order (highest first) using BinaryHeap<(u8, String)>.
  • Running median: Maintain two heaps (max-heap for lower half, min-heap for upper half via Reverse) to compute the running median as each number is inserted; the median is always accessible in O(1).
  • Open Source Repos