The heapq
module doesn't define any new data type to represent heaps. A heap is simply a list (or really, any sequence), that obeys the heap invariant:
Heaps are arrays for which a[k] <= a[2*k+1]
and a[k] <= a[2*k+2]
for all k
, counting elements from 0.
In order for a list to not be a heap, it is necessary and sufficient to find an index k
for which the invariant is false.
This is impossible for empty and single-item lists, because the required list elements simply do not exist, so both are trivially heaps.
For lists with 2 or more elements, there is always at least one condition that can be false, namely a[0] <= a[1]
.
heappush
and heappop
are both documented as "maintaining the heap invariant": if each function's first argument is a heap before the function is called, it remains a heap after the function returns. (If the first argument is not a heap, their behavior is essentially undefined.)
Here are the definitions of each:
def heappush(heap, item):
"""Push item onto heap, maintaining the heap invariant."""
heap.append(item)
_siftdown(heap, 0, len(heap)-1)
The private function _siftdown
is responsible for restoring the heap invariant after appending item
could have potentially violated it.
def heappop(heap):
"""Pop the smallest item off the heap, maintaining the heap invariant."""
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
if heap:
returnitem = heap[0]
heap[0] = lastelt
_siftup(heap, 0)
return returnitem
return lastelt
The private function _siftup
is responsible for restoring the heap invariant after replacing heap[0]
with lastelt
may have violated it.
In the code you linked to, pq
is initialized to a single-item list, which as we previously noted is already a heap. Since pq
is only subsequently modified by calls to heappop
and heappush
, it remains a heap for the duration of the function call.