Introduction

Heap is complete binary tree maintaining heap property: every parent satisfies ordering constraint relative to children. Min-heap: parent <= children. Max-heap: parent >= children. Enables O(log n) insertion/deletion while maintaining order, O(1) minimum/maximum access.

Contrast with binary search trees: BST enables O(log n) search for arbitrary values, but finding min/max requires traversal. Heaps sacrifice arbitrary search capability (no O(log n) search) for guarantee on extrema.

Cornerstone of priority queues: always access highest-priority (min/max) element. Applications: Dijkstra's algorithm (expand lowest-cost node), Huffman coding (combine lowest-frequency nodes), operating system scheduling (execute highest-priority task).

"Heaps elegantly solve the priority queue problem: maintain extrema while enabling efficient updates. Their array representation makes them both memory-efficient and cache-friendly." -- Data structure design

Heap Property and Completeness

Heap Property

Heap ordering property: parent-child relationship constraint. Min-heap: key(parent) <= key(child). Max-heap: key(parent) >= key(child).

Critical: only parent-child ordering guaranteed. Siblings unordered. Means: minimum at root (min-heap), maximum at root (max-heap).

Completeness

Complete binary tree: all levels filled except possibly last (filled left-to-right). Constraint on structure: enables array representation without gaps. Height = ceil(log n) for n elements.

Complete heap (5 elements):
 1
 / \
 2 3
 / \
 4 5

Not complete: 5 right child of 2? Would require 6 elements total.

Height

Height = O(log n). All operations exploit this: insertion/deletion bubble up/down at most height edges = O(log n).

Min-Heaps and Max-Heaps

Min-Heap

Minimum element at root. Used for: priority queue (lower priority value = higher priority), Dijkstra's algorithm (expand lowest-cost vertex).

Max-Heap

Maximum element at root. Used for: priority queue (higher priority value = higher priority), heap sort (extract elements descending).

Conversion

Min-heap to max-heap: negate all values, or use comparator (reverse comparison). Structurally identical, just different comparison direction.

Example Min-Heap

 1
 / \
 2 3
 / \ /
 4 5 6

Heap property: 1 <= 2, 1 <= 3, 2 <= 4, 2 <= 5, 3 <= 6 (satisfied)
Minimum: 1 (at root)

Array Representation of Heaps

Index Mapping

Store heap elements in array index 0 to n-1. Complete tree structure maps to array naturally:

Node at index i:
 Left child: 2*i + 1
 Right child: 2*i + 2
 Parent: (i - 1) / 2 (integer division)

Example (1-indexed for clarity):
Index: 1 2 3 4 5 6 7
Element: [1, 2, 3, 4, 5, 6, 7]
 1
 / \
 2 3
 / \ / \
 4 5 6 7

Advantages

  • No explicit pointers (array compact)
  • Cache-friendly (contiguous memory)
  • Parent/child access O(1) (arithmetic)
  • Space-efficient (no node objects)

Array Resizing

If array full, allocate larger array, copy elements. Amortized O(1) per operation with doubling.

Insert Operation and Bubble Up

Algorithm

def insert(heap, value):
 heap.append(value) # Add to end
 bubble_up(heap, len(heap) - 1)

def bubble_up(heap, index):
 while index > 0:
 parent_index = (index - 1) // 2
 if heap[index] < heap[parent_index]: # Min-heap comparison
 swap(heap, index, parent_index)
 index = parent_index
 else:
 break

Example

Insert 0 into min-heap [1, 2, 3, 4, 5]:
Initial: [1, 2, 3, 4, 5, 0]
 1
 / \
 2 3
 / \ /
 4 5 0 (0 at index 5)

Bubble up:
0 < parent 2, swap → [1, 0, 3, 4, 5, 2]
 1
 / \
 0 3
 / \ /
4 5 2

0 < parent 1, swap → [0, 1, 3, 4, 5, 2]
 0
 / \
 1 3
 / \ /
4 5 2

Done: 0 at root (min-heap property restored)

Time Complexity

Bubble up at most height steps = O(log n). Each step: comparison, swap. Total O(log n).

Delete Min/Max and Bubble Down

Delete Root

def delete_min(heap):
 if len(heap) == 0:
 raise IndexError("Heap empty")
 min_value = heap[0]
 heap[0] = heap[-1] # Move last to root
 heap.pop() # Remove last
 bubble_down(heap, 0)
 return min_value

def bubble_down(heap, index):
 while True:
 smallest = index
 left_child = 2 * index + 1
 right_child = 2 * index + 2

 if left_child < len(heap) and heap[left_child] < heap[smallest]:
 smallest = left_child
 if right_child < len(heap) and heap[right_child] < heap[smallest]:
 smallest = right_child

 if smallest != index:
 swap(heap, index, smallest)
 index = smallest
 else:
 break

Example

Delete min from [1, 2, 3, 4, 5, 6]:
Min: 1
Move last (6) to root: [6, 2, 3, 4, 5]
 6
 / \
 2 3
 / \
4 5

Bubble down 6:
Smaller child: 2 (left), swap → [2, 6, 3, 4, 5]
 2
 / \
 6 3
 / \
4 5

Smaller child: 4, swap → [2, 4, 3, 6, 5]
 2
 / \
 4 3
 / \
6 5

Done: 2 at root, heap property restored

Time Complexity

Bubble down at most height steps = O(log n).

Heapify: Building Heaps from Arrays

Naive Approach

Insert each element: n insertions * O(log n) per insert = O(n log n).

Efficient Heapify

