Visualizer CodeViz · Algorithms, visualized

Part I · Mergesort · week 3

Mergesort (top-down)

Sort each half, then merge. The merge is the only real work, and it is linear — which is why the whole thing is n log n on every input.

Run the animation, step by step → generated live from any input you type — nothing is pre-recorded

Cost and properties

timeΘ(n log n), guaranteed
compares≤ n lg n
spacen — not in place
stableyes

Reference: Sedgewick & Wayne, §2.2; von Neumann 1945.

Mergesort (top-down) in Python

Runnable as-is, and written for reading. Watch the highlighted line move through it in the animation, or send it straight to the visualizer — it arrives with a test case ready to run, yours to edit.

Open in Visualizer

def sort(a):
    aux = list(a)                         # ONE auxiliary array, allocated once
    _sort(a, aux, 0, len(a) - 1)
    return a

def _sort(a, aux, lo, hi):
    if hi <= lo:                          # 0 or 1 keys: already sorted
        return
    mid = lo + (hi - lo) // 2
    _sort(a, aux, lo, mid)                # sort the left half
    _sort(a, aux, mid + 1, hi)            # sort the right half
    merge(a, aux, lo, mid, hi)            # merge two sorted halves

def merge(a, aux, lo, mid, hi):
    aux[lo:hi + 1] = a[lo:hi + 1]         # copy out, then merge back in
    i, j = lo, mid + 1
    for k in range(lo, hi + 1):
        if i > mid:                       # left half is used up
            a[k] = aux[j]; j += 1
        elif j > hi:                      # right half is used up
            a[k] = aux[i]; i += 1
        elif aux[j] < aux[i]:             # strictly less: keeps it STABLE
            a[k] = aux[j]; j += 1
        else:
            a[k] = aux[i]; i += 1

Why it works

Why n log n falls out

The recursion halves the array until the pieces are single keys — that is lg n levels — and each level merges a total of n keys in linear time. n per level × lg n levels = ~n lg n, on every input, with no probabilistic caveat. Mergesort is asymptotically optimal: no compare-based sort can beat n lg n.

The merge is the algorithm

Copy the range into aux, then walk two indices and always take the smaller front key. The four branches are exhaustive: left exhausted, right exhausted, right strictly smaller, otherwise left. Getting the boundary cases right is the whole difficulty — and note there is one auxiliary array, created once in sort. Allocating inside merge is the classic performance bug: it turns a linear merge into a memory-allocation storm.

Stability, in one character

aux[j] < aux[i] is strict, so when keys are equal the left half wins and equal keys keep their original relative order. Change it to <= and mergesort is no longer stable. Stability is why mergesort is the sort of choice when records are sorted by one field after another.

Practical improvements the course adds

  • Cutoff to insertion sort for ranges of ~7 keys: recursion overhead dominates for tiny arrays (20–30% faster overall).
  • Skip the merge when a[mid] <= a[mid+1] — the range is already sorted. Makes nearly-ordered input linear.
  • Alternate the roles of a and aux to avoid the copy entirely.

What to try in the animation

Every input costs the same ~n lg n. Try already sorted — mergesort cannot exploit it (bottom-up with a sorted-check can).

The animation is generated from the input box, in your browser — press Animate after editing it. To execute the Python itself, use Open in Visualizer above.

Open Mergesort (top-down) in the player →

The rest of Mergesort