Overview
A binary heap (array-backed, complete tree) supports efficient insert and delete/extract by restoring heap-order locally while preserving the shape. The two fundamental primitives are:
-
Sift-up (a.k.a. bubble-up/percolate-up): used after insert or increaseKey (max-heap) to move a too-large child upward until the parent is >= it.
-
Sift-down (a.k.a. bubble-down/percolate-down): used after extract or delete or decreaseKey (max-heap) to push a too-small parent downward toward its proper place.
Both operations take O(log n) time, touching at most one node per level. See Heaps — Overview for properties and Heapify for O(n) bulk build.
Structure Definition
We assume a max-heap and 0-based array A[0..n-1]:
-
parent(i) = (i - 1) // 2(fori > 0) -
left(i) = 2*i + 1 -
right(i) = 2*i + 2 -
Heap-order:
A[i] >= A[left(i)]andA[i] >= A[right(i)]if children exist. (For a min-heap, reverse the comparisons.)
Leaves live at indices floor(n/2) .. n-1. The last internal node is floor(n/2) - 1.
Tip
Use the pull-up pattern to reduce swaps: carry a temporary
xand move larger children upward until the right spot forxis found; then assign once.
Core Operations
Sift-up (max-heap)
function SIFT_UP(A, i):
x = A[i]
while i > 0:
p = (i - 1) // 2
if A[p] >= x: break
A[i] = A[p] // pull parent down
i = p
A[i] = xSift-down (max-heap)
function SIFT_DOWN(A, n, i): // n = current heap size (exclusive)
x = A[i]
while true:
L = 2*i + 1
R = 2*i + 2
if L >= n: break // no children
c = L
if R < n and A[R] > A[L]:
c = R // choose larger child
if A[c] <= x: break // already in place
A[i] = A[c] // pull child up
i = c
A[i] = xInsert (push)
Append the new key at A[n], increment size, then sift-up from that index.
function INSERT(A, n, key): // returns new size
A[n] = key
SIFT_UP(A, n)
return n + 1ExtractMax (pop)
Swap the root with the last element, decrement size, then sift-down from the root.
function EXTRACT_MAX(A, n): // returns (max_value, new_size)
maxv = A[0]
A[0] = A[n-1]
n = n - 1
if n > 0:
SIFT_DOWN(A, n, 0)
return (maxv, n)Delete at index i
Replace A[i] with the last element, shrink, then sift-up or sift-down depending on the new value’s relation to its parent/children.
function DELETE_AT(A, n, i): // returns new size
if i == n-1: return n-1
moved = A[n-1]
A[i] = moved
n = n - 1
p = (i - 1) // 2
if i > 0 and A[p] < A[i]:
SIFT_UP(A, i)
else:
SIFT_DOWN(A, n, i)
return nWarning
If you store handles to indices elsewhere (e.g., in a decrease-key workload), remember that
DELETE_ATmay move the last element into positioni. Update that element’s handle.
Key updates (priority changes)
-
increaseKey(i, newKey) on a max-heap: set
A[i]=newKey, then sift-up(i). -
decreaseKey(i, newKey) on a max-heap: set
A[i]=newKey, then sift-down(i). For a min-heap, swap the directions (increase → sift-down, decrease → sift-up).
Example or Trace
Start with an empty max-heap. Insert the sequence 8, 3, 10, 1, 6, 14, 4, 7, 13.
After inserts (show key steps):
-
Insert
8:[8]. -
Insert
3: append → sift-up (no swap) →[8,3]. -
Insert
10: append → sift-up: swap with8→[10,3,8]. -
Insert
1: append → parent3ok →[10,3,8,1]. -
Insert
6: append → parent3<6→ swap →[10,6,8,1,3]. -
Insert
14: append → sift-up past10→[14,6,10,1,3,8]. -
Insert
4:[14,6,10,1,3,8,4](no swaps). -
Insert
7:[14,7,10,6,3,8,4,1](sift-up 7 past 6). -
Insert
13: append → sift-up over7, then14stops →[14,13,10,7,3,8,4,1,6].
Now EXTRACT_MAX twice:
-
Extract #1: swap root with last (6), shrink, sift-down from 0:
[13,7,10,6,3,8,4,1]→ compare children (7,10) choose 13? Actually root=6: swap with13? Careful: after swap the array pre-sift is[6,13,10,7,3,8,4,1]. Sift-down: compare 13 and 10 → move 13 up, then compare 7 and 3 vs6→ move 7 up, place6: Result:[13,7,10,6,3,8,4,1]. -
Extract #2: swap root with last (1), shrink, sift-down from 0: Pre-sift:
[1,7,10,6,3,8,4]→ move 10 up, compare children of index 2 (8,4) → move 8 up, place 1: Result:[10,7,8,6,3,1,4].
Complexity and Performance
-
Insert: O(log n) due to at most one swap (or assignment) per level.
-
Extract/Delete root: O(log n) via a single sift-down path.
-
Delete arbitrary index: O(log n) by one sift-up or sift-down (never both when implemented as above).
-
Space: O(1) auxiliary; array holds the heap.
-
Constants: The pull-up variant reduces writes and branches, improving practical throughput.
Implementation Notes or Trade-offs
-
Max vs Min heaps. Swap comparison directions consistently; a single wrong comparator corrupts invariants.
-
Stability and duplicates. Heaps are not stable. To break ties deterministically, store
(key, tie_id)wheretie_idincreases over time. -
Handles for decrease-key. If external code needs
decreaseKeyby handle, store an index back-pointer inside the payload or keep a parallel maphandle -> index, updating it after swaps. -
Bounds & off-by-one. Always guard child indices (
L < n,R < n). The last internal node isfloor(n/2)-1, notfloor(n/2). -
Bulk construction. Prefer Heapify to build from an existing array in O(n) rather than
ninserts (O(n log n)).
Common Pitfalls or Edge Cases
Warning
Using array length instead of heap size. When the heap lives in a prefix of
A, pass the current heap sizentoSIFT_DOWN. Reading into the inactive suffix breaks correctness.
Warning
Deleting without repair. After replacing
A[i]with the last element, you must choose one direction: ifA[i]grew (greater than parent), sift-up; otherwise sift-down. Doing both wastes time and may overshoot.
Warning
Mutating keys in place. Changing
A[i]without a follow-up sift-up/down silently violates the heap property.
Warning
Incorrect tie-handling. With equal keys, strict
>/<vs>=/<=determines whether equal nodes move. Be consistent across all operations.
Warning
Comparator inconsistencies. Custom comparators must be a consistent strict weak order; otherwise the heap can oscillate or loop.
Applications
-
Priority queues: task dispatchers, schedulers, event loops.
-
Graph algorithms: pick the next smallest tentative distance in Dijkstra’s Algorithm.
-
Top-k / streaming: maintain a size-
kheap and compare new items to the root. -
Heapsort: build once, then repeatedly extract to grow a sorted suffix (see Heapsort).
Summary
Binary heap updates hinge on two local repairs: sift-up and sift-down. With a complete-tree array layout, inserts append and move up; deletes/extracts swap with the last element and move down. Each operation visits O(log n) nodes, uses O(1) extra space, and—when implemented with pull-up and careful bounds—delivers fast, predictable priority-queue performance.