The floors

The floor moves when the question does

Sorting 4,096 elements needs at least 43,250 comparisons. Finding one element among the same 4,096, already sorted, needs at least 13. The difference is a factor of 3,300 and it comes entirely from how many different answers the algorithm has to be able to give. A lower bound is a property of the question, not of any algorithm.

The decision-tree argument that gives sorting its floor is not really about sorting. It is about how much a yes-or-no question can convey, and it applies to any problem where the algorithm learns by asking them.

Change the problem and the same machinery gives a different number. The machinery is: count the outcomes the algorithm must be able to distinguish, take the base-two logarithm, and that is how many comparisons it needs at minimum.

Applied to two questions about the same 4,096 elements:

  • Sort them. There are 4096!4096! possible orderings. log2(4096!)=43,250\log_2(4096!) = 43{,}250 comparisons.
  • Find one of them, given that they are sorted. There are 4,097 possible answers — each position, plus “absent”. log24097=13\lceil \log_2 4097 \rceil = 13 comparisons, because 212=40962^{12} = 4096 is one short.

A factor of about 3,300, from the same argument applied to the same data. The difference is entirely in how many answers the algorithm must be able to produce.

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. 1 Two questions, two floors, and what actual algorithms achieve against each. The bars are logarithmically scaled because the range is otherwise undrawable. Binary search’s worst case over all 4,096 possible targets is exactly 13 — it meets its floor rather than approaching it — while linear search’s is 4,096.

Binary search is optimal, not merely good

The searching bound is worth dwelling on because it is one of the few places in this subject where an algorithm meets its lower bound exactly, with nothing left over.

The argument is the same as for sorting. A search on a sorted array of nn elements must distinguish n+1n + 1 outcomes: the target is at position 0, or 1, …, or n1n-1, or is not present. Each comparison yields one bit. A binary tree distinguishing n+1n+1 outcomes has depth at least log2(n+1)\lceil \log_2(n+1) \rceil, so some target costs at least that many comparisons.

For n=4096n = 4096: log24097=13\lceil \log_2 4097 \rceil = 13, since 212=40962^{12} = 4096 falls one outcome short.

Measured over all 4,096 possible targets, binary search’s worst case is exactly 13. Not 14, not 13.4 — thirteen, matching the floor with no slack at all. The site’s gate asserts this equality rather than an inequality, because “binary search is optimal” is a stronger and more interesting claim than “binary search is efficient”, and equality is the form the stronger claim takes.

The reason it achieves the bound is visible in the algorithm. Each comparison splits the remaining candidate range as close to in half as possible, so each one extracts a full bit. There is no navigation overhead, no repeated work, nothing discarded. Merge sort comes within 2.2% of its floor and the 2.2% is structural; binary search has no gap to explain.

Why the sorting floor is so much higher

The factor of 3,300 is not because sorting is “harder” in some vague sense. It is arithmetic.

Sorting must distinguish n!n! outcomes and searching must distinguish n+1n+1. By Stirling, log2(n!)nlog2n1.44n\log_2(n!) \approx n \log_2 n - 1.44n, which grows almost linearly in nn, while log2(n+1)\log_2(n+1) grows logarithmically. The gap therefore widens without limit: at n=16n = 16 it is a factor of 9, at n=4,096n = 4{,}096 a factor of 3,300, and at a million a factor of about a million.

The useful way to hold this is in bits. Sorting a million elements requires learning about 18.5 million bits of information about the input. Finding one element among a million sorted ones requires learning 20. It is not surprising that one takes longer.

This also explains something that surprises people the first time they meet it: it is worth sorting an array before searching it only if it is going to be searched many times. One search costs 20 comparisons on a sorted million-element array and about 500,000 on an unsorted one. Sorting costs 18.5 million. The sort pays for itself after roughly thirty-seven searches, and not before.

Where the bound stops applying

The theorem is about algorithms that learn through comparisons. Step outside that class and the floor does not apply — not because the argument is wrong, but because its premise is false.

Counting sort. Given nn integers known to lie in [0,r)[0, r), allocate rr counters, walk the input incrementing them, then walk the counters emitting values. Zero comparisons. Θ(n+r)\Theta(n + r) operations. On a million integers in the range 0–255 this is dramatically faster than any comparison sort, and it does not contradict the floor because it never compares anything.

What it does instead is use each element’s value as an address. That operation is not in the decision-tree model. It is also not free in practice: the counter array must fit in memory, so counting sort demands a bounded key range, and its memory access pattern is a scatter rather than a sweep, which real hardware charges for.

Interpolation search. On uniformly distributed sorted data, guessing where the target should be — rather than always splitting in the middle — reaches O(loglogn)O(\log \log n) expected comparisons. That is below the log2(n+1)\log_2(n+1) floor, and again it does not contradict it: the floor is a worst-case bound over all inputs, and interpolation search’s worst case is Θ(n)\Theta(n) when the data is distributed badly. The average improves; the guarantee does not.

