Skip to main content

Welcome to a Grasp lesson

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

Lesson 54

Building a Heap from an Unordered Array

Recommended resources

Hello! Let's dive into our next lesson on heaps.

In our last session, we implemented a binary heap from the ground up. We learned how to insert elements one by one, using an upheap operation (percolateUp), and how to extract the root element using a downheap operation (percolateDown). While inserting N elements one by one is a valid way to build a heap, it takes time in total.

Today, we'll learn a more efficient method. When you're given an entire collection of items upfront, you can build a heap in-place much faster. Your learning outcome is to build a heap from an unordered array in linear time using the heapify-down operation. This is a classic and highly efficient algorithm, frequently asked about in interviews and essential for implementing Heap Sort.

1. Two Approaches to Building a Heap

Imagine you have an array of numbers, say [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]. How would you turn this into a max-heap?

  1. One-by-one Insertion (): You could start with an empty heap and insert each element from the array. The first insertion is cheap, but as the heap grows, each subsequent insertion can take up to time, where k is the current size of the heap. Summing this over all N elements gives a total time complexity of .

  2. The buildHeap Algorithm (): A much faster way is to treat the entire array as a complete (but not yet valid) binary tree and then fix it. This process is often called "heapifying" the array.

Let's watch a short video that contrasts these two methods and sets the stage for the efficient buildHeap algorithm.

HeapSort | Heapify method to build a MaxHeap in O(n) | DSA-One Course #32

The video 'HeapSort | Heapify method...' by Anuj Kumar Sharma provides a clear introduction to the two methods of heap creation.

Watch from the beginning to 03:33. Notice the complexity of inserting elements one by one, and then see the introduction to the more efficient 'heapify' algorithm which we will explore in detail.

As you saw, the heapify approach promises a linear time complexity, which is a significant improvement. Let's see how it works.


2. The buildHeap Algorithm: Bottom-Up Heapifying

The core idea behind the efficient buildHeap algorithm is surprisingly simple:

  • A leaf node, by itself, is a valid heap of size one.
  • Therefore, we only need to worry about the non-leaf nodes.

Given an array of size n, we can view it as a complete binary tree. The leaf nodes occupy the second half of the array. Specifically, in a 0-indexed array, the last non-leaf node is at index (n/2) - 1.

The algorithm proceeds as follows:

  1. Start at the last non-leaf node: index i = (n/2) - 1.
  2. Call the siftDown (or percolateDown) function on the element at index i. This ensures that the subtree rooted at i becomes a valid heap. Crucially, when we do this, the children of i are already roots of valid heaps (since we are moving backwards from the leaves).
  3. Decrement i and repeat the process until you reach the root of the tree (index 0).

After siftDown has been called on the root, the entire array will satisfy the heap property.

Building Heap from Array: Initial Setup
This diagram shows an initial array and its tree representation. It correctly identifies that we only need to heapify the non-leaf nodes, starting from the last one (index 3 in this 1-indexed example, which would be `(10/2)-1=4` in a 0-indexed array of size 10) and moving backwards to the root.

Let's watch a detailed walkthrough of this process.

HeapSort | Heapify method to build a MaxHeap in O(n) | DSA-One Course #32

Anuj Kumar Sharma's video provides an excellent, step-by-step visualization of the heapify process.

Watch from 03:33 to 09:41. Follow along as he starts from the last non-leaf node and works his way up to the root, applying the heapify (sift-down) operation at each step. Pay close attention to how the array is modified after each swap.

The siftDown operation is the same one we learned in the previous lesson for extractMax/extractMin. It takes a node, compares it with its children, and swaps it with the largest (for a max-heap) or smallest (for a min-heap) child if needed. The process repeats until the node is in its correct place.

Heapify-Down Operation to Build a Max-Heap
This image illustrates a single `siftDown` operation. The root of the subtree (1) is smaller than its children. It's swapped with its largest child (12). The process would continue downwards if '1' were still out of place in its new position.

3. Implementation in Java

