How does heapify work, and how can it build a heap in linear time?
A heap is stored as a complete binary tree in a plain array, so the children of the node at index i live at two i plus one and two i plus two. Heapify works from the bottom up: start at the last non-leaf node and sift each one downwards, and by the time you reach the root the whole array satisfies the heap invariant.
It is linear rather than quadratic because most of the nodes are near the bottom and have almost nothing to sift, and the total amount of sifting is proportional to the number of nodes.
def sift_down(data, start, n):
root = start
while True:
child = 2 * root + 1
if child >= n:
return
if child + 1 < n and data[child + 1] < data[child]:
child += 1
if data[child] >= data[root]:
return
data[root], data[child] = data[child], data[root]
root = child
def heapify(values):
data = list(values)
n = len(data)
for i in range(n // 2 - 1, -1, -1):
sift_down(data, i, n)
return data
print(heapify([5, 3, 8, 1, 2, 7]))The output is 1, 2, 7, 3, 5, 8. The first element is the smallest, and every parent is no greater than its children, which is the min heap invariant.
The mechanism is the bottom up order. The last non-leaf node is at index n divided by two minus one, and sifting from there means that when you reach a node, both of its subtrees are already valid heaps, so the single sift down repairs the whole subtree under it.
The trade-off to name is that pushing every element into an empty heap one at a time is logarithmic per element and therefore linear in total as well, but with a much worse constant because of the repeated sifting. The classic interview trap is asking whether heapify is linear or logarithmic: building a heap by repeated pushes is linear overall but does more work than heapify does.