Both cases show the same thing. A lower bound comes with a model of computation attached, and the model is doing as much work as the mathematics — in the same way that a complexity class comes with an input distribution attached and means very little without one. Quoting a bound without its model is quoting half of it.

Why the floor is log₂(n!): four elements need five comparisonsEach internal node is one comparison and has two outcomes, so a run of the algorithm is a root-to-leaf path and the path's length is that run's comparison count. Every one of the 24 orderings of 4 elements must arrive at its own leaf, or two inputs needing different answers would receive the same one. The deepest tree on the left has 16 leaves. Eight orderings, in red, have nowhere to go — so no comparison sort of four elements can always finish in four comparisons, and the floor is ⌈log₂ 24⌉ = 5.every algorithm that makes at most 4 comparisonsthe 24 orderings of 4 elementsroot16 leaves16 seated · 8 with no leafone comparison per level, two outcomes per comparison⌈log₂(4!)⌉ = 5 comparisons
Fig. 2 The argument that gives both floors, drawn for the smallest case where it bites. Four elements have 24 orderings; a tree of depth four has 16 leaves; eight orderings have nowhere to go. Change 24 to n+1 and the same picture gives the searching bound. The machinery is identical and only the count of outcomes differs.

Linear search is not stupid

Linear search’s worst case is nn comparisons — 4,096 where binary search needs 13 — and by the comparison count it is one of the worst algorithms on this site.

It is also the algorithm most likely to win on a small array, and the reason is not about comparisons at all.

Linear search reads memory in perfect sequential order. Every access is to the next element, which is the one pattern hardware is unambiguously good at: the prefetcher sees the stream and fetches ahead, and a cache line holding eight elements is paid for once and used eight times. Binary search jumps: first to the middle, then to a quarter point, then an eighth. Every one of the first several probes is to a location far from the last, and on a large array each is a fresh cache miss.

So the comparison ratio is 315 to 1 in binary search’s favour at n=4096n = 4096, and the cache-miss ratio is closer to 1 to 1 — binary search’s thirteen probes are thirteen misses, while linear search’s 4,096 accesses are 512 misses and every one of them prefetchable. On small arrays, where the whole thing fits in cache anyway, linear search’s freedom from index arithmetic and unpredictable branches makes it genuinely faster, and this is why real implementations of things like std::lower_bound fall back to linear scanning below a threshold.

The floor is about comparisons. It is silent about everything that decides which algorithm is actually worth writing, which is the machine field’s entire subject.

4,096 accesses, five ordersEvery row performs exactly 4,096 array accesses — the same count an operation-counting analysis would assign them all — and the modelled miss counts differ by a factor of 8. Reading backwards is as cheap as reading forwards, because a cache line is a line whichever end you enter it from. Stepping by 8 elements touches a new line every time and is as expensive as random. Model: fully associative · 32 lines × 8 elements · LRU.cache missesstraight through512100% sequentialbackwards5120% sequentialevery 8th element4,0960% sequentialevery 97th element4,0960% sequentialuniformly random3,8460% sequentialfully associative · 32 lines × 8 elements · LRU8× between best and worst order
Fig. 3 Why the previous paragraph is true. Five access patterns over 4,096 elements, each performing exactly the same number of accesses, with modelled miss counts differing by a factor of eight. Reading straight through is the cheapest thing in the picture. Jumping around is the most expensive. A comparison count cannot see this distinction and it is often the whole story.

A third question, for scale

There is a question between the two, and it makes the pattern clearer.

Find the smallest element in an unsorted array of nn. There are nn possible answers, so the information-theoretic floor is log2n\log_2 n — exactly 12 for n=4096n = 4096.

But the achievable bound is n1n - 1, not 12, and the difference is instructive. The information-theoretic argument gives a necessary condition and not a sufficient one. To be sure that element xx is the minimum, every other element must have lost a comparison at some point — otherwise it might be smaller and nobody checked. That is n1n-1 distinct losses, each requiring its own comparison, so n1n-1 comparisons are needed.

The counting argument gives 12 and the correct answer is 4,095. The bound is valid and enormously loose, which is the normal situation for lower bounds and the reason sorting’s floor is unusual. Sorting happens to be a problem where the information-theoretic bound is also achievable to within a couple of percent. Most problems are not like that, and the gap between what is provably necessary and what is achievably sufficient is where most of the open questions in the subject live.

Finding both the minimum and the maximum is a small further case worth knowing: the naive method costs 2n22n - 2 comparisons, and processing elements in pairs — compare the pair to each other first, then the smaller against the running minimum and the larger against the running maximum — costs 3n/22\lceil 3n/2 \rceil - 2. That saving of 25% is provably optimal, and it is exactly the kind of constant-factor result that a complexity class cannot express, since both methods are Θ(n)\Theta(n) — and exactly the kind that measurement is good at.

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. 4 Back to sorting, where the bound and the achievable meet. Ten algorithms measured against the floor for their question. The reason this picture is worth drawing — and the minimum-finding picture is not — is that the floor here is nearly attained, so the distances mean something.

