Visualizer CodeViz · Algorithms, visualized

Part II · Radix sorts · week 5

MSD radix sort

Sort on the first character, then recur inside each group. It can stop as soon as keys are distinguished, so it often examines only a fraction of each key.

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

Cost and properties

timesublinear in the input
examinesjust enough characters
needsaux array + R counters per call
variable lengthhandled

Reference: Sedgewick & Wayne, §5.1.

MSD radix sort 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 msd_sort(a):
    aux = [None] * len(a)
    _msd(a, aux, 0, len(a) - 1, 0)
    return a

# R is the RADIX -- the alphabet size. 26 here because the keys are
# lowercase words, so a letter's digit is ord(c) - ord('a'), and -1 means
# 'past the end of the key' (which must sort before every real letter).
# Use 256 and ord(s[d]) for arbitrary bytes; the algorithm is unchanged.
def _char_at(s, d):                       # -1 past the end of the string
    return -1 if d >= len(s) else ord(s[d]) - ord('a')

def _msd(a, aux, lo, hi, d):
    if hi <= lo:
        return                            # 0 or 1 keys: nothing to do
    R = 26
    count = [0] * (R + 2)                 # +2: one slot for 'end of key'
    for i in range(lo, hi + 1):
        count[_char_at(a[i], d) + 2] += 1
    for r in range(R + 1):
        count[r + 1] += count[r]
    for i in range(lo, hi + 1):
        aux[count[_char_at(a[i], d) + 1]] = a[i]
        count[_char_at(a[i], d) + 1] += 1
    for i in range(lo, hi + 1):
        a[i] = aux[i - lo]
    for r in range(R):                    # recur inside each character group
        _msd(a, aux, lo + count[r], lo + count[r + 1] - 1, d + 1)

Why it works

Sublinear, genuinely

MSD examines just enough characters to distinguish the keys. On random input from a large alphabet that is about logR n characters per key — so it can sort without reading most of the input. On the distinct first letters preset it finishes after one character each. LSD, by contrast, always reads every character of every key.

The end-of-string slot

_char_at returns −1 past the end of a key, and the counts are offset by 2 rather than 1 to leave room for it. Keys that have run out therefore sort before every real character — which is exactly right (pre precedes prefix) and is what lets MSD handle variable-length keys at all.

The performance trap

Every recursive call allocates R+2 counters. With R = 256 and many tiny subarrays, that initialisation dwarfs the sorting: MSD can be slower than insertion sort on small groups, and for a large alphabet like Unicode it is much worse. Real implementations cut off to insertion sort for subarrays under ~15 keys, and that cutoff is not an optimisation — without it MSD is unusable.

Or use 3-way string quicksort

Sedgewick and Bentley's 3-way string quicksort partitions on a single character into <, =, > — no counters, in place, and it handles long shared prefixes well. It is the standard choice when keys have structure, and it is what the course recommends over MSD in practice.

What to try in the animation

Unlike LSD, keys may have different lengths, and the sort stops looking at a key as soon as it is distinguished from the others.

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 MSD radix sort in the player →

The rest of Radix sorts