Skip to content

Heap sort

Problem

Implement in-place heap sort for a Python list.

Key trick

Build a max heap, repeatedly move its root to the end, and restore the heap.

Heap sort is inappropriate for linked lists because parent and child lookup requires efficient indexed access.

Trap

Heap construction is \(O(n)\), not \(O(n \log n)\), when performed bottom-up.

Why is it interesting?

It guarantees \(O(n \log n)\) time with \(O(1)\) auxiliary space, but it is unstable and generally slower than Python's built-in sort.

Python solution

class sort_heap_sort:
    def sort(self, nums: list[int]) -> list[int]:
        # Preserve the caller's input.
        arr = nums[:]
        n = len(arr)

        # Build a max heap bottom-up in O(n) time.
        # Nodes after `n // 2 - 1` are leaves and need no work.
        for root in range(n // 2 - 1, -1, -1):
            self._sift_down(arr, root, n)

        # Repeatedly move the heap maximum to the sorted suffix.
        for end in range(n - 1, 0, -1):
            arr[0], arr[end] = arr[end], arr[0]

            # Exclude the newly sorted suffix from the heap.
            self._sift_down(arr, 0, end)

        return arr

    def _sift_down(
        self,
        arr: list[int],
        root: int,
        n: int,
    ) -> None:
        """Restore the max-heap property below `root`."""

        while True:
            # Array representation of a binary heap.
            l = 2 * root + 1
            r = l + 1
            largest = root

            # Find the largest among the root and its children.
            if l < n and arr[l] > arr[largest]:
                largest = l

            if r < n and arr[r] > arr[largest]:
                largest = r

            # The subtree already satisfies the heap property.
            if largest == root:
                return

            # Move the larger child up and continue down that subtree.
            arr[root], arr[largest] = arr[largest], arr[root]
            root = largest