The implementation of buildHeap is remarkably concise. It's a simple for loop that calls the siftDown method you've already seen.

Let's examine the code from the OpenDSA resource.

Heaps and Priority Queues

This article from OpenDSA provides the formal definition and a clear Java implementation of buildHeap.

First, read the section 'Building a Heap'. It describes the bottom-up strategy. Then, look at the Java code provided. Focus on the buildHeap() method. Notice its simplicity: it's just a loop that iterates from parent(n-1) down to 0, calling siftDown(i) at each step. You can refer to the siftDown implementation provided in the same code block to refresh your memory.

Here's what the buildHeap method typically looks like in Java for a heap stored in an ArrayList<T> named heap.

private void buildHeap() {
    // Start from the last non-leaf node and go up to the root
    for (int i = (heap.size() / 2) - 1; i >= 0; i--) {
        siftDown(i);
    }
}

This function would be called in the constructor if you were creating a heap from an existing collection.

Test your understanding!

You are given the array [10, 20, 5, 8, 30]. You want to build a max-heap.

  1. What is the size n of the array?
  2. What is the index of the last non-leaf node where you would start the buildHeap process?
  3. What will the array look like after the first siftDown operation is completed?
Show answer
  1. The size n is 5.
  2. The index of the last non-leaf node is (n/2) - 1 = (5/2) - 1 = 2 - 1 = 1. We start at index 1.
  3. The initial array is [10, 20, 5, 8, 30]. The buildHeap loop starts at i=1. We call siftDown(1) on the element 20. Its children are at indices 2*1+1=3 (value 8) and 2*1+2=4 (value 30). Since 30 is the largest child and is greater than 20, we swap them.
    The array becomes [10, 30, 5, 8, 20]. This is the state after the first siftDown operation. The next step in the loop would be siftDown(0).

4. Why is it O(N) Time? A Closer Look

At first glance, the complexity seems to be . After all, we have a loop that runs about N/2 times, and inside it, we call siftDown, which takes time in the worst case. So why is the total complexity linear?

The key is that siftDown rarely takes its worst-case time. The cost of siftDown on a node is proportional to the height of that node, not the height of the whole tree.

  • Most nodes are at the bottom: In a complete binary tree, about half the nodes are leaves (height 0). They require 0 work.
  • A quarter are one level up: About N/4 nodes are parents of leaves (height 1). They can only be sifted down by at most 1 level.
  • An eighth are two levels up: About N/8 nodes have a height of 2. They can be sifted down by at most 2 levels.
  • ...and so on. Only a single root node can be sifted down by the full height of the tree, .

The total work is the sum of the heights of all nodes. This sum turns out to be a series that converges to a value proportional to N. Therefore, the total time complexity is .

Let's hear a final, clear explanation of this analysis.

Introduction to Heap Data Structure + Priority Queue + Heapsort Tutorial

Kunal Kushwaha offers a good intuitive and mathematical breakdown of why the complexity is O(N).

Watch from 57:48 to 1:06:30. He first walks through the buildHeap logic and then addresses the time complexity, explaining why the initial N log N assumption is incorrect and providing the intuition for the linear time bound.


Conclusion

You've just mastered a fundamental and highly efficient algorithm. While building a heap via repeated insertions is intuitive, the buildHeap algorithm is the superior choice when you have all the data at once.

Key Takeaways:

  • You can build a heap from an unordered array in linear time.
  • The buildHeap algorithm works by treating the array as a complete binary tree and calling siftDown on each non-leaf node.
  • The process iterates backwards, from the last non-leaf node ((n/2) - 1) up to the root (index 0).
  • This bottom-up approach works because by the time you siftDown a node i, the subtrees rooted at its children are already valid heaps.
  • The linear time complexity comes from the fact that most nodes are near the bottom of the tree and require very few swaps.

Preview of the Next Lesson:
Now that we have an efficient buildHeap method, we have the first major component for one of the most elegant sorting algorithms: Heap Sort. In the next lesson, we will combine buildHeap with the extractMax operation to sort an array in-place with an time complexity.