Heap Data Structure (Data Structures & Algorithms)
Learn Heap Data Structure (Data Structures & Algorithms) step by step with clear examples and exercises.
Why This Matters
The Heap Data Structure plays a significant role in computer science as it offers efficient solutions to complex problems. Heaps are used extensively in various real-world applications such as scheduling tasks, resource allocation, and graph algorithms. By optimizing solutions with quick access to elements with high or low priority, heaps become essential in competitive programming and algorithmic challenges.
Prerequisites
To fully grasp the concept of heaps, you should have a solid understanding of:
- Basic Python syntax and control structures (if-else, for, while)
- Data structures such as lists and dictionaries
- Big O notation and time complexity analysis
- Recursion and recursive functions
- Understanding of binary trees and their properties
- Familiarity with sorting algorithms like Bubble Sort, Selection Sort, and Quick Sort
- Concepts of priority queues and their applications
Core Concept
A heap is a specialized tree data structure that maintains a specific order among its nodes, either max-heap (where each parent node is greater than or equal to its children) or min-heap (where each parent node is less than or equal to its children). Heaps are stored in an array, making them more memory-efficient and faster for operations like insertion, deletion, and finding the maximum/minimum element.
Max-Heap Properties
- Complete binary tree: Every level is completely filled except possibly the last level, and all nodes are as far left as possible.
- Heap property: For every node
i, if2*i+1and2*i+2exist, thenparent[i] >= min(child[2*i+1], child[2*i+2]).
Min-Heap Properties
- Complete binary tree: Every level is completely filled except possibly the last level, and all nodes are as far left as possible.
- Heap property: For every node
i, if2*i+1and2*i+2exist, thenparent[i] <= min(child[2*i+1], child[2*i+2]).
Common Operations on Heaps
- Insertion: Add an element to the heap and maintain the heap property by bubbling up the inserted element until it satisfies the heap property.
- Extraction-max (delete-min in min-heap): Remove the root node (highest priority element) and replace it with the last node, then restore the heap property by sinking down the new root until it satisfies the heap property.
- Extraction-min (delete-max in max-heap): Remove the root node (lowest priority element) and replace it with the last node, then restore the heap property by sinking down the new root until it satisfies the heap property.
- Heapify: Convert an arbitrary array into a heap by repeatedly applying the insertion or extraction operations until the array becomes a heap.
- Building a heap from scratch using the build-heap algorithm (also known as heapification).
- Merging two heaps into one using various methods, such as merging sorted lists or merging binary trees.
- Finding the kth smallest/largest element in a heap.
- Implementing priority queues with custom priorities.
Worked Example
Let's implement a max-heap using Python and perform some common operations like inserting elements, extracting the maximum element, comparing it with other sorting algorithms, and printing the heap.
def build_max_heap(arr):
n = len(arr)
for i in range((n // 2) - 1, -1, -1):
heapify(arr, n, i)
def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[largest] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def insert_max_heap(arr, value):
arr.append(value)
build_max_heap(arr)
def extract_max_heap(arr):
if len(arr) == 1:
return None
max_val = arr[0]
last_val = arr.pop()
arr[1], arr[0] = max_val, last_val
heapify(arr, len(arr), 1)
return max_val
def print_max_heap(arr):
for i in range(len(arr)):
print(arr[i], end=" ")
print()
def compare_with_sorting_algorithms(arr, n):
sorted_array = sorted(arr, reverse=True)
bubble_sorted = bubble_sort(arr)
selection_sorted = selection_sort(arr)
quick_sorted = quick_sort(arr)
print("Original array: ", arr)
print("Sorted array: ", sorted_array)
print("Bubble Sort: ", bubble_sorted)
print("Selection Sort:", selection_sorted)
print("Quick Sort: ", quick_sorted)
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
max_index = i
for j in range(i + 1, n):
if arr[max_index] < arr[j]:
max_index = j
arr[i], arr[max_index] = arr[max_index], arr[i]
return arr
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left, right = [], []
for i in arr:
if i < pivot:
left.append(i)
elif i > pivot:
right.append(i)
return quick_sort(left) + [pivot] + quick_sort(right)
if __name__ == "__main__":
arr = [0, 10, 5, 2, 7, 8, 9, 4, 6]
build_max_heap(arr)
print("Max Heap:")
print_max_heap(arr)
arr.append(3)
build_max_heap(arr)
print("After inserting 3:")
print_max_heap(arr)
max_val = extract_max_heap(arr)
print(f"Extracted maximum value: {max_val}")
print("Max Heap after extraction:")
print_max_heap(arr)
compare_with_sorting_algorithms(arr, len(arr))
Common Mistakes
- Forgetting to check if a child node exists before comparing its value with the parent node.
- Not updating the parent node when swapping during insertion or deletion operations.
- Implementing a min-heap instead of a max-heap, which will result in incorrect output for maximum and minimum element extraction.
- Failing to maintain the heap property after performing an operation, leading to an invalid heap.
- Not handling edge cases such as an empty or single-element heap correctly.
- Implementing inefficient sorting algorithms instead of using heaps for problems that require frequent insertion and deletion operations.
- Misunderstanding the time complexity of common heap operations, leading to suboptimal solutions.
Practice Questions
- Write a function to find the kth smallest element in a max-heap.
- Implement a min-heap using Python and perform common operations like insertion, extraction, and printing the min-heap.
- Given an array of integers, convert it into a min-heap using the heapify operation.
- Write a recursive function to find the maximum sum of any subtree in a binary tree represented as a max-heap.
- Implement a function to merge two max-heaps into a single one.
- What is the time complexity of common heap operations? Analyze each operation's worst-case scenario.
- Compare the performance of heaps with other sorting algorithms like Bubble Sort, Selection Sort, and Quick Sort for various input sizes.
- Implement a priority queue using heaps with custom priorities.
- Write a function to find the kth largest element in a max-heap.
- Given two sorted arrays, merge them into a single sorted array using heaps.
FAQ
Can we implement heaps using linked lists instead of arrays?
Yes, it is possible to implement heaps using linked lists, but using arrays is more efficient due to constant time access and insertion/deletion operations.
What is the difference between a max-heap and a min-heap?
A max-heap stores elements such that each parent node is greater than or equal to its children, while a min-heap stores elements such that each parent node is less than or equal to its children.
What is the time complexity of common heap operations?
Insertion: O(log n)
Extraction-max (delete-min in min-heap): O(log n)
Heapify: O(n log n) for bottom-up approach and O(n) for top-down approach
Building a heap from scratch using the build-heap algorithm (also known as heapification): O(n log n) for bottom-up approach and O(n) for top-down approach
Merging two heaps into one: O(n log n) if using sorting, or O(n + k log k) if using recursive method with k being the number of elements in the smaller heap.
Finding the kth smallest/largest element in a heap: O(k log n)
Can we implement priority queues using other data structures like sorted arrays or binary search trees?
Yes, it is possible to implement priority queues using sorted arrays and binary search trees, but heaps provide better performance for frequent insertion and deletion operations.