Collection Adapters and Specialized Types

BinaryHeap vs. Priority Queue Implementations

While std::collections::BinaryHeap<T> is the standard priority queue for single-ended push, peek, and pop workloads, some algorithms (such as Dijkstra's shortest path with decrease_key, double-ended priority queues, or key-addressable cancellation) require comparing BinaryHeap against BTreeSet or a lazy-deletion BinaryHeap.

Comparing Priority Queue Approaches in Standard Rust

Capability BinaryHeap<T> BTreeSet<(Priority, Id)> Sorted Vec<T>
Inspect maximum (peek / last) $O(1)$ $O(\log n)$ $O(1)$
Inspect both min and max No ($O(1)$ for one end only) $O(\log n)$ (first & last) $O(1)$ (first & last)
Push new item $O(\log n)$ (amortized $O(1)$) $O(\log n)$ $O(n)$ (shift)
Pop highest priority $O(\log n)$ $O(\log n)$ (pop_last) $O(1)$ (pop from back)
Update / cancel arbitrary queued item (decrease_key / remove) Not directly supported (use lazy deletion) $O(\log n)$ (remove(&old) + insert(new)) $O(n)$
Duplicate items allowed? Yes Via unique tie-breaker (prio, id) Yes
Memory layout Single contiguous Vec<T> Multi-element B-Tree nodes Single contiguous Vec<T>

Pattern 1: Lazy Deletion in BinaryHeap (Dijkstra's Algorithm)

Standard BinaryHeap<T> does not provide a handle to decrease the key of an arbitrary interior element in place. In practice, pushing a new (Reverse(shorter_dist), node) entry into the BinaryHeap and ignoring stale entries when popped (if d > dist[u] { continue; }) is faster than maintaining an indexed heap handle array because BinaryHeap sits in a single contiguous Vec:

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

fn dijkstra(adj: &[Vec<(usize, u64)>], start: usize) -> Vec<u64> {
    let mut dist = vec![u64::MAX; adj.len()];
    let mut pq: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();

    dist[start] = 0;
    pq.push(Reverse((0, start)));

    while let Some(Reverse((d, u))) = pq.pop() {
        // Lazy deletion check: skip stale heap entries whose distance was already improved!
        if d > dist[u] {
            continue;
        }

        for &(v, weight) in &adj[u] {
            let next_d = d + weight;
            if next_d < dist[v] {
                dist[v] = next_d;
                pq.push(Reverse((next_d, v)));
            }
        }
    }
    dist
}

fn main() {
    let graph = vec![
        vec![(1, 10), (2, 3)], // 0 -> 1 (10), 0 -> 2 (3)
        vec![(3, 2)],          // 1 -> 3 (2)
        vec![(1, 4), (3, 8)],  // 2 -> 1 (4),  2 -> 3 (8)
        vec![],
    ];

    println!("Shortest distances from 0: {:?}", dijkstra(&graph, 0));
}
Shortest distances from 0: [0, 7, 3, 9]

Pattern 2: Key-Addressable Priority Queue with BTreeSet + HashMap

When queued jobs can be canceled or reprioritized by job_id before execution (and cannot leave stale entries accumulating in memory), pair a HashMap<JobId, Priority> (to look up the current priority of a job) with a BTreeSet<(Priority, JobId)> (to pop the highest/lowest priority or remove (old_prio, job_id) in $O(\log n)$ time):

use std::collections::{BTreeSet, HashMap};

struct UpdatablePriorityQueue {
    by_id: HashMap<&'static str, u32>,
    ordered: BTreeSet<(u32, &'static str)>,
}

impl UpdatablePriorityQueue {
    fn new() -> Self {
        Self {
            by_id: HashMap::new(),
            ordered: BTreeSet::new(),
        }
    }

    fn upsert(&mut self, job_id: &'static str, new_priority: u32) {
        if let Some(old_priority) = self.by_id.insert(job_id, new_priority) {
            self.ordered.remove(&(old_priority, job_id));
        }
        self.ordered.insert((new_priority, job_id));
    }

    fn pop_highest(&mut self) -> Option<(&'static str, u32)> {
        let (prio, id) = self.ordered.pop_last()?;
        self.by_id.remove(id);
        Some((id, prio))
    }
}

fn main() {
    let mut pq = UpdatablePriorityQueue::new();
    pq.upsert("backup", 10);
    pq.upsert("compile", 50);
    pq.upsert("backup", 99); // Reprioritize "backup" from 10 -> 99 in O(log n)

    println!("1st popped: {:?}", pq.pop_highest());
    println!("2nd popped: {:?}", pq.pop_highest());
}
1st popped: Some(("backup", 99))
2nd popped: Some(("compile", 50))