Heapsort
- Running time is
. - Sorts in place, only a constant number of elements needed in addition to the input.
- Manages data with a heap.
A binary heap can be represented as a binary tree, but is stored as an array. The root is the first element of the array. The left subnode for the element at index
Using 0-based indexing, we can use

Figure 1: A binary tree as a heap with its array representation (Cormen et al. 2022).
Heaps come in two flavors: max-heaps and min-heaps. They can be identified by satisfying a heap property.
- max-heap property:
- min-heap property:
These properties imply that the root is the largest element in a max-heap and the smallest element in a min-heap.
When it comes to heapsort, a max-heap is used. Min-heaps are used in priority queues. These notes will cover both.
Maintaining the Heap Property
When using heapsort, the heap should always satisfy the max-heap property. This relies on a procedure called max_heapify
. This function assumes that the root element may violate the max-heap property, but the subtrees rooted by its subnodes are valid max-heaps. The function then swaps nodes down the tree until the misplaced element is in the correct position.
def max_heapify(A, i, heap_size):
l = left(i)
r = right(i)
largest = i
if l < heap_size and A[l] > A[i]:
largest = l
if r < heap_size and A[r] > A[largest]:
largest = r
if largest != i:
A[i], A[largest] = A[largest], A[i]
max_heapify(A, largest, heap_size)
Analysis of Max Heapify
Given that max_heapify
is a recursive function, we can analyze it with a recurrence. The driving function in this case would be the fix up that happens between the current node and its two subnodes, which is a constant time operation. The recurrence is based on how many elements are in the subheap rooted at the current node.
In the worst case of a binary tree, the last level of the tree is half full. That means that the left subtree has height
We now have that the number of nodes in the tree is equal to
This implies that max_heapify
is
Building the Heap
Given an array of elements, how do we build the heap in the first place? The solution is to build it using a bottom-up approach from the leaves. The elements from max_heapify
on the remaining elements to build the heap.
def build_max_heap(A):
heap_size = len(A)
for i in range(len(A) // 2, -1, -1):
max_heapify(A, i, heap_size)
Why does this work?
Each node starting at
The figure below is from (Cormen et al. 2022) and shows the process of building a max-heap from an array.

Figure 2: Building a max-heap from an array (Cormen et al. 2022).
Analysis of Build Max Heap
A general analysis is fairly straightforward considering that the call to max_heapify
is build_max_heap
runs
A heap of max_heapify
can also be viewed in terms of the height of the tree build_max_heap
at
Let
Thus, a heap can be constructed in linear time. This is independent on whether the original data is already sorted.
Heapsort
We now have all of the components necessary to implement heapsort. The algorithm is as follows:
def heapsort(A):
build_max_heap(A)
heap_size = len(A)
for i in range(len(A) - 1, 0, -1):
A[0], A[i] = A[i], A[0]
heap_size -= 1
max_heapify(A, 0, heap_size)
It starts by building a max-heap on the input array. As seen in the previous section, this is done in linear time. From there, it’s a matter of taking the root element out of the heap and then running max_heapify
to maintain the max-heap property. This is done

Figure 3: Heapsort in action (Cormen et al. 2022).
Heapsort is visualized in the figure above, starting with a constructed max-heap in (a) (Cormen et al. 2022).
Questions
- What is the running time of heapsort given an array that is already sorted in ascending order?
- What is the running time of heapsort given an array that is already sorted in descending order?