Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Indexed Priority Queue — Mutable Priorities

An indexed priority queue combines a heap with a map from item identity to heap position. It supports changing or removing an arbitrary item’s priority without scanning the entire heap.

When to use it

Use it for schedulers with cancellation, Dijkstra variants with decrease-key, order queues with priority updates, and simulations whose future events change.

Use an ordinary BinaryHeap when priorities never change after insertion.

1. Invariant

heap[index].key = K  ⇔  positions[K] = index

Every heap swap must update both position entries. After a priority change, bubble the item upward or downward to restore heap order.

use std::collections::HashMap;

struct IndexedHeap {
    heap: Vec<(String, i32)>,
    positions: HashMap<String, usize>,
}

impl IndexedHeap {
    fn new() -> Self { Self { heap: Vec::new(), positions: HashMap::new() } }

    fn swap(&mut self, a: usize, b: usize) {
        self.heap.swap(a, b);
        self.positions.insert(self.heap[a].0.clone(), a);
        self.positions.insert(self.heap[b].0.clone(), b);
    }

    fn push(&mut self, key: String, priority: i32) {
        assert!(!self.positions.contains_key(&key));
        let mut index = self.heap.len();
        self.heap.push((key.clone(), priority));
        self.positions.insert(key, index);
        while index > 0 {
            let parent = (index - 1) / 2;
            if self.heap[parent].1 >= self.heap[index].1 { break; }
            self.swap(parent, index);
            index = parent;
        }
    }

    fn update(&mut self, key: &str, priority: i32) {
        let mut index = self.positions[key];
        let old = self.heap[index].1;
        self.heap[index].1 = priority;
        if priority > old {
            while index > 0 {
                let parent = (index - 1) / 2;
                if self.heap[parent].1 >= self.heap[index].1 { break; }
                self.swap(parent, index);
                index = parent;
            }
        } else {
            loop {
                let left = index * 2 + 1;
                if left >= self.heap.len() { break; }
                let right = left + 1;
                let child = if right < self.heap.len()
                    && self.heap[right].1 > self.heap[left].1 { right } else { left };
                if self.heap[index].1 >= self.heap[child].1 { break; }
                self.swap(index, child);
                index = child;
            }
        }
    }
}

fn main() {
    let mut queue = IndexedHeap::new();
    queue.push("compile".into(), 2);
    queue.push("deploy".into(), 5);
    queue.update("compile", 8);
    assert_eq!(queue.heap[0], ("compile".into(), 8));
}

Lookup is expected O(1); insertion and priority change are O(log n). The map/heap synchronization invariant is the structure’s main source of bugs.

2. What you should internalize

An ordinary heap exposes only its root. Adding an index map makes arbitrary items addressable, but every structural change must maintain two coordinated representations.

Exercise

Implement removal by key. After every swap and removal, assert that each heap entry’s index agrees with positions and that the heap-order invariant holds.