def heapify(array):
 # Start from last non-leaf node, bubble down
 for i in range(len(array) // 2 - 1, -1, -1):
 bubble_down(array, i)

Time: O(n)

Intuition: bottom-up approach. Leaf nodes already satisfy heap property.
Bubble down non-leaves. Total work: O(n) not O(n log n).

Analysis

Why O(n)? Worst case: bubble down O(log n) at root. But most nodes near bottom (leaves), take ~0 bubble-down steps. Average work per node: O(1). Total: O(n).

Build Heap from Data

Heapify is critical: given unordered array, build heap in O(n) instead of O(n log n). Foundation for heap sort.

Heap Sort Algorithm

Algorithm

def heap_sort(array):
 # Build max-heap in-place
 for i in range(len(array) // 2 - 1, -1, -1):
 bubble_down_max(array, i)

 # Extract elements from heap
 for i in range(len(array) - 1, 0, -1):
 swap(array, 0, i) # Move root (max) to position i
 bubble_down_max(array, 0, i) # Heapify array[0:i]

Time: O(n log n), Space: O(1) in-place

Example

Sort [3, 1, 4, 1, 5]:
Build max-heap: [5, 3, 4, 1, 1]
 5
 / \
 3 4
 / \
1 1

Extract: swap 5 with last (index 4), heapify [4, 3, 1, 1] | 5
→ [4, 3, 1, 1, 5]
→ [4, 1, 1, 3] | 3, 5
→ [3, 1, 1, 4] | 4, 5
→ [1, 1, 3] | 3, 4, 5
→ [1, 1] | 1, 3, 4, 5
→ [1] | 1, 1, 3, 4, 5

Sorted: [1, 1, 3, 4, 5]

Comparison with QuickSort

Heap sort: guaranteed O(n log n), not adaptive (same performance sorted/unsorted). QuickSort: O(n log n) average, O(n^2) worst, faster in practice (cache-friendly, less data movement). Most systems prefer QuickSort unless worst-case guarantee needed.

Priority Queues Using Heaps

Implementation

class PriorityQueue:
 def __init__(self):
 self.heap = []

 def enqueue(self, value, priority):
 self.heap.append((priority, value))
 bubble_up(self.heap, len(self.heap) - 1)

 def dequeue(self):
 if not self.heap:
 raise IndexError("Queue empty")
 min_priority, value = self.heap[0]
 self.heap[0] = self.heap[-1]
 self.heap.pop()
 bubble_down(self.heap, 0)
 return value, min_priority

 def peek(self):
 if not self.heap:
 raise IndexError("Queue empty")
 return self.heap[0][1]

Operations

Enqueue: O(log n). Dequeue: O(log n). Peek: O(1). No ordering required during enqueue (unlike sorted array), yet dequeue returns minimum in O(log n).

Dijkstra's Algorithm with Priority Queue

Priority queue enables efficient Dijkstra: expand lowest-cost vertex. O((V + E) log V) vs. O(V^2) without priority queue.

Time and Space Complexity

Operation Time Space
Insert O(log n) O(1)
Delete Min/Max O(log n) O(1)
Peek Min/Max O(1) O(1)
Build Heap O(n) O(1)
Heap Sort O(n log n) O(1)

Space

Heap elements stored in array (no additional structure). Auxiliary space O(1) for heap operations (recursion depth O(log n) but not counted as auxiliary in many analyses).

Heap Variants: Fibonacci, Binomial

Fibonacci Heaps

Advanced structure: decrease-key O(1) amortized (vs. O(log n) binary heap). More complex implementation. Used in Dijkstra for dense graphs (improves complexity to O(V log V + E)).

Binomial Heaps

Forest of binomial trees. Merge heaps efficiently O(log n). Decrease-key O(log n). Alternative to Fibonacci when merge or decrease-key frequent.

Ternary Heaps

Each node up to 3 children. Reduces tree height slightly (log_3 n vs. log_2 n), decreases bubble-down operations. Marginal improvement, rarely used.

Practical Choice

Binary heaps: simplest, cache-friendly, sufficient for most applications. Fibonacci/binomial: complex, only beneficial for specific algorithms with many decrease-key operations.

Applications

Dijkstra's Shortest Path

Priority queue: expand lowest-cost vertex. O((V + E) log V) with heap vs. O(V^2) without.

Prim's Minimum Spanning Tree

Greedy: add cheapest edge connecting tree to non-tree. Priority queue tracks candidate edges.

Huffman Coding

Repeatedly combine two lowest-frequency nodes. Min-heap enables O(n log n) algorithm.

Heap Sort

In-place sorting. O(n log n) guaranteed. Rarely used in practice (QuickSort faster average).

Load Balancing

Assign task to server with minimum load. Min-heap tracks server loads, update on task completion.

Event Simulation

Priority queue of events (ordered by time). Dequeue next event, process, enqueue resulting events.

References

  • Cormen, T. H., Leiserson, C. E., Rivest, R. L., and Stein, C. "Introduction to Algorithms." MIT Press, 3rd edition, 2009.
  • Knuth, D. E. "The Art of Computer Programming, Volume 1: Fundamental Algorithms." Addison-Wesley, 3rd edition, 1997.
  • Sedgewick, R., and Wayne, K. "Algorithms, Part I." Addison-Wesley, 4th edition, 2011.
  • Fibonacci, L. "The Art of Thinking Combinatorially." American Scientist, vol. 88, 2000.
  • Fredman, M. L., and Tarjan, R. E. "Fibonacci Heaps and Their Uses." Journal of the ACM, vol. 34, 1987.