Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 10 – Heaps and priority queues

Heapify and heapsort: building a heap in O(n)

Bottom-up heapify turns a list into a heap in O(n) by sifting down from the last parent; heapsort then sorts it in place in O(n log n), traced step by step.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: Heapify: sift down every parent, last one first · Heapsort: the largest item to the end, n − 1 times · Heapify against n pushes, counted · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Build a heap bottom-up in linear time
  • Implement heapsort in place
  • Compare heapsort with quicksort and merge sort

Before you start

Bottom-up heapify, published by Robert Floyd in 1964, turns any list into a heap in O(n) time: it sifts down every parent of the tree, starting with the last parent, at index n // 2 − 1, and ending at the root. Heapsort, published by J. W. J. Williams the same year, then sorts the list in place. Build a max-heap, swap its root, the largest item, with the last item of the heap, shrink the heap by one and sift the new root down; after n − 1 such steps the list is in ascending order. Heapsort takes O(n log n) time on every input and O(1) extra memory, but it is not stable, and on large arrays it is usually slower than quicksort and merge sort, because it jumps around in memory.

Heapify: sift down every parent, last one first

When all the items are already in a list, there is a cheaper way than the one push per item of the previous lesson. Leave the items where they are and repair the heap property from the bottom up, as Python’s heapq.heapify does “in-place, in linear time”, in the words of its documentation:

  1. The indices n // 2 to n − 1 are leaves, and a single item is already a heap.
  2. For i from n // 2 − 1 down to 0, sift a[i] down: swap it with its larger child (for a max-heap) while that child is larger.
  3. When the loop has handled index 0, the whole list is a heap.

The order matters. Sifting down at index i assumes that both subtrees below i are heaps already, so that the larger child is the largest item of its subtree. Going from the last parent towards the root makes that true at every step.

The cost is linear because most items live near the bottom of the tree, where there is little room to sink. Half the items are leaves and never move. The quarter just above the leaves can sink one level each, the eighth above them two, and every level higher holds half as many items with one more level of room. With pushes it is the other way round: the many items at the bottom are the ones that may climb the whole height.

A 15-item heap labelled with how far each item can sink, from 0 at the leaves to 3 at the root: 11 swaps at most, against 34 for pushes.How far each item can sink during heapify3221111000000001 item: ≤ 32 items: ≤ 2 each4 items: ≤ 1 each8 leaves: 0heapify: at most 4·1 + 2·2 + 1·3 = 11 swaps15 pushes, each climbing to the root:up to 2·1 + 4·2 + 8·3 = 34 swapsnumbers in the circles: the most levels that item can sink

Most items sit near the bottom, where there is little room to sink

Text description of the diagram

The diagram is a complete binary tree of 15 items in four levels. Each circle holds the most levels its item can sink during bottom-up heapify, which is the height of that node.

  1. The root can sink 3 levels.
  2. The 2 items at depth 1 can sink 2 levels each.
  3. The 4 items at depth 2 can sink 1 level each.
  4. The 8 leaves at depth 3 cannot sink at all.

Adding them up, heapify makes at most 4 × 1 + 2 × 2 + 1 × 3 = 11 swaps. Building the same heap with 15 pushes, each new item can climb as far as its depth: 2 items 1 level, 4 items 2 levels and 8 items 3 levels, up to 2 × 1 + 4 × 2 + 8 × 3 = 34 swaps. The deep levels hold most of the items, which is what makes the bottom-up order cheap and the pushes expensive.

Heapsort: the largest item to the end, n − 1 times

Heapsort uses a max-heap, so that the largest item is at index 0 and can move straight to the end of the list:

  1. Heapify the whole list into a max-heap.
  2. For end from n − 1 down to 1: swap a[0] with a[end], then sift the new root down inside a[0:end] only.

After each swap the slots from end onwards hold the largest items in ascending order and are never touched again, and the heap part shrinks by one. The code is short:

Heapsort on eight numbers, traced

The trace · heapsort/trace.py

