Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 55

Implementing Heap Sort

Recommended resources

Hello! Welcome to our next lesson on heap-based algorithms.

In our last session, we mastered an efficient, linear-time algorithm to build a heap from an unordered array using the buildHeap method. This powerful technique is the first of two key ingredients for one of the most important comparison-based sorting algorithms.

Today, you will learn to implement heap sort. We will combine the buildHeap operation with the heap's property of providing quick access to the maximum element to create an efficient, in-place sorting algorithm. Heap sort is a classic algorithm, valued for its guaranteed performance and constant space usage, making it a frequent topic in technical interviews.

1. The Two Phases of Heap Sort

Heap sort works in two distinct phases, both performed on the same array in-place:

  1. Heapify Phase: Transform the entire unsorted input array into a max-heap. We will use the efficient buildHeap algorithm we learned in the previous lesson. After this phase, the largest element in the array is guaranteed to be at the root (index 0).

  2. Sort-down Phase: Repeatedly extract the largest element from the heap and move it to its final sorted position at the end of the array.

    • Swap the root element (the current maximum) with the last element of the heap.
    • The largest element is now at the end of the array, in its correct sorted position.
    • Decrease the considered size of the heap by one, effectively "locking in" the sorted element at the end.
    • The new root element may violate the max-heap property. Restore the heap by calling heapify (or siftDown) on the root of the now smaller heap.
    • Repeat this process until the heap is empty.

Let's watch a video that visualizes this entire process.

Heap Sort Visually Explained | Sorting Algorithm | Heaps

The video 'Heap Sort Visually Explained' by ByteQuest provides a clear, modern animation of the heap sort algorithm. It correctly starts with the efficient build-heap phase and then moves to the sorting phase.

Watch from 00:24 to 02:31. The video first shows the build max heap step (which you learned last time) and then demonstrates the core sorting loop: swapping the root with the last element and calling heapify down to restore the heap property.

The key insight is that by swapping the max element with the last element of the heap, we are simultaneously moving the largest unsorted item to its final place while also using that now-vacant spot to continue the heap operations. This is what makes the algorithm in-place.


2. A Detailed Walkthrough

Let's dive deeper into the mechanics of the "sort-down" phase. The Happy Coders article provides an excellent set of diagrams that walk through this process step-by-step.

Heapsort – Algorithm, Source Code, Time Complexity

The article 'Heapsort – Algorithm, Source Code, Time Complexity' provides a rich, visual explanation that will solidify your understanding.

Focus on 'Phase 2: Sorting the Array'. Start reading from that heading and follow the diagrams. Observe how after each swap, the largest element is moved to the end, and the heap is 'repaired' using the heapify method on the root.

As you can see from the walkthrough, the process systematically places the largest elements at the end of the array, one by one, shrinking the heap from the right and growing the sorted portion from the right.

Heapify Process Visualization
A quick recap from our last lesson: the `buildHeap` process efficiently transforms an unsorted array into a max-heap by applying the `heapify` (sift-down) operation on all non-leaf nodes in a bottom-up manner.

3. Java Implementation

Now, let's translate this logic into Java code. Given your background, you'll appreciate how the algorithm's logic maps directly to a clean implementation. The main heapSort method orchestrates the two phases, relying on a helper heapify function (the siftDown you know well) to maintain the heap property.

We will again refer to the Happy Coders article, as it provides a complete and well-structured Java implementation.

Heapsort – Algorithm, Source Code, Time Complexity

This section of the article provides the complete Java code for Heap Sort.

Read the 'Heapsort Java Code Example' section carefully. Examine the sort(), buildHeap(), and heapify() methods. Notice that the heapify method is implemented iteratively using a while loop, which is slightly more space-efficient than a recursive approach as it avoids extra call stack frames.

Let's break down the essential pieces of code you just reviewed.

The heapSort method:
This is the main driver. It first calls buildHeap and then enters the sorting loop.

public void heapSort(int[] arr) {
    int n = arr.length;

    // Phase 1: Build a max-heap from the array.
    // We start from the last non-leaf node and go up to the root.
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }

    // Phase 2: One by one extract an element from heap
    for (int i = n - 1; i > 0; i--) {
        // Move current root (max element) to the end
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        // call heapify on the reduced heap of size 'i'
        heapify(arr, i, 0);
    }
}

The heapify (sift-down) method:
This is the workhorse function that maintains the max-heap property.

// To heapify a subtree rooted with node i which is an index in arr[]. 
// n is the size of the heap.
void heapify(int[] arr, int n, int i) {
    int largest = i;       // Initialize largest as root
    int left = 2 * i + 1;  // left child
    int right = 2 * i + 2; // right child

    // If left child is larger than root
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }

    // If right child is larger than largest so far
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }

    // If largest is not root
    if (largest != i) {
        // Swap arr[i] and arr[largest]
        int swap = arr[i];
        arr[i] = arr[largest];
        arr[largest] = swap;

        // Recursively heapify the affected sub-tree
        heapify(arr, n, largest);
    }
}

