The floors

The floor when the values repeat

log₂(n!) counts orderings of distinguishable things. Two hundred and fifty-six values drawn from eight distinct ones have 1,684 bits of permutation entropy and 739 bits of distinguishability, so the real floor is less than half the one every table quotes — and merge sort, which sits exactly on the quoted floor, is 2.3 times above the one that applies.

The comparison-sorting bound rests on counting outputs. There are n!n! orderings, each comparison distinguishes at most two cases, so no comparison sort finishes in fewer than log2(n!)\log_2(n!) comparisons. It is two lines of argument, exact at every nn, and matched to within a couple of percent.

The count of outputs is n!n! only if the keys are distinct.

Take 256 values drawn from eight distinct ones — a column of status codes, a set of grades, a field with a handful of enumerated states. A sorting algorithm cannot tell two equal keys apart and does not need to: every arrangement that produces the same sorted sequence is the same output. The number of outputs it must separate is the multinomial (256k1,,k8)\binom{256}{k_1, \ldots, k_8}, and its logarithm is 739 bits against log2(256!)=1,684\log_2(256!) = 1{,}684.

The real floor is 44% of the quoted one, and every algorithm’s distance from it is more than twice what the usual table reports.

Two floors, 8 distinct values among 256Every sort's comparison count on an input drawn from 8 distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!) = 1,684 bits, which is the floor every table quotes. The left-hand line is the log of the multinomial — 739 bits — which is the number of outcomes a sort must actually separate, and it is 2.28 times lower. Merge sort sits exactly on the right-hand line and is 2.28 times above the left-hand one.the floor that applieslog₂(n!)Merge sort2.3×1,682Shellsort2.3×1,714Merge sort + cutoff2.6×1,938Heapsort4.2×3,101Quicksort, first6.6×4,887Quicksort, random7.2×5,291Quicksort, median-37.8×5,728Insertion sort18.5×13,644Bubble sort43.1×31,820Selection sort44.2×32,6408 distinct values, n = 256, seededthe two floors are 2.28× apart
Fig. 1 Every sort’s comparison count on an input with eight distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!), the quoted floor. The left-hand line, 2.28 times lower, is the floor that applies. Merge sort sits exactly on the quoted line and 2.28 times above the real one.

The measurement

At n=256n = 256 with eight distinct values, seeded:

algorithm comparisons × the entropy floor × log₂(n!)
merge sort 1,682 2.28 1.00
Shellsort 1,714 2.32 1.02
merge sort with a cutoff 1,938 2.62 1.15
heapsort 3,101 4.20 1.84
quicksort, first-element 4,887 6.62 2.90
quicksort, random pivot 5,291 7.16 3.14
quicksort, median of three 5,728 7.75 3.40
insertion sort 13,644 18.47 8.10
bubble sort 31,820 43.07 18.90
selection sort 32,640 44.18 19.38

Three separate things are visible in that table and each is worth its own paragraph.

Merge sort is at 1.00 times the permutation floor. On random distinct input it is at 1.03; here it is at 1.00, and at n=1,024n = 1{,}024 it is at 0.99 — genuinely below the number the sorting bound gives. That is not a violation of anything. log2(n!)\log_2(n!) is a worst-case bound over inputs with distinct keys, and this input does not have distinct keys, so the theorem does not apply to it. An algorithm quietly going under a floor is exactly the signal that the floor being used is the wrong one.

Merge sort is at 2.28 times the floor that does apply. It has not become a worse algorithm; the target moved. Merge sort’s structure does not notice repeats — it merges two runs by comparing their heads whether or not the heads are equal — so it spends the same nlog2nn \log_2 n comparisons it always does, and half of them are now conveying no information.

Every quicksort has got dramatically worse. On random distinct input all three sit near 1.24 times the floor. Here they are at 2.90, 3.14 and 3.40 times the permutation floor and near seven times the real one. Repeated keys are quicksort’s other bad case, and unlike the sorted-input one it is not fixed by the pivot rule — all three rules fail together.

Why two-way partitioning fails on repeats

The reason is worth spelling out because it is the one bad case of quicksort that randomisation does not touch.

A two-way partition splits the array into “less than the pivot” and “not less than the pivot”. Elements equal to the pivot all go to one side. If eight distinct values are spread over a thousand elements, then whichever value the pivot takes, about an eighth of the array equals it and goes wholesale into one partition — and then gets partitioned again, against a pivot that is very likely the same value, producing the same lopsided split.

