Heap Sort Algorithm in DSA Rust - Time & Space Complexity
We want to understand how the time taken by heap sort changes as the list size grows.
How does the number of steps increase when sorting bigger lists?
Analyze the time complexity of the following heap sort code.
fn heap_sort(arr: &mut [i32]) {
let n = arr.len();
for i in (0..n / 2).rev() {
heapify(arr, n, i);
}
for i in (1..n).rev() {
arr.swap(0, i);
heapify(arr, i, 0);
}
}
fn heapify(arr: &mut [i32], n: usize, i: usize) {
let mut largest = i;
let left = 2 * i + 1;
let right = 2 * i + 2;
if left < n && arr[left] > arr[largest] {
largest = left;
}
if right < n && arr[right] > arr[largest] {
largest = right;
}
if largest != i {
arr.swap(i, largest);
heapify(arr, n, largest);
}
}
This code builds a max heap from the array, then repeatedly swaps the largest element to the end and fixes the heap.
Look at the loops and recursive calls that repeat work.
- Primary operation: The
heapifyfunction called many times to maintain the heap property. - How many times:
heapifyruns once for each node when building the heap, then once for each element during sorting, each time possibly recursing down the tree.
As the list size grows, the number of operations grows faster than just the list size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 30 to 40 heapify steps |
| 100 | About 700 to 800 heapify steps |
| 1000 | About 10,000 to 12,000 heapify steps |
Pattern observation: The operations grow roughly proportional to n times log n, meaning it grows faster than the list size but slower than n squared.
Time Complexity: O(n log n)
This means if you double the list size, the work grows a bit more than double, but not as much as square.
[X] Wrong: "Heap sort is as slow as bubble sort because both swap elements repeatedly."
[OK] Correct: Heap sort smartly organizes data in a tree structure, reducing the number of swaps and comparisons compared to bubble sort, making it much faster for large lists.
Understanding heap sort's time complexity shows you can analyze efficient sorting methods, a key skill in many coding challenges and real projects.
"What if we changed the heap from a max heap to a min heap? How would the time complexity change?"
Practice
Solution
Step 1: Understand heap property
A heap is a special tree where the parent node is always larger (max-heap) or smaller (min-heap) than its children.Step 2: Role in Heap Sort
Heap Sort uses this property to quickly find the largest element at the root, then remove it and rebuild the heap.Final Answer:
To efficiently find and remove the largest (or smallest) element -> Option CQuick Check:
Heap = fast max/min extraction [OK]
- Thinking heap stores elements sorted permanently
- Confusing heap with simple array or list
- Assuming heap counts element frequency
Solution
Step 1: Identify Rust vector swap method
Rust's standard Vec<T> has a method called swap that exchanges elements at two indices.Step 2: Check method names
Onlyvec.swap(i, j);is valid syntax. Others are incorrect or do not exist.Final Answer:
vec.swap(i, j); -> Option BQuick Check:
Rust Vec swap method = vec.swap(i, j) [OK]
- Using assignment instead of swap for exchange
- Calling non-existent methods like swap_elements
- Confusing swap with exchange
vec = vec![4, 10, 3, 5, 1];?Solution
Step 1: Build max heap from vector
Heap Sort first builds a max heap: [10, 5, 3, 4, 1]Step 2: Extract max and reorder
Repeatedly swap root with last element and heapify, resulting in sorted ascending vector: [1, 3, 4, 5, 10]Final Answer:
[1, 3, 4, 5, 10] -> Option AQuick Check:
Heap Sort output = sorted ascending [OK]
- Confusing max heap array with sorted output
- Expecting descending order output
- Not heapifying after swaps
fn heapify(vec: &mut Vec<i32>, n: usize, i: usize) {
let mut largest = i;
let l = 2 * i + 1;
let r = 2 * i + 2;
if l < n && vec[l] > vec[largest] {
largest = l;
}
if r < n && vec[r] > vec[largest] {
largest = r;
}
if largest != i {
vec.swap(i, largest);
heapify(vec, n, largest);
}
}
fn heap_sort(vec: &mut Vec<i32>) {
let n = vec.len();
for i in (0..n/2).rev() {
heapify(vec, n, i);
}
for i in (1..n-1).rev() {
vec.swap(0, i);
heapify(vec, i, 0);
}
}Solution
Step 1: Check second for loop range
The second loop should run from n-1 down to 1 inclusive to swap root with last element correctly.Step 2: Identify loop range error
Current loop is (1..n-1).rev(), which iterates from n-2 down to 1, but Rust ranges exclude upper bound, so it misses n-1.Final Answer:
The second for loop should start from n-1, not 1 -> Option AQuick Check:
Loop must cover all indices from n-1 down to 1 [OK]
- Using wrong loop range excluding last element
- Misunderstanding Rust range bounds
- Incorrect recursive call parameters
vec = vec![3, 1, 4, 1, 5, 9, 2, 6, 5], how many times will the heapify function be called during the entire Heap Sort process?Solution
Step 1: Understand heapify calls in Heap Sort
Heapify is called during building the heap (about n/2 calls) and during sorting (about n * log n calls).Step 2: Total calls estimate
Overall, heapify is called approximately n * log n times because each extraction requires heapify down the tree height (log n).Final Answer:
Approximately n * log n calls -> Option DQuick Check:
Heapify calls scale with n log n [OK]
- Assuming heapify calls equal n only
- Thinking heapify calls are constant log n
- Confusing build heap and sort phases
Ready to prove it?
Pass the test and this pattern moves unknown