Reading a floor as a budget

There is a way of using these numbers that has nothing to do with proving anything, and it is the one most likely to be useful in practice.

A floor converts into a time budget the moment the cost of one comparison is known.

Sorting a million strings by a locale-aware collation: the floor is 18.5 million comparisons. If a collation comparison takes 200 nanoseconds — which is realistic for anything doing Unicode normalisation — then no comparison sort finishes this in under 3.7 seconds, on any hardware, with any amount of parallelism per comparison. If the requirement is one second, the requirement cannot be met by sorting with comparisons, and the conversation has to move to precomputing sort keys or to a radix method.

That is a genuinely useful thing to be able to say early in a design discussion, and it takes one logarithm to say it. The equivalent statement from a complexity class — “sorting is Θ(nlogn)\Theta(n \log n)” — supports no such calculation, because it has no constant in it.

The same arithmetic in the other direction is what makes binary search’s optimality worth knowing. Thirteen comparisons on 4,096 elements is not going to be beaten, so where a lookup is too slow the problem is not the search algorithm. It is the memory access pattern, or the comparison cost, or the fact that the lookup is happening at all.

The floor, computed two waysThe exact bound log₂(n!) summed term by term, Stirling's closed form laid over it, and n log₂ n above both for comparison. The exact sum and the approximation agree to better than one part in a million across this range. The gap to n log₂ n is n log₂ e, about 1.44n, which is why an algorithm at exactly n log₂ n comparisons is not optimal but is within a factor that shrinks towards 1.1010010³10⁴10010³10⁴10⁵ncomparisonsn log₂ nlog₂(n!)Stirlingthe two routes agree to 5.1e-7 relativeexact sum · Stirling
Fig. 5 The sorting floor as a function of n, computed exactly and checked against Stirling. Anyone wanting a budget reads it off this curve and multiplies by the cost of one comparison. The line above it is n log₂ n, which overstates the requirement by about 18% at n = 256 and less as n grows.

One more variation makes the pattern complete, and it is the one people most often expect to be free.

Find the median of nn unsorted elements. There are nn possible answers, so the counting argument gives log2n\log_2 n — 12 for n=4096n = 4096, and useless, as it was for the minimum. The achievable bound is linear: median-of-medians selects in Θ(n)\Theta(n) comparisons in the worst case, with a constant that is large but bounded.

That is a genuinely surprising result the first time one meets it, because the obvious way to find the median is to sort, which costs nlognn \log n. Selection is strictly easier than sorting — it needs less information, since the ranks of the elements that are not the median do not have to be determined — and the gap between Θ(n)\Theta(n) and Θ(nlogn)\Theta(n \log n) is exactly what that lesser information is worth.

So three questions about the same array give three floors and three achievable bounds:

question information floor achievable
sort log2(n!)nlog2n\log_2(n!) \approx n \log_2 n nlog2n\approx n \log_2 n
select the median log2n\log_2 n Θ(n)\Theta(n)
search, sorted log2(n+1)\log_2(n{+}1) log2(n+1)\lceil \log_2(n{+}1) \rceil

Only the third has a tight information-theoretic floor. Sorting’s is tight for a different reason — the achievable happens to match — and selection’s is not tight at all, with the real bound coming from an argument about how many elements must be touched rather than about how many bits are needed.

The floor, computed two waysThe exact bound log₂(n!) summed term by term, Stirling's closed form laid over it, and n log₂ n above both for comparison. The exact sum and the approximation agree to better than one part in a million across this range. The gap to n log₂ n is n log₂ e, about 1.44n, which is why an algorithm at exactly n log₂ n comparisons is not optimal but is within a factor that shrinks towards 1.1010010³10⁴10010³10⁴10⁵ncomparisonsn log₂ nlog₂(n!)Stirlingthe two routes agree to 5.1e-7 relativeexact sum · Stirling
Fig. 6 The one floor in that table that this site computes exactly. log₂(n!) summed term by term with Stirling laid over it, and n log₂ n above. The other two floors are a single logarithm each and need no picture, which is itself informative about how much harder sorting is than the alternatives.

What a lower bound is for

It says where not to look.

Where the cost is comparisons and the task is sorting, merge sort is within 2.2% of optimal and there is nothing worth finding. Where the task is searching a sorted array, binary search is exactly optimal and there is nothing at all. Those are unusual and valuable pieces of information, they are the reason to compute a floor even with no intention of writing a new algorithm, and they are what turns a distance-to-floor ratio into advice rather than trivia.

They also indicate what to change when more is needed. Where sorting is too slow and merge sort is already near the floor, the answer is not a better comparison sort — it is to stop comparing. Counting sort, radix sort and bucket sort exist because somebody noticed that the wall was a property of the question, and asked a different one.