The recursion therefore keeps re-partitioning blocks of identical elements that are already in their final relative position, and it does so log\log-many times over. At n=1,024n = 1{,}024 median-of-three does 71,378 comparisons where merge sort does 8,645: a factor of 8.3, between two algorithms that are within 30% of each other on random input.

The standard fix is three-way partitioning — Dijkstra’s Dutch national flag — which splits into less, equal and greater, and never looks at the equal block again. On an input with dd distinct values it turns the cost from Θ(nlogn)\Theta(n \log n) into Θ(nlogd)\Theta(n \log d), which for d=8d = 8 and n=1,024n = 1{,}024 is a factor of three and a bit. Every production quicksort has it, and its presence is a decision about this input distribution specifically.

That is the same shape as the insertion-sort cutoff: a universally-implemented modification, justified by a case the headline complexity does not mention, and invisible in every summary of the algorithm.

Two floors, 8 distinct values among 1024Every sort's comparison count on an input drawn from 8 distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!) = 8,769 bits, which is the floor every table quotes. The left-hand line is the log of the multinomial — 3,034 bits — which is the number of outcomes a sort must actually separate, and it is 2.89 times lower. Merge sort sits exactly on the right-hand line and is 2.85 times above the left-hand one.the floor that applieslog₂(n!)Merge sort2.8×8,645Shellsort2.9×8,664Merge sort + cutoff3.3×9,972Heapsort5.2×15,874Quicksort, first22.6×68,676Quicksort, random22.8×69,115Quicksort, median-323.5×71,378Insertion sort75.9×230,351Bubble sort169.6×514,731Selection sort172.6×523,7768 distinct values, n = 1024, seededthe two floors are 2.89× apart
Fig. 2 The same input distribution at four times the size. The two floors have moved further apart — 2.89 times rather than 2.28, because more elements among the same eight values means more indistinguishable orderings — and the quicksorts have moved much further right. What separates the two groups is whether the algorithm’s cost depends on the number of distinct values or only on n.

The floors are further apart at larger n

At n=256n = 256 the two floors differ by a factor of 2.28; at n=1,024n = 1{,}024, by 2.89; at n=4,096n = 4{,}096 the gap keeps widening.

This is not subtle once written down. With nn elements over dd equally likely values, the permutation floor is about nlog2n1.44nn\log_2 n - 1.44n and the entropy floor is about nlog2dn \log_2 d. The first grows faster than linearly and the second is linear, so the ratio grows like log2n/log2d\log_2 n / \log_2 d without bound.

The consequence is that the sorting bound becomes more misleading as the input grows, which is the opposite of the usual direction for an asymptotic statement. Somebody sorting a billion records with a dozen distinct keys is being told a floor that is six or seven times too high.

How few distinct values it takes

Eight distinct values among 256 is a strong case, and the obvious question is where the effect starts to matter. Sweeping the number of distinct values at n=1,024n = 1{,}024:

distinct values permutation floor entropy floor ratio merge sort ÷ entropy floor
2 8,769 1,018 8.61 7.06
4 8,769 2,029 4.32 4.06
8 8,769 3,034 2.89 2.85
32 8,769 4,974 1.76 1.79
128 8,769 6,717 1.31 1.33
1,024 8,769 8,327 1.05 1.07

The entropy floor is close to nlog2dn\log_2 d — 1,024 elements over dd values need log2d\log_2 d bits each — and the permutation floor is fixed at nlog2n1.44nn\log_2 n - 1.44n regardless. So the ratio is roughly log2n/log2d\log_2 n / \log_2 d, and it is above two whenever d<nd < \sqrt{n}.

That is a low bar. Thirty-two distinct values among a thousand elements already puts the quoted floor 76% too high, and thirty-two distinct values is an enumeration, a rating, a day of the month or a small category. The last row is the reassuring one: drawing 1,024 values uniformly from 1,024 possibilities still produces plenty of collisions — about 37% of the possible values do not appear at all, so a comparable share of those that do appear more than once — and the ratio is nonetheless only 1.05, so the effect really does need the alphabet to be small rather than merely finite.

The right-hand column is the useful one, because it shows what a comparison sort can do about it. Merge sort’s count tracks the entropy floor’s multiple almost exactly: 7.06 at d=2d = 2, 2.85 at d=8d = 8, 1.07 at d=1,024d = 1{,}024. Its absolute comparison count barely moves — 7,193, 8,229, 8,645, 8,909, 8,939, 8,937 across the whole sweep — because merge sort does not notice repeats at all. It spends nlog2nn \log_2 n comparisons whatever the alphabet, and the floor falls away beneath it.

