What does an indexed heap's position map buy you when a queued item's priority changes?
answer
- a heap can only see its minimum
- how do you find an arbitrary element?
- keep a second structure beside the array
- identity to current index, kept in sync
- lookup O(1), then one sift
basics
~20 sAn indexed heap maps each element's identity to its current array slot, so reprioritizing a queued element costs O(log n): O(1) to locate it, then one sift. A plain heap must scan O(n) to find it first.
solid answer
~50 sA heap's contract is "give me the minimum" — it offers no way to find an arbitrary element, because heap order says nothing about where a given key sits. So changing a queued element's priority in a plain heap means an O(n) linear scan of the array to locate it, then an O(log n) sift. An indexed heap adds a second structure alongside the array: a position map from a stable element id to that element's current index, maintained on every positional write. Now decrease-key is `i = pos[id]`, lower the key, `sift_up(i)` — O(1) lookup plus O(log n) repair. In a build scheduler where a queued job's deadline can be pulled forward, that is the difference between O(n) and O(log n) per reprioritization. The price is O(n) extra memory, two map writes on every swap, and a requirement that elements carry distinct identities.
go deeper
Know that a heap only gives you the minimum and has no lookup by element. Be able to say that finding a specific queued item in the array means scanning it, which is O(n).
Explain the position map as identity-to-index, state the O(1) lookup plus O(log n) sift for decrease-key, and get the direction right: lowering a key sifts up, raising it sifts down.
Weigh it against the simpler options — scan and sift, batch rebuild, or superseded-entry skipping — and choose from measured reprioritization frequency and queue size rather than reaching for the fancier structure by default.
Own the decision that a hand-maintained position map is a permanent tax on every sift and a new invariant the team must defend in review and in tests. Require evidence the queue is on the critical path before accepting it.
## The gap in a plain heap's contract A binary heap supports `insert`, `peek-min` and `extract-min`, all resting on one invariant: every parent is no greater than its children. That invariant deliberately says **nothing** about where any particular key lives. Two elements with unrelated keys can be anywhere relative to each other, so there is no way to navigate to a specific element — the only entry point is the root. That is fine until a queued element's priority changes. Consider a build scheduler holding 50,000 queued jobs keyed by deadline. A release gets promoted, so one queued job's deadline jumps from tomorrow to ten minutes from now. The heap has that job somewhere in its array, and to change its key you must first find it: a linear O(n) scan. The repair itself is cheap — one sift-up — but the search dominates and turns a logarithmic structure into a linear one for the operation that the scheduler performs most often. ## What the indexed variant adds An **indexed heap** (also called an addressable or index-min priority queue) pairs the heap array with a **position map**: `pos[id] = the current index of the element with that id` If the ids are dense small integers — job slots numbered 0..n-1, for example — `pos` is just another array, so lookups are a single indexed read. If ids are opaque tokens, it is a hash-based map with expected O(1) lookup. The heap array itself is unchanged. The operations become: - **insert(id, key)** — append, record `pos[id]`, sift up. - **extract-min** — take index 0, move the last element into it, drop the trailing slot, erase the old id's `pos` entry, sift down. - **decrease-key(id, k)** — `i = pos[id]`; write the smaller key at `a[i]`; `sift_up(i)`. O(log n). - **increase-key(id, k)** — same lookup, but `sift_down(i)`, because a bigger key sinks. - **change-key / delete(id)** — lookup, then repair in whichever direction is needed; a general change may need to try both, and a deletion moves the last element into the hole and then sifts up **or** down depending on how it compares with its parent. - **contains(id)** — O(1), which a plain heap cannot answer at all. ## The invariant that makes it work One sentence governs the whole structure: > For every index `i` in the heap array, `pos[a[i].id] == i`. Every routine that moves an element — swap during a sift, the append in insert, the last-element relocation in extract-min or delete — must re-establish it for **each** element it moved. There is no partial version of this invariant that is still useful. ## What it costs - **Memory**: an extra entry per element, so O(n) additional space, plus whatever overhead the map implementation carries. - **Constant factors**: every swap inside a sift now performs two extra writes. On an extract-heavy loop that is a measurable slowdown of the operations you did not want to slow down. - **Identity**: the heap can no longer hold bare values. Elements need stable, distinct ids — two jobs with the same deadline must still be distinguishable, or one will overwrite the other's map entry. - **Surface area**: more code paths that must all maintain the same invariant, which is exactly where this structure's characteristic bugs come from. ## When you do not need it The honest alternatives, when reprioritization is rare: 1. **Scan and sift.** O(n) per change is fine if the queue holds hundreds of items or changes arrive once a minute. Simplicity has value. 2. **Rebuild.** If a batch of priorities changes at once, discarding and rebuilding bottom-up is Θ(n) for the whole batch — cheaper than n individual O(log n) updates once the batch is large. 3. **Push a revised copy and skip superseded entries on the way out.** No position map at all, at the cost of a heap that holds more entries than there are live jobs, so its size is bounded by total updates rather than by the live set. The indexed heap earns its complexity when reprioritization is a first-class, frequent operation on a large queue — a scheduler whose deadlines move while jobs wait is exactly that shape. Reach for it because a measured operation is on the critical path, not because it appears in the textbook chapter on priority queues.
- What exactly is the invariant the position map must satisfy, and when must it be re-established?For every array index i, `pos[a[i].id] == i`. It must be restored after every positional write: each swap during a sift (for both elements moved), the append in insert, and the relocation of the last element into a hole during extract-min or delete. Any code path that writes the array without updating the map breaks it.
- Why can't decrease-key just lower the key in place and stop there?Lowering a key can violate heap order against the parent, which is precisely the condition sift-up repairs. Skipping the sift leaves an element smaller than its parent, so the structure may report a minimum that is not the minimum. Symmetrically, raising a key requires sift-down instead.
- Ten thousand queued jobs all get new deadlines in one batch. Is per-item decrease-key still the right move?No. Ten thousand individual updates cost O(n log n) in total, while writing the new keys and rebuilding bottom-up is Theta(n). Once the batch is a large fraction of the queue, rebuild wins outright — and it also sidesteps the direction problem when some keys rose and others fell.
- What breaks if two queued elements share an id?The map is keyed by id, so the second insert overwrites the first's position entry. One element becomes unreachable for reprioritization while the other's entry points at the wrong slot, and a later update silently mutates the wrong element. Ids must be unique per live element, and equal keys must still be distinguishable.
A heap alone is a stack of parcels sorted only by which one is on top. The position map is the courier's tracking table: it tells you exactly which shelf a parcel is on, so re-sorting one parcel doesn't mean unpacking the whole depot.
saying these in an interview costs you the question
- Thinks a heap can locate an arbitrary element in O(log n)
- Says decrease-key just overwrites the key without sifting
- Ignores the extra memory and per-swap map writes
- Forgets an increase-key must sift down, not up
- Assumes bare values work without stable element identities