Visualizer CodeViz · Algorithms, visualized

Part I · Quicksort · week 3

3-way quicksort

Partition into less-than, equal-to and greater-than. Arrays with few distinct keys drop from n log n to linear — and duplicates are everywhere in real data.

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

Cost and properties

distinct keyslinear when few
entropy-optimalyes
in placeyes
extra costmore exchanges

Reference: Sedgewick & Wayne, §2.3; Dijkstra.

3-way quicksort 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 sort3(a, lo, hi):
    # INVARIANT:  a[lo..lt-1] < v  =  a[lt..i-1] == v  ?  a[i..gt]  >  a[gt+1..hi]
    if hi <= lo:
        return
    lt, i, gt = lo, lo + 1, hi
    v = a[lo]
    while i <= gt:
        if a[i] < v:
            a[lt], a[i] = a[i], a[lt]     # small: send it left
            lt += 1
            i += 1
        elif a[i] > v:
            a[i], a[gt] = a[gt], a[i]     # large: send it right
            gt -= 1                       # i does NOT advance
        else:
            i += 1                        # equal: it is already home
    sort3(a, lo, lt - 1)                  # only the < and > parts remain
    sort3(a, gt + 1, hi)

Why it works

Three regions, one pass

The invariant names four regions: keys smaller than v, keys equal to v, the unexamined middle, and keys larger than v. Each iteration shrinks the unexamined part by one and keeps all four claims true. When it vanishes, every key equal to the pivot is in its final position — not just one.

Why i does not advance on a large key

The key swapped in from gt has never been examined, so it must be tested next. Advancing i there is the standard bug: it steps over an unknown key and the invariant silently breaks. Only the < and = branches advance i.

The payoff

Ordinary quicksort re-partitions blocks of equal keys again and again. 3-way removes them from the problem in a single pass, so an array with a constant number of distinct values sorts in linear time. Sedgewick and Bentley proved the sort is entropy-optimal: its compares match the information-theoretic lower bound for the input's distribution of duplicate keys.

Real-world context

This is not an exotic case. Sorting records by a low-cardinality field — country, status flag, day of week, DNA base — is the norm, and a widely-used C qsort was once quadratic on such input. Java's Arrays.sort for primitives uses a dual-pivot quicksort partly for the same reason.

What to try in the animation

The payoff shows with duplicates. Compare all equal here (one pass, done) with the same input in ordinary quicksort.

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 3-way quicksort in the player →

The rest of Quicksort