An algorithm that did notice would spend Θ(nlogd)\Theta(n \log d), and three-way partitioning is exactly that algorithm. The gap between the two columns is the space that three-way partitioning, counting sort and radix sort occupy, and it is a factor of seven at d=2d = 2 and nothing at all at d=nd = n.

Two floors, 8 distinct values among 4096Every sort's comparison count on an input drawn from 8 distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!) = 43,250 bits, which is the floor every table quotes. The left-hand line is the log of the multinomial — 12,244 bits — which is the number of outcomes a sort must actually separate, and it is 3.53 times lower. Shellsort sits exactly on the right-hand line and is 3.44 times above the left-hand one.the floor that applieslog₂(n!)Shellsort3.4×42,170Merge sort3.5×42,338Merge sort + cutoff3.9×47,734Heapsort6.3×77,419Quicksort, random86.8×1,062,542Quicksort, first87.4×1,070,142Quicksort, median-387.9×1,076,785Insertion sort300.3×3,677,047Bubble sort672.8×8,237,775Selection sort684.9×8,386,5608 distinct values, n = 4096, seededthe two floors are 3.53× apart
Fig. 3 The same eight distinct values among sixteen times as many elements. The two floors are further apart again, and the quicksorts have moved further right still — the gap between the bound everybody quotes and the bound that applies grows with the data.

Where the linear-time sorts come from

The entropy floor is also the answer to a question the floors field has otherwise had to dodge: how counting sort and radix sort can beat a bound that is supposed to be unbeatable.

They are not comparison sorts, so the decision-tree argument does not constrain them at all — that is the standard answer and it is correct and slightly unsatisfying, because it explains why the theorem does not apply without explaining what they are exploiting.

The entropy floor explains it. Counting sort’s cost on this input is not a comparison count at all; it reads each value once, increments a counter, and writes the output: 256 reads and 264 writes, 520 operations in total, against merge sort’s 1,682 comparisons and 3,364 reads. It never distinguishes two equal keys because it never compares anything, and its cost is proportional to n+dn + d rather than to either floor.

So the honest statement is not “counting sort breaks the bound”. It is:

  • the permutation floor, 1,684 bits, describes a problem this input does not pose;
  • the entropy floor, 739 bits, describes the information that must be extracted;
  • a comparison sort extracts it one bit at a time and needs at least 739 comparisons;
  • an algorithm allowed to look at the values extracts all of it in one pass, because reading an 8-valued key yields three bits rather than one.

The floor moves when the operation moves, which is the floors field’s recurring result arriving from a new direction. Every previous instance changed the question; this one changes what a step is allowed to be.

The floor in bits, which is where radix sort lives

There is one more floor under both of the ones above, and naming it completes the picture.

An algorithm that may look at the bits of a key is not bounded by the number of comparisons at all; it is bounded by how many bits it must read. Sorting nn keys of bb bits requires reading nbnb bits in the worst case, because a key never read cannot be placed. That is the input-size floor, and it is the analogue of the adversary bound for connectivity — a coverage argument rather than a counting one.

Radix sort achieves it to within a constant. It reads each key once per digit, and with bb bits taken rr at a time that is b/rb/r passes over nn elements. For 32-bit keys and 8-bit digits, four passes: 4n4n reads and 4n4n writes, whatever nn is.

Set the three floors side by side for 1,024 keys drawn from eight values:

floor value what it constrains
permutation, log2(n!)\log_2(n!) 8,769 bits comparison sorts, distinct keys
entropy, log2\log_2 of the multinomial 3,034 bits comparison sorts, this input
input size, nbnb 3,072 bits at 3 bits per key anything that must read the input

The bottom two are nearly equal, and that is not a coincidence — with dd equally likely values the entropy is nlog2dn\log_2 d and the minimum key width is log2d\lceil\log_2 d\rceil bits, so the two agree whenever dd is a power of two. The information a sort must extract and the information it must read are the same information.

Which is the cleanest way to see why the linear-time sorts are not cheating. They are not getting under a floor; they are operating against a different and lower floor, they reach it, and the reason the comparison sorts cannot is that a comparison yields one bit where a digit read yields rr.

The gap between the two floors is not a fact about one size. It widens with nn at a fixed number of distinct values, because log2n!\log_2 n! grows faster than the multinomial does, and two more points of the sweep say so.

