An indexed binary heap supports both O(1)
lookup of the in-heap index and
O(log n)
update of the element value, for any element in the heap.
This is achieved by augmenting each heap operation with the corresponding
updates to an external index table.
This data structure often proves useful when implementing algorithms with a
greedy flavor.
For example, each iteration in Dijkstra's algorithm involves
(a) finding the node x
with the shortest "tentative distance", and
(b) updating the tentative distances of x
's neighbors.
A normal binary heap suffices for (a), but not quite for (b), as the latter
calls for both looking up the in-heap index of each neighbor node and modifying
elements deep inside the heap without destroying the heap order.
These two problems can be solved simultaneously using an indexed heap.
The iheap
interface resembles that of std::make/push/pop_heap
, taking a
pair of RandomIt
s into a container. There are a few differences:
-
For a heap with element values in
T
, theRandomIt
iterator should havevalue_type
beingstd::pair<T, K>
(instead of justT
). Here,K
is the type of the key of each element, generalizing an integer index in the simplest case.In the context of Dijkstra's algorithm over a 2D lattice,
T = int
is the tentative distance to the initial node, whileK = std::pair<int, int>
gives the node coordinates. -
Each heap function takes as an additional argument a functor
indexer
. The expected signature isint& indexer(K key);
For each key, the indexer should return a mutable reference to the corresponding entry in the in-heap index table allocated elsewhere.
For the Dijkstra's algorithm example above,
indexer({x, y})
should return a reference to the entry in the heap index table for the node at{x, y}
, meaning that the node{x, y}
currently resides inheap[indexer({x, y})]
.More generally, the following invariant is maintained for all in-heap elements
{value, key}
by all operations:heap[indexer(key)] == {value, key}
Popping an element from the heap sets its
indexer(key)
to-1
. -
The comparator functor should compare two instances of
std::pair<T, K>
rather than just twoT
s. The expected signature isbool comp(const std::pair<T, K>& a, const std::pair<T, K>& b);
instead of just
bool comp(const T& a, const T& b);
as is the case forstd::
heaps. The more general comparator overstd::pair<T, K>
makes it possible to explicitly resolve equality overT
by further comparing overK
.Similar to the
std::
case, the comparator here defaults tostd::less<std::pair<T, K>>()
, corresponding to a max-indexed-heap. -
The
O(1)
heap index lookup makes it straightforward to update the value of any heap element (as specified by a key) on the fly and "reheapify" accordingly, with a total time complexity of justO(log n)
. This functionality is provided byupdate
. This function returnstrue
on a successful update, andfalse
when the input key is not in the heap. Similarly, we also provide a functionpop_key
that pops a specific key from the heap, returningtrue
if successful.