Note: This version is recursive for clarity. The iterative version from the article is equally valid and slightly more optimized for space.

Test your understanding!

Given the array [3, 7, 1, 8, 2], what is the state of the array after the first two iterations of the sorting loop (the second phase of heap sort)?

Hint: First, you must buildHeap on the array. Then, perform the swap-and-heapify step twice.

Show answer
  1. Build Heap Phase:

    • Initial array: [3, 7, 1, 8, 2]
    • Start heapify at i = 5/2 - 1 = 1. heapify(arr, 5, 1) swaps 7 and 8. Array becomes [3, 8, 1, 7, 2].
    • Next i = 0. heapify(arr, 5, 0) on 3. 3's children are 8 and 1. Swap 3 and 8. Array becomes [8, 3, 1, 7, 2]. 3 is now at index 1. Its children are 7 and 2. 7 is larger, so swap 3 and 7. Array becomes [8, 7, 1, 3, 2].
    • After buildHeap: The max-heap is [8, 7, 1, 3, 2].
  2. Sort-down Phase (Iteration 1):

    • Swap root 8 with last element 2. Array: [2, 7, 1, 3, 8].
    • Heap size is now 4. Call heapify(arr, 4, 0) on the new root 2.
    • 2's children are 7 and 1. Swap 2 and 7. Array: [7, 2, 1, 3, 8].
    • 2 is now at index 1. Its only child in the heap of size 4 is 3. Swap 2 and 3. Array: [7, 3, 1, 2, 8].
    • After 1st iteration: [7, 3, 1, 2, 8].
  3. Sort-down Phase (Iteration 2):

    • Swap root 7 with last element of the heap 2. Array: [2, 3, 1, 7, 8].
    • Heap size is now 3. Call heapify(arr, 3, 0) on the new root 2.
    • 2's children are 3 and 1. Swap 2 and 3. Array: [3, 2, 1, 7, 8].
    • After 2nd iteration: [3, 2, 1, 7, 8].

4. Algorithm Analysis and Properties

For technical interviews, simply knowing how to implement an algorithm is not enough. You must also understand its performance characteristics and trade-offs.

Time Complexity

  • Heapify Phase (buildHeap): As we learned, this takes time.
  • Sort-down Phase: This loop runs times. In each iteration, we do one swap () and one call to heapify on the root. The heapify operation's complexity is proportional to the height of the heap, which is where is the current heap size. Since goes from down to 1, the total time for this phase is .
  • Total Time Complexity: . This is the time complexity for the worst, average, and best cases.

Space Complexity

Heap sort is an in-place algorithm. The sorting is done directly within the input array. If using an iterative heapify, you only need a few extra variables for loops and swaps, making the auxiliary space complexity . If using a recursive heapify, the space complexity becomes due to the recursion call stack. For interviews, it's best to highlight the potential with an iterative implementation.

Stability

Heap sort is not stable. This means that if there are two equal elements in the input array, their relative order is not guaranteed to be preserved in the sorted output. Long-range swaps during the heapify process can easily change their original order.

This quick video and detailed article section will help you consolidate these facts.

Heap Sort Visually Explained | Sorting Algorithm | Heaps

Let's revisit the ByteQuest video for a concise summary of the complexity.

Watch from 02:31 to 03:23. It clearly breaks down the time and space complexity, confirming our analysis.

Heapsort – Algorithm, Source Code, Time Complexity

For a deeper dive into why heap sort is not stable, this section of the Happy Coders article is perfect.

Read the section 'Further Characteristics of Heapsort', focusing on 'Space Complexity of Heapsort' and 'Stability of Heapsort'. The stability section provides a clear counterexample.


Conclusion

Congratulations! You've successfully implemented and analyzed Heap Sort. This is a significant milestone in your study of sorting algorithms. Its combination of time and space complexity makes it a unique and powerful tool.

Key Takeaways:

  • Heap Sort is an in-place sorting algorithm with a time complexity of .
  • It works in two phases: first building a max-heap () and then repeatedly swapping the root with the last element and heapifying the reduced heap ().
  • Its auxiliary space complexity is (for an iterative implementation), which is its main advantage over Merge Sort.
  • Unlike Merge Sort, Heap Sort is not stable.

Preview of the Next Lesson:
The heap data structure isn't just for sorting. Its primary application is often as the backing structure for a Priority Queue. In our next lesson, we will implement a priority queue using the heap operations you now know so well and use it to solve common interview problems like finding the "Top K" elements in a stream of data.