"""Heapsort on eight numbers: the bottom-up heapify, then every move of the largest item to the sorted tail."""

from heap_tools import sift_down

a = [4, 10, 3, 5, 1, 8, 7, 2]
print(f"start:       {a}")
n = len(a)
for i in range(n // 2 - 1, -1, -1):
    item = a[i]
    swaps = sift_down(a, i, n)
    print(f"sift {item:>2} at {i}: {a}  ({swaps} swap{'' if swaps == 1 else 's'})")
print(f"max-heap:    {a}")
for end in range(n - 1, 0, -1):
    a[0], a[end] = a[end], a[0]
    swaps = sift_down(a, 0, end)
    print(f"{a[end]:>2} to {end}:     {a[:end]} | {a[end:]}  ({swaps} swap{'' if swaps == 1 else 's'})")
print(f"sorted:      {a}")

Output

start:       [4, 10, 3, 5, 1, 8, 7, 2]
sift  5 at 3: [4, 10, 3, 5, 1, 8, 7, 2]  (0 swaps)
sift  3 at 2: [4, 10, 8, 5, 1, 3, 7, 2]  (1 swap)
sift 10 at 1: [4, 10, 8, 5, 1, 3, 7, 2]  (0 swaps)
sift  4 at 0: [10, 5, 8, 4, 1, 3, 7, 2]  (2 swaps)
max-heap:    [10, 5, 8, 4, 1, 3, 7, 2]
10 to 7:     [8, 5, 7, 4, 1, 3, 2] | [10]  (2 swaps)
 8 to 6:     [7, 5, 3, 4, 1, 2] | [8, 10]  (2 swaps)
 7 to 5:     [5, 4, 3, 2, 1] | [7, 8, 10]  (2 swaps)
 5 to 4:     [4, 2, 3, 1] | [5, 7, 8, 10]  (2 swaps)
 4 to 3:     [3, 2, 1] | [4, 5, 7, 8, 10]  (1 swap)
 3 to 2:     [2, 1] | [3, 4, 5, 7, 8, 10]  (1 swap)
 2 to 1:     [1] | [2, 3, 4, 5, 7, 8, 10]  (0 swaps)
sorted:      [1, 2, 3, 4, 5, 7, 8, 10]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trace.py

Python · heapsort/heap_tools.py

"""Bottom-up heapify and heapsort on a Python list, with a max-heap: no child is larger than its parent.

Indices start at 0, so the children of i are 2i + 1 and 2i + 2, and only a[0:n] belongs to the heap.
"""


def sift_down(a, i, n):
    """Moves a[i] down inside a[0:n] until no child is larger. Returns the number of swaps it made."""
    swaps = 0
    while True:
        child = 2 * i + 1
        if child >= n:
            return swaps  # a[i] is a leaf
        if child + 1 < n and a[child + 1] > a[child]:
            child += 1  # the larger child
        if a[child] <= a[i]:
            return swaps
        a[i], a[child] = a[child], a[i]
        swaps += 1
        i = child


def heapify(a):
    """Turns the whole list into a max-heap in place: sift down every parent, the last parent first."""
    return sum(sift_down(a, i, len(a)) for i in range(len(a) // 2 - 1, -1, -1))


def heapsort(a):
    """Sorts a in place in ascending order: heapify, then swap the maximum to the end of the heap n - 1 times."""
    heapify(a)
    for end in range(len(a) - 1, 0, -1):
        a[0], a[end] = a[end], a[0]  # the largest item left joins the sorted tail
        sift_down(a, 0, end)  # the heap is now a[0:end]

Heapify starts at index 3, the last parent of 8 items. 5 is already larger than its only child, 3 sinks below its larger child 8, 10 stays, and 4 sinks twice, past 10 and then past 5: three swaps build the heap. Each later line moves the root into the slot just left of the sorted tail, 10 to index 7, then 8 to index 6, and sifts the item that took its place down again.

Heapify against n pushes, counted

This run builds max-heaps of 2¹⁰ to 2¹⁶ items both ways, from shuffled input and from ascending input, and counts the swaps. The ratio column compares each size with the one before, which is half as large:

Swaps of heapify and of n pushes, from 1,024 to 65,536 items Python · heapsort/build_counts.py
"""Swaps made by bottom-up heapify against n pushes into an empty max-heap, for n = 2**10 to 2**16."""

import random

from heap_tools import heapify


def build_by_pushes(items):
    """Pushes the items one at a time (append, then sift up). Returns the heap and the swaps."""
    heap, swaps = [], 0
    for x in items:
        heap.append(x)
        i = len(heap) - 1
        while i > 0 and heap[(i - 1) // 2] < heap[i]:
            heap[i], heap[(i - 1) // 2] = heap[(i - 1) // 2], heap[i]
            swaps += 1
            i = (i - 1) // 2
    return heap, swaps


rng = random.Random(5)
for order in ("random", "ascending"):
    print(f"{order} input: swaps, and the ratio to the size before")
    previous = None
    for k in range(10, 17):
        n = 2**k
        items = list(range(n))
        if order == "random":
            rng.shuffle(items)
        floyd = items[:]
        h = heapify(floyd)
        _, p = build_by_pushes(items)
        ratio = f"  x{h / previous[0]:.2f} and x{p / previous[1]:.2f}" if previous else ""
        print(f"  n = 2^{k} = {n:>6,}: heapify {h:>7,} ({h / n:.2f} n), pushes {p:>9,} ({p / n:.2f} n){ratio}")
        previous = (h, p)

Output

random input: swaps, and the ratio to the size before
  n = 2^10 =  1,024: heapify     770 (0.75 n), pushes     1,294 (1.26 n)
  n = 2^11 =  2,048: heapify   1,537 (0.75 n), pushes     2,640 (1.29 n)  x2.00 and x2.04
  n = 2^12 =  4,096: heapify   3,069 (0.75 n), pushes     5,223 (1.28 n)  x2.00 and x1.98
  n = 2^13 =  8,192: heapify   6,111 (0.75 n), pushes    10,543 (1.29 n)  x1.99 and x2.02
  n = 2^14 = 16,384: heapify  12,254 (0.75 n), pushes    21,074 (1.29 n)  x2.01 and x2.00
  n = 2^15 = 32,768: heapify  24,447 (0.75 n), pushes    42,187 (1.29 n)  x2.00 and x2.00
  n = 2^16 = 65,536: heapify  48,552 (0.74 n), pushes    83,895 (1.28 n)  x1.99 and x1.99
ascending input: swaps, and the ratio to the size before
  n = 2^10 =  1,024: heapify   1,015 (0.99 n), pushes     8,204 (8.01 n)
  n = 2^11 =  2,048: heapify   2,038 (1.00 n), pushes    18,445 (9.01 n)  x2.01 and x2.25
  n = 2^12 =  4,096: heapify   4,085 (1.00 n), pushes    40,974 (10.00 n)  x2.00 and x2.22
  n = 2^13 =  8,192: heapify   8,180 (1.00 n), pushes    90,127 (11.00 n)  x2.00 and x2.20
  n = 2^14 = 16,384: heapify  16,371 (1.00 n), pushes   196,624 (12.00 n)  x2.00 and x2.18
  n = 2^15 = 32,768: heapify  32,754 (1.00 n), pushes   426,001 (13.00 n)  x2.00 and x2.17
  n = 2^16 = 65,536: heapify  65,521 (1.00 n), pushes   917,522 (14.00 n)  x2.00 and x2.15

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_counts.py

On shuffled input both stay close to linear: about 0.75 swaps per item for heapify and 1.29 for the pushes. Ascending input is the worst case for pushes into a max-heap, since every new item is the largest so far and climbs to the root: 8 swaps per item at 1,024 items and 14 at 65,536, each doubling costing 2.15 to 2.25 times as much, the signature of n log n. Heapify stayed at 1.00 swap per item. When all the items are there at the start, heapify is the way to build a heap; pushes are for items that arrive one at a time.

Heapsort, quicksort and merge sort compared

Three O(n log n) sorts on an array of n items
Criterion HeapsortQuicksortMerge sort
Worst-case time O(n log n)O(n²), or O(n log n) as introsortO(n log n)
Extra memory O(1)O(log n) for the recursionA buffer of n items
Stable NoNoYes
Memory access Jumps between parents and children far apartScans ranges from both endsReads and writes runs in order
Speed on large arrays Usually the slowest of the threeUsually the fastestClose to quicksort
When to choose A guaranteed worst case with no extra memoryGeneral in-memory sortingStable sorting, linked lists, data on disk

Heapsort is the only one of the three that guarantees O(n log n) time with O(1) extra memory, which is why libraries keep it as a safety net inside their quicksort.

Exercise and self-check

Write heapsort yourself, in place, without the built-in sort. The sample tests also count the comparisons, so a quadratic sort fails on 4,096 items.

Exercise · Medium · Python, JavaScript

Heapsort a list in place

Write heapsort(a) in heapsort.py (JavaScript: heapsort(a) in heapsort.mjs). It sorts the list a of numbers in ascending order in place: it rearranges the items inside a itself, keeps no second list and returns nothing. Use a max-heap inside a: build it bottom up, then move the largest item to the end of the heap part and sift the new root down, until the heap part holds one item.

Do not call the built-in sort. The sample tests check small lists, repeated and negative numbers, that the list itself is sorted, 200 random lists against the built-in sort, and the number of comparisons on 4,096 items, which must stay below 2n log₂ n + 2n, about 106,000: a quadratic sort needs millions.

Python · Starter code · heapsort.py

def heapsort(a):
    """Sorts the list a in ascending order in place, with a max-heap kept in a itself. Returns nothing."""
    # Replace this line: heapify a bottom up, then move the largest item to the end n - 1 times.
    raise NotImplementedError
The sample tests · test_heapsort.py
import math
import random

from heapsort import heapsort


class NoSort(list):
    """A list whose built-in sort refuses to run."""

    def sort(self, *args, **kwargs):
        raise AssertionError("write heapsort yourself instead of calling sort()")


def test_small_lists():
    """sorts the lesson's eight numbers and small edge cases in place"""
    for values in ([4, 10, 3, 5, 1, 8, 7, 2], [], [7], [2, 1], [3, 1, 3, 1, 2], [-5, 0, -5, 9], [1, 2, 3, 4], [9, 7, 5, 3, 1]):
        a = NoSort(values)
        assert heapsort(a) is None, "heapsort returns nothing: it sorts the list it was given"
        assert list(a) == sorted(values), f"{values}: got {list(a)}"


def test_random_lists():
    """200 random lists of up to 60 numbers match sorted()"""
    rng = random.Random(4)
    for _ in range(200):
        values = [rng.randint(-20, 20) for _ in range(rng.randint(0, 60))]
        a = NoSort(values)
        heapsort(a)
        assert list(a) == sorted(values), f"{values}: got {list(a)}"


class Counted:
    """A number that counts every comparison made with it."""

    comparisons = 0

    def __init__(self, v):
        self.v = v

    def __lt__(self, other):
        Counted.comparisons += 1
        return self.v < other.v

    def __le__(self, other):
        Counted.comparisons += 1
        return self.v <= other.v

    def __gt__(self, other):
        Counted.comparisons += 1
        return self.v > other.v

    def __ge__(self, other):
        Counted.comparisons += 1
        return self.v >= other.v


def test_comparisons_on_4096_items():
    """4,096 random numbers take fewer than 2n log2 n + 2n comparisons"""
    rng = random.Random(8)
    n = 4096
    values = [rng.random() for _ in range(n)]
    a = NoSort(Counted(v) for v in values)
    Counted.comparisons = 0
    heapsort(a)
    limit = 2 * n * math.log2(n) + 2 * n
    assert Counted.comparisons < limit, f"{Counted.comparisons:,} comparisons; the limit is {limit:,.0f}"
    assert [x.v for x in a] == sorted(values)

JavaScript · Starter code · heapsort.mjs

/** Sorts the array a in ascending order in place, with a max-heap kept in a itself. Returns nothing. */
export function heapsort(a) {
  // Replace this line: heapify a bottom up, then move the largest item to the end n - 1 times.
  throw new Error('not written yet');
}
The sample tests · heapsort.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { heapsort } from './heapsort.mjs';

const ascending = (a, b) => a - b;
function noSort(values) {
  const a = [...values];
  a.sort = () => { throw new Error('write heapsort yourself instead of calling sort()'); };
  return a;
}

test("sorts the lesson's eight numbers and small edge cases in place", () => {
  for (const values of [[4, 10, 3, 5, 1, 8, 7, 2], [], [7], [2, 1], [3, 1, 3, 1, 2], [-5, 0, -5, 9], [1, 2, 3, 4], [9, 7, 5, 3, 1]]) {
    const a = noSort(values);
    assert.equal(heapsort(a), undefined, 'heapsort returns nothing: it sorts the array it was given');
    assert.deepEqual([...a], [...values].sort(ascending), JSON.stringify(values));
  }
});

test('200 random arrays of up to 60 numbers match a sorted copy', () => {
  let seed = 4;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  for (let k = 0; k < 200; k++) {
    const values = Array.from({ length: Math.floor(random() * 61) }, () => Math.floor(random() * 41) - 20);
    const a = noSort(values);
    heapsort(a);
    assert.deepEqual([...a], [...values].sort(ascending), JSON.stringify(values));
  }
});

test('4,096 random numbers take fewer than 2n log2 n + 2n comparisons', () => {
  // Comparing two of these numbers with <, <=, > or >= reads both values, so the reads are twice the comparisons.
  const box = { reads: 0 };
  let seed = 8;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  const n = 4096;
  const values = Array.from({ length: n }, random);
  const a = noSort(values.map((v) => ({ v, valueOf() { box.reads++; return v; } })));
  heapsort(a);
  const comparisons = box.reads / 2;
  const limit = 2 * n * Math.log2(n) + 2 * n;
  assert.ok(comparisons < limit, `${comparisons} comparisons; the limit is ${limit}`);
  assert.deepEqual(a.map((x) => x.v), [...values].sort(ascending));
});
A hint

Write sift_down(a, i, n) first: it moves a[i] down inside a[0:n] only, always towards the larger child, until no child is larger. Heapify calls it for i from len(a) // 2 - 1 down to 0 with n = len(a). Then, for end from len(a) - 1 down to 1, swap a[0] with a[end] and call it with n = end, so the sorted tail is never touched again.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Paste heap_tools.py and trace.py into one file, then change the loop in heapify to run from 0 upwards and watch the result stop being a heap.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 Bottom-up heapify runs on a list of 20 items, indexed from 0. At which index does it sift down first?

    Type a number.

    Show the answer to question 1

    Answer: 9

    It starts at the last parent, 20 // 2 − 1 = 9, whose child is index 19, the last item. Indices 10 to 19 are leaves and are already heaps of one item, so there is nothing to do there.

  2. Question 2 of 4 Why does heapify sift down the parents from the last one towards the root, and not from the root down?

    Choose one answer.

    Show the answer to question 2

    Answer: Sifting down at an index works only when both subtrees below it are already heaps

    Sift-down assumes that everything below the item is in heap order, so that the larger child is the largest item of its subtree. Working from the last parent upwards makes that true at every step, because every subtree below the current index has been fixed already. From the root down, a large item buried deep in the list can stay stuck below a smaller one.

  3. Question 3 of 4 At most how many swaps does bottom-up heapify make on 15 items?

    Type a number.

    Show the answer to question 3

    Answer: 11 swaps

    8 leaves never move, 4 items can sink 1 level, 2 items 2 levels and the root 3 levels: 4 × 1 + 2 × 2 + 1 × 3 = 11. Fifteen pushes that each climb to the root could make 34.

  4. Question 4 of 4 Which statement about heapsort is true?

    Choose one answer.

    Show the answer to question 4

    Answer: It sorts in place in O(n log n) time even in the worst case, but it is not stable

    Heapify costs O(n) and each of the n − 1 extractions at most O(log n), whatever the input, and the work happens inside the list with O(1) extra memory. The first swap of the root with the last item can move an item past equal ones, so heapsort is not stable, and sorted input does not make it faster.

Why heapify is linear: the invariant, the exact bound and the comparisons Pro: opens with a Pro, Premium or Ultimate pass

Linear time is a claim about a sum, and the sum can be pinned down exactly. This part proves the loop correct, reproduces the failure of the opposite order, gives the bound in closed form, checks it against every order of up to nine items and counts the comparisons that the bottom-up sift-down saves.

In this part:

  • The loop invariant, and the root-first order failing
  • The bound in closed form: n minus the 1-bits of n
  • Comparisons, not swaps: the bottom-up sift-down

2 runnable examples (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Heapsort in depth: its proof, its instability and its speed, measured Pro: opens with a Pro, Premium or Ultimate pass

Heapsort’s guarantees are easy to prove and its weak points are easy to show. This part proves the extraction loop correct, reproduces its instability on student records and times it against the C++ library’s quicksort and merge sort, with the comparisons counted as well.

In this part:

  • Why the output is sorted: the invariant of the extraction loop
  • Equal keys reordered: the instability, reproduced
  • Fewer comparisons, still slower: timed against std::sort and std::stable_sort
  • Where heapsort earns its place

2 runnable examples (Build and run, C++, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Heapsort in five languages, the slips that break it, and a practice set Pro: opens with a Pro, Premium or Ultimate pass

The same heapsort reads almost line for line in every interview language, and its slips are small enough to make under time pressure. This part gives recorded runs in five languages, reproduces four slips on the shortest inputs that catch them and ends with a practice set with answers.

In this part:

  • Heapsort in Python, JavaScript, Java, C++ and Go
  • Four slips, reproduced on lists of two to four items
  • Practice set

3 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    In bottom-up heapify, why does the loop run from index n // 2 − 1 down to 0, and what goes wrong if it runs from 0 upwards?

    Sifting an item down is only correct when both subtrees below it are already heaps, because the larger child must be the largest item of its subtree. The indices n // 2 to n − 1 are leaves, heaps of one item, so the loop starts at the last parent, n // 2 − 1, and each step works on a node whose children have been fixed already; when it reaches index 0, the whole list is a heap. Run from the root upwards and that premise is missing: the root is sifted while large items are still buried lower down, and nothing moves them past it later, so a large item can end up below a smaller one. Starting at n − 1 instead of n // 2 − 1 is harmless, only wasteful: leaves have nothing to sift.

  2. Mid to senior

    A microcontroller must sort 50,000 sensor records of 16 bytes in place, with 8 KB of spare RAM, and the control loop needs a guaranteed worst-case time. Quicksort, merge sort or heapsort: which do you choose, and how many comparisons do you budget for?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A game keeps 1,000,000 scores in an array and must list the top 100, in order, after each nightly batch. You may rearrange the array. Estimate the comparisons of heapify followed by 100 pops against a full sort, and say which you would ship.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A payroll job heapsorts 10 million employee records by salary, but auditors require employees with equal salaries to stay in employee-ID order, the order of the input file. What goes wrong, and which fixes would you consider, with their extra memory?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Bottom-up heapify sifts down every parent from index n // 2 − 1 to 0 and builds a heap in O(n): most items are near the bottom and can barely sink.
  • Pushes are for items that arrive one at a time; on ascending input they cost O(n log n), 14 swaps per item at 65,536 items against heapify’s 1.
  • Heapsort heapifies into a max-heap, then swaps the root to the end and sifts down n − 1 times: O(n log n) on every input, O(1) extra memory, not stable.
  • Among the three classic O(n log n) sorts, heapsort is the one that needs no extra memory and has no bad case, which is why libraries keep it as a fallback for quicksort.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.