Linear-Time Selection
Finding the -th smallest element looks like it should require sorting, but it does not. Quickselect adapts quicksort's partition to recurse on just one side, achieving expected .
╌╌╌╌
How do you find the median of numbers? The obvious answer, sorting them and reading off the middle, costs . But the median, and more generally the -th smallest element, can be found in linear time.1 The insight is that selection requires less than sorting: we want one element's value, not the full ordering, and we can stop as soon as we have it.
The selection problem
Special cases: is the minimum, the maximum, and the median. The minimum and maximum are easy in comparisons. The median is the interesting case, and the algorithms below solve it as a byproduct of solving general selection.
Minimum and maximum together
The minimum alone costs comparisons, and no algorithm can do better: every element except the eventual winner must lose at least one comparison, so losses are unavoidable. Finding both the minimum and the maximum naively costs — run the minimum scan and the maximum scan separately. But you can do it in about by processing elements in pairs. For each pair, compare the two against each other first ( comparison), then send the smaller to the running minimum and the larger to the running maximum ( more). That is comparisons per elements, or total, versus for the separate scans. The saving comes from never comparing the larger of a pair against the current minimum, nor the smaller against the current maximum — half the comparisons in the naive method are provably pointless.
Quickselect: partition, then recurse on one side
Quicksort partitions around a pivot and recurses on both halves. But for selection we only care about one of them. After partitioning around a pivot that lands at index , the pivot is the -th smallest element of the subarray. Compare that rank to :
- if it equals , the pivot is the answer;
- if is smaller, the answer lies in the left part, so recurse there;
- if is larger, the answer lies in the right part; recurse there, adjusting to skip the elements we discarded.
Throwing away the side that cannot contain the answer is what turns into .2
- 1if then
- 2returnonly element: the answer
- 3call
- 4pivot rank in A[p..r]
- 5if then
- 6returnpivot is the k-th smallest
- 7else if then
- 8return callrecurse left
- 9else
- 10return callrecurse right, shift k
A worked trace
Take () and ask for the -th smallest. Suppose each call happens to pick the last element of its range as the pivot.
The first partition uses pivot . Everything smaller slides left, everything larger slides right, and settles into the slot where it belongs:
The pivot landed at index , so its rank in the range is . We want rank , so the answer is in the left part and we recurse there with unchanged.
Partition around pivot : . The pivot's rank in this range is . We still want rank , and , so we recurse right into and shift the target to .
That range has a single element, , which is trivially its own st smallest — so returns . Checking against the sorted array : the -th smallest is indeed . At every step we partitioned one range and then threw away everything on the wrong side of the pivot, never sorting the parts we kept.
Expected linear time
Partition costs . The win over quicksort is that we recurse on only one side. With a randomized pivot, the partition splits the array at a uniformly random rank, so on average we discard a constant fraction each time. Intuitively, a random pivot lands in the middle half of the array with probability , in which case the surviving side has at most elements. The expected work satisfies, roughly,
a geometric (not branching) recurrence. Unrolling it,
The geometric series converges to a constant, so the total is linear.
The hand-wave above hides one step: why is the expected surviving size a constant fraction of ? With a uniformly random pivot, its rank is equally likely to be any of . Call the pivot good if its rank falls in the middle half, between and ; that happens with probability . A good pivot leaves at most elements on either side. So on average we need at most two partitions to shrink the range to , and each partition costs . That gives , which unrolls to the geometric sum above. CLRS reaches the same more carefully with indicator variables summed over every possible pivot rank; the constant it extracts is small.
The catch is the same as quicksort's: the worst case, with every pivot maximally unbalanced, is
Randomization makes that astronomically unlikely, but it is still possible. Can we guarantee linear time?
Median of medians: a guaranteed-good pivot
The deterministic algorithm of Blum, Floyd, Pratt, Rivest, and Tarjan (1973) achieves worst-case by spending a little effort to choose a pivot that is provably not too extreme.3 The idea is to pick the pivot as a median of medians.
- 1if has at most elements then
- 2return the -th smallest of by direct sorting
- 3divide into groups of elements (last group may be smaller)
- 4foreach group do
- 5find that group's median by sorting its elements
- 6let be the array of the group medians
- 7callmedian of medians
- 8partition around the pivot , returning its rank
- 9if then
- 10return
- 11else if then
- 12return call
- 13else
- 14return call
Why groups of five give a good pivot
Here is the core of it. Picture the groups as columns, each sorted top (large) to bottom (small), and now imagine the columns reordered left to right by their medians. The pivot is the median of that middle row, so it sits dead-center in the grid below. (We assume distinct elements for simplicity.)
The pivot is built in stages: chop the array into groups of five, sort each group to expose its median, collect those medians, and recurse to find their median — the median of medians.
For example, take these numbers and split them into three groups of five:
Sort each group and read off its median (the third of five):
The three group medians are . Their median is — the median of medians, our pivot. Now count what guarantees. Its own group contributes and everything at or below it there () as elements ; , whose median , contributes its median and the two below it () as elements . That already fixes of the values as before we even scan the array. Partitioning around therefore cannot strand it near either end: the split is provably balanced. This is the guarantee in miniature — with , at least to elements are pinned to each side.
(Five is the smallest odd group size that makes the recurrence below close; groups of fail because their fractions sum to exactly .)
Solving the recurrence
Tallying the work: splitting into groups and finding their medians is (each group is sorted in ). Partitioning is . There are two recursive calls:
- finding the median of the medians, a subproblem of size ;
- recursing into the surviving side, a subproblem of size at most .
The two fractions are what make this work: , so the two subproblems together are strictly smaller than the input. That is the shrinkage — the total work contracts by a constant factor at every level.
Start with the single-call cousin . The master theorem gives ; unrolling shows why directly,
The constant-factor shrinkage plus linear cleanup work sums to . The median-of-medians recurrence has two calls instead of one, but the same shrinkage idea carries it through, stated as a general lemma:
Plugging in , (so ) gives , that is, worst-case linear time. Had the fractions summed to or more (as for groups of , where ), the lemma's hypothesis fails: the per-level savings vanish, the recursion tree carries levels of work, and the bound degrades to .
Which to use
Both algorithms are linear, but they trade off differently.
| expected time | worst case | pivot cost | in practice | |
|---|---|---|---|---|
| Randomized quickselect | one random draw | fast; the default | ||
| Median of medians | recursive, heavy | slow constant |
The median-of-medians algorithm settled a real question: it proves selection is possible in worst-case linear time, with no randomness and no probabilistic escape hatch. But its constant factor is large. Every level does the grouping, the per-group sort, and a second recursive call just to pick the pivot, so the hidden constant dwarfs quickselect's. On real inputs randomized quickselect is faster and is the algorithm to reach for.4 Its quadratic worst case is a theoretical possibility that a random pivot makes vanishingly unlikely — an adversary who cannot see your coin flips cannot force it.
The deterministic version matters in two places. First, when a hard
worst-case guarantee is mandatory (a real-time deadline, or an adversarial
setting where inputs are chosen to break you). Second, and more commonly, as a
pivot-selection subroutine for quicksort: use to find the
true median in , partition around it, and every quicksort split is
perfectly balanced, giving a worst-case sort. The practical
compromise, introselect, runs plain quickselect but watches the recursion
depth; if it ever exceeds a threshold (a sign of bad pivots), it switches to
median-of-medians for the rest. That keeps quickselect's speed on ordinary
inputs while capping the worst case at — the strategy C++'s
std::nth_element uses.
Bonus: Closest Pair of Points
Sorting and selection are not the only classics that fall to divide-and-conquer. Finding the closest pair among points in the plane beats the brute force the same way: split by the median -coordinate, recurse on each half to get the best distance within each side, and combine by checking only pairs that straddle the dividing line. The combine looks dangerous — a naive cross-check is — but the same kind of counting argument as in median-of-medians fixes it: any straddling pair closer than lies in a width- strip, and a packing bound shows each strip point need only be compared against a constant number of -neighbours. That makes the combine and the whole recurrence . The full algorithm, the strip-packing proof, and the pseudocode live in Polygons & Proximity.
Selection in practice
Selection is a solved problem in theory — both algorithms are linear — but the constant factors and the shift to huge or distributed data keep it a live engineering topic.
Floyd–Rivest: fewer comparisons than either. The practical winner is often
neither plain quickselect nor median-of-medians but the Floyd–Rivest
algorithm (1975). It samples a small random subset of the array, selects two
pivots from the sample chosen to straddle the target rank with high
probability, and partitions into three parts so that after one pass the surviving
range is a tiny -sized sliver almost certain to contain the answer. It
finds the median in expected comparisons, beating quickselect's
constant and far below median-of-medians', which is why high-performance numeric
libraries reach for it when comparisons are the bottleneck. It is a refinement of
the same sample to guess a good pivot
idea, pushed to two pivots and a sampled
estimate of where lands.
Streaming and approximate selection. When the data is a stream too large to
store — network packets, sensor readings, query logs — you cannot partition an
array you never hold. Exact selection provably needs space in one
pass, so practical systems compute approximate quantiles instead. Sketches like
Greenwald–Khanna (2001) and the t-digest (Dunning, 2019) maintain a small
summary, space, that answers the -th order statistic, within rank error
for any . These
power the percentile latency dashboards (, ) that every
production service watches; the exact median of a billion requests is neither
needed nor affordable, but a th percentile good to a fraction of a percent
is both.
Parallel and distributed medians. On a cluster the data is sharded across machines and no single node sees it all. The median-of-medians idea reappears: each machine computes a local summary or weighted median, a coordinator combines these into a pivot estimate, and one round of counting how many global elements fall below the pivot narrows the search — a distributed echo of the sequential partition-and-recurse. The recurring theme across all three settings is the one this lesson opened with: selection requires less than sorting, and every regime, sequential, streaming, or distributed, finds a way to do only the work the answer needs.4
Takeaways
- Selection finds the -th smallest element; it needs less than sorting, so it can run in .
- = quicksort's partition, but recurse into only the side that holds the answer; expected via a geometric (non-branching) recurrence, worst case .
- Median of medians chooses a provably balanced pivot using groups of five, guaranteeing the recursion drops at least elements per side.
- That balance yields , and because , the recurrence solves to worst-case .
- In practice randomized quickselect wins on constants; median-of-medians matters for guarantees and as a quicksort pivot rule.
- Closest pair of points is divide-and-conquer with the same flavor: split by median , recurse on each half, then combine over a width- strip. A geometric argument caps the strip work at comparisons per point, giving .
Footnotes
- CLRS, Ch. 9 — Medians and Order Statistics: selecting the -th order statistic in linear time without fully sorting. ↩
- Erickson, Algorithms, Ch. 1 — Recursion: quickselect adapting quicksort's partition to recurse into only the side that holds the answer. ↩
- CLRS, Ch. 9 — Medians and Order Statistics: the Blum–Floyd–Pratt–Rivest–Tarjan median-of-medians algorithm achieving worst-case via groups of five. ↩
- Skiena, The Algorithm Design Manual, §4 — Sorting and Searching: randomized quickselect as the practical choice over deterministic median-of-medians. ↩ ↩2
╌╌ END ╌╌