Two floors, 8 distinct values among 512Every sort's comparison count on an input drawn from 8 distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!) = 3,875 bits, which is the floor every table quotes. The left-hand line is the log of the multinomial — 1,504 bits — which is the number of outcomes a sort must actually separate, and it is 2.58 times lower. Merge sort sits exactly on the right-hand line and is 2.55 times above the left-hand one.the floor that applieslog₂(n!)Merge sort2.6×3,837Shellsort2.6×3,980Merge sort + cutoff3.0×4,504Heapsort4.7×7,082Quicksort, random11.9×17,855Quicksort, first12.0×17,990Quicksort, median-313.1×19,718Insertion sort36.6×55,019Bubble sort85.5×128,605Selection sort87.0×130,8168 distinct values, n = 512, seededthe two floors are 2.58× apart
Fig. 4 Eight distinct values among 512 elements. The quoted floor is log2(512!)=3,875\log_2(512!) = 3{,}875 bits; the multinomial — the number of outcomes a sort actually has to separate — is 1,504, a factor of 2.58. Merge sort sits exactly on the first line and 2.55 times above the second.
Two floors, 8 distinct values among 2048Every sort's comparison count on an input drawn from 8 distinct values, on a logarithmic axis, with both floors marked. The right-hand line is log₂(n!) = 19,580 bits, which is the floor every table quotes. The left-hand line is the log of the multinomial — 6,101 bits — which is the number of outcomes a sort must actually separate, and it is 3.21 times lower. Shellsort sits exactly on the right-hand line and is 3.16 times above the left-hand one.the floor that applieslog₂(n!)Shellsort3.2×19,252Merge sort3.2×19,269Merge sort + cutoff3.6×22,088Heapsort5.8×35,221Quicksort, first44.3×270,096Quicksort, random44.4×270,697Quicksort, median-344.9×273,984Insertion sort152.1×927,685Bubble sort337.6×2,059,813Selection sort343.6×2,096,1288 distinct values, n = 2048, seededthe two floors are 3.21× apart
Fig. 5 And the same eight values among 2,048. The quoted floor is 19,580 bits and the multinomial is 6,101 — a factor of 3.21, against 2.58 at a quarter of the size. The floor that is quoted and the floor that binds are diverging, and the algorithms are tracking the wrong one.

What the gate does with this

Two assertions, and the second is the one that would catch a mistake.

The two floors must be far apart. With eight distinct values among 256, the entropy floor must be under half the permutation floor. If the input generator were changed so that repeats became rare, this figure would be comparing two nearly-identical lines and its whole argument would evaporate — silently, with a perfectly reasonable-looking picture.

At least one algorithm must sit on the permutation floor. Merge sort at 1.00 is the evidence that the quoted bound is the wrong one: an algorithm exactly at a floor, on an input where a better algorithm exists, means the floor is not binding. If merge sort ever drifted to 1.3 the figure would still draw and the essay’s opening claim would be unsupported.

There is also a check that does not appear here and appears everywhere else in this field. The main floor figure asserts that nothing averages below the floor, which is the assertion that catches an instrument that has stopped counting. It cannot be used on this input, because on this input going below log2(n!)\log_2(n!) is correct behaviour and merge sort does it at n=1,024n = 1{,}024.

Losing an assertion is a real cost and it is the reason this is a separate essay with a separate figure rather than an option on the existing one. A gate that has to be disabled for one case is a gate with a hole in it; a second figure with its own two assertions is not.

Comparisons used, as a multiple of the floor, n = 256The information-theoretic floor at n = 256 is 1,684 comparisons: a binary decision tree with 256! leaves cannot be shallower than that. The bar is what each algorithm averaged over 16 random inputs, divided by the floor. Merge sort comes within 2% of it; Selection sort uses 19 times as many.the floorMerge sort1.02×Quicksort, first1.23×Quicksort, random1.24×Merge sort + cutoff1.29×Quicksort, median-31.32×Shellsort1.45×Heapsort1.96×Insertion sort9.69×Bubble sort19.26×Selection sort19.38×floor = log₂(256!) = 1,684 comparisonsmean of 16 runs, against a proved bound
Fig. 6 The comparison, on random distinct input, where the permutation floor is the right one. Merge sort at 1.02, heapsort at 1.96, selection sort at 19.4 — and every one of those ratios is against a bound that genuinely applies. Put this figure beside the one at the top of this essay and the same algorithms have moved by factors of two to three, from changing nothing but how many distinct values the input contains.

Skew makes it worse, and real data is skewed

Every measurement above uses eight values that occur about equally often, and that is the kindest case for the quoted floor. The multinomial’s logarithm is nHn H, where HH is the entropy of the value distribution, and HH is at its largest when the values are equally likely. Make them unequal and the floor falls further.

Take the shape a real column actually has. A status field where 99% of rows are ok and the remaining one per cent is spread over seven other codes has an entropy of about 0.11 bits per element. At n=1,024n = 1{,}024 that is a floor of a hundred and twelve bits.

input, n=1,024n = 1{,}024 entropy per element floor log2(n!)\log_2(n!) ratio
eight values, equally likely 3.00 3,034 8,769 2.9
eight values, 99% in one 0.11 112 8,769 78

The quoted floor is seventy-eight times the one that applies, and merge sort — which does not look at values and spends about 8,900 comparisons whatever the input contains — is eighty times above the information it is extracting.

That is not an exotic instance. It is what a status column, an error code, a boolean flag with a rare true, a category with a dominant member, or any log line’s severity field looks like. The distribution that makes the standard bound most misleading is the distribution real data most often has, and it is misleading in the direction that flatters every algorithm measured against it.

It also says which algorithms would notice. Three-way partitioning collapses the dominant value into a single equal block in one pass, so it does very well here; a run-detecting merge sort finds the long stretches of the dominant value as ready-made runs and merges them cheaply. Both are exploiting skew rather than merely tolerating repeats, and the difference between the two rows above is exactly the size of the prize.

The floor a comparison sort can reach, and the one it cannot

Three-way partitioning is offered above as the fix, and it is worth being precise about which floor it reaches, because it is not the one in the table.

A three-way quicksort on nn elements over dd distinct values costs Θ(nlogd)\Theta(n \log d). When the values are equally likely that is the entropy floor up to a constant, since H=log2dH = \log_2 d exactly, and the algorithm is optimal in the sense that matters.

When they are not equally likely, HH is strictly less than log2d\log_2 d — 0.11 against 3.00 in the table above — and Θ(nlogd)\Theta(n\log d) is no longer close to nHnH. The algorithm’s cost depends on how many distinct values there are and the floor depends on how they are distributed, and the two come apart exactly when the distribution is skewed.

Closing that last gap needs an algorithm whose comparison count is governed by the frequencies rather than by the count of distinct values, and there is one: sort by merging the runs of equal elements in a Huffman-shaped merge tree, so that the frequent values are merged at shallow depth and the rare ones deep. The total is nH+O(n)nH + O(n) comparisons, which is the floor.

That construction is the coding field’s tree, used for a different purpose. A Huffman code assigns short codes to frequent symbols and its expected length is within a bit of the entropy; here the same tree assigns shallow merges to frequent values and its comparison count is within a constant of the same entropy. Sorting a multiset optimally and coding it optimally are the same problem, and the identical tree solves both — which is worth knowing chiefly because it says the floor in this essay is not a curiosity but the same quantity the compression field has been measuring all along.

It is named here and not built. What is measured on this page is where the floors are; which algorithm reaches which of them is a claim about implementations, and the only one this collection has measured is that the algorithms it holds reach none of them on this input.

What to take from it

The general lesson is not about repeated keys. It is that a lower bound is a statement about a set of inputs, and the set is usually named so casually that it disappears.

log2(n!)\log_2(n!) is quoted as “the sorting bound”. It is the bound for sorting nn distinct keys by comparison, and the word “distinct” is doing as much work as the word “comparison” — which at least gets stated, because the exception to it is famous.

Real data is full of repeats. A sorted column of timestamps at second resolution, a list of countries, an enumeration, a floating-point field rounded for display: all of these have dnd \ll n, and for all of them the quoted floor overstates the unavoidable work by a factor that grows with the size of the data. An algorithm chosen because it comes within 2% of a floor is coming within 2% of the wrong floor.

Two questions, two floors, n = 4096Sorting 4096 elements cannot be done in fewer than 43,250 comparisons; finding one element in a sorted array of 4096 cannot be done in fewer than 13, and binary search's worst case over all 4096 targets is exactly 13. The difference is a factor of 3,327, and it comes entirely from how many outcomes the algorithm must be able to distinguish — 4096! orderings against 4096 + 1 positions. A lower bound is a property of the question, not of any algorithm.comparisons (bar length is log-scaled)sorting, floor43,250sorting, merge sort43,976searching, floor13searching, binary13searching, linear4,096green outline: a proved floor · blue: a measured run3,327× between the two floors
Fig. 7 Four questions about the same data with four different floors. This essay adds a fifth line that would sit below all of them: the same question, the same algorithm, and an input whose keys are not all different.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

The 8 essays that link to this one and share the most of its objects, of 12 that link here.

The objects this essay names

Each one links to every other essay that touches it.

Comparison countCounting sortEntropyLower boundMultisetPivotQuicksortThree-way partition