Counting

The count of the part that was read

Handing back the smallest ten of 65,536 keys in order costs 965,656 comparisons by sorting them and 65,670 by a knockout tournament, against a floor of 65,526. Read to the last element, the same tournament makes exactly merge sort's 965,656 — it is merge sort, charged one element at a time. A sort's count has no term for how much of its answer anyone reads, and the two floors that do have one cannot simply be added.

Every sort measured so far hands back a complete order: where each of nn elements belongs. The sort whose count has no distribution ended on the observation that a great deal of sorting is done for callers who do not want that. A search shows its best ten results. A query asks for the hundred nearest points. A report lists the first page and waits to be asked for the second. Each of these reads a prefix of the sorted order and discards the rest, and each pays, if it calls a sort, for the rest.

A sort’s comparison count has no term in it for this. Merge sort on 65,536 keys makes about a million comparisons whether the caller reads one element or all of them, because the count is a function of the input, and how much of the output is used is not part of the input. The quantity that matters to the caller — call it kk, the number of elements read — does not appear in any bound a sort is described by.

This page puts kk into the count. It takes six ways of handing back the kk smallest keys in order, counts each one’s comparisons as kk runs from one to all 65,536, and sets them against what no method can do better than.

Two floors, and why they do not add

There are two easy arguments for a floor, and they bound different things.

Every element not handed back must lose a comparison. To know that an element is not among the kk smallest, a method must have seen it compared with something smaller, and each comparison has exactly one loser. So at least nkn - k comparisons are needed, whatever else is learned. For k=1k = 1 this is the familiar fact that finding a minimum of nn takes n1n - 1 comparisons.

The answer must be distinguished from every other possible answer. The output is a sequence of kk positions, in order, chosen from nn, and there are n!/(nk)!n!/(n-k)! such sequences. Each comparison has two outcomes, so at least log2n!/(nk)!\lceil\log_2 n!/(n-k)!\rceil comparisons are needed. This is the argument the floor under every comparison sort makes for a full sort, and at k=nk = n it is that floor exactly. The floor moves when the question does made the general point that a floor belongs to a question, and kk is a dial between two questions.

It is tempting to add the two, since one counts losers and the other counts bits. At k=1k = 1 the sum is 65,535+16=65,55165{,}535 + 16 = 65{,}551, and a simple scan finds the minimum of 65,536 keys in 65,535 comparisons — sixteen fewer than the sum. The same comparisons that eliminate the losers carry the information, and nothing forces a method to pay for them twice. The floor is the larger of the two, not their sum, and the plates below draw it that way. The checks require the sum to be refused as a floor by a method that goes under it.

The larger of two floors has a weak point, and it is where they cross. For 65,536 keys the loser count starts at 65,535 and falls slowly as kk grows; the information count starts at 16 and rises by about sixteen for every element read. They meet near k=3,900k = 3{,}900, at about 61,700 comparisons each, and there each is saying only that the other might be right. Neither floor is close to what any method achieves at that point, and it is worth remembering when the ratios below peak there.

Six ways to hand back the first k

The smallest 10 of 65,536 in order: 965,656 comparisons by sorting everything, 65,670 by tournament, and a floor of 65,526Comparisons made to hand back the k smallest of 65,536 randomly ordered distinct keys in order, by six methods, against k, on logarithmic axes; every result is checked against the true answer. The dashed line is the floor, the larger of n − k and ⌈log₂ n!/(n−k)!⌉. The two methods that draw random pivots, selection and incremental quicksort, are means over 16 seeds. sort all, read k: 965,656 at 1, 965,656 at 4, 965,656 at 10, 965,656 at 40, 965,656 at 100, 965,656 at 400, 965,656 at 1,000, 965,656 at 4,000, 965,656 at 10,000, 965,656 at 40,000, 965,656 at 65,536. build a heap, pop k: 123,525 at 1, 123,615 at 4, 123,795 at 10, 124,693 at 40, 126,487 at 100, 135,459 at 400, 153,399 at 1,000, 242,673 at 4,000, 419,761 at 10,000, 1,272,631 at 40,000, 1,895,071 at 65,536. select k, sort those: 123,520 at 1, 123,657 at 4, 123,676 at 10, 123,940 at 40, 125,483 at 100, 128,845 at 400, 145,737 at 1,000, 208,070 at 4,000, 316,029 at 10,000, 768,185 at 40,000, 1,057,777 at 65,536. incremental quicksort: 123,520 at 1, 123,657 at 4, 123,670 at 10, 123,946 at 40, 125,038 at 100, 128,637 at 400, 144,202 at 1,000, 211,965 at 4,000, 344,225 at 10,000, 881,117 at 40,000, 1,275,254 at 65,536. keep the best k while scanning: 65,535 at 1, 65,622 at 4, 65,919 at 10, 68,174 at 40, 73,049 at 100, 98,056 at 400, 145,352 at 1,000, 345,344 at 4,000, 644,394 at 10,000, 1,215,968 at 40,000, 1,105,315 at 65,536. knockout tournament: 65,535 at 1, 65,580 at 4, 65,670 at 10, 66,120 at 40, 67,020 at 100, 71,520 at 400, 80,512 at 1,000, 125,388 at 4,000, 214,667 at 10,000, 647,294 at 40,000, 965,656 at 65,536. Floor: 65,535 at 1, 65,532 at 4, 65,526 at 10, 65,496 at 40, 65,436 at 100, 65,136 at 400, 64,536 at 1,000, 63,821 at 4,000, 158,839 at 10,000, 617,016 at 40,000, 954,037 at 65,536.1101001,00010,00010⁵10⁶k, the elements the caller readscomparisonssort all, read kbuild a heap, pop kselect k, sort thoseincremental quicksortkeep the best k while scanningknockout tournamentdashed: the floorlabels at k = 1,00065,536 random distinct keysevery answer checked
Fig. 1 Comparisons to hand back the k smallest of 65,536 randomly ordered distinct keys in order, against k, on logarithmic axes, with every answer checked; the dashed line is the floor, and the two methods that draw random pivots are means over sixteen seeds. For the smallest ten: sort everything 965,656; build a heap and pop 123,795; select then sort 123,676; incremental quicksort 123,670; keep the best ten while scanning 65,919; knockout tournament 65,670; floor 65,526. For the smallest thousand: 965,656; 153,399; 145,737; 144,202; 145,352; 80,512; floor 64,536. For all of them: 965,656; 1,895,071; 1,057,777; 1,275,254; 1,105,315; 965,656; floor 954,037.

Sort everything, then read kk. A flat line at 965,656, fifteen times the floor for any small kk, and within 1.2% of it at k=nk = n, where the question is sorting.

Build a heap, then pop kk times. Building a binary heap from the bottom costs about 1.9n1.9n comparisons — building a heap from the bottom measured 1.87 per element on random input — and each pop sifts a replacement down about log2n\log_2 n levels at two comparisons a level. So the line starts at 123,525 for the smallest element and rises by about 30 comparisons per element read: 1.9n+2klog2n1.9n + 2k\log_2 n. It is the method every priority-queue library supports, and it pays the building cost before the first element whatever kk turns out to be.

Select, then sort. Partition around random pivots until the kk smallest are separated from the rest, then sort those kk. Selection by random pivots is expected to cost about 2n2n comparisons when kk is small and more as kk approaches the middle, and sorting kk elements adds about klog2kk\log_2 k. The method needs kk in advance.

Incremental quicksort. Paredes and Navarro’s method partitions only the leftmost unsettled range, and only when the caller asks for the next element, keeping a stack of pivot positions so that no partition is ever repeated. For small kk it does the same work as selection. It does not need kk in advance, and read to the end it becomes a quicksort, at 1,275,254 comparisons.

Keep the best kk while scanning. Walk the input once, holding the kk smallest seen so far in a heap whose root is the largest of them; each new element is compared with that root and, if smaller, replaces it. At k=1k = 1 this is the minimum scan, and it meets the floor exactly. It is what a database does for a query with a small LIMIT over an ORDER BY, and what a standard library’s “n smallest” function does when nn is small.

A knockout tournament. Pair the keys and keep the smaller of each pair, pair the winners and keep the smaller again, and so on up to a single champion — n1n - 1 comparisons to find the smallest. To find the next, remove the champion’s leaf and replay only the matches along its path to the top. The tournament’s line hugs the floor from k=1k = 1 to k=400k = 400 and is never above any other line on the plate.

A tournament, drawn

A knockout tournament on sixteen keys after 3 have been read: 15 matches to crown the first, then 3, 3, 3 to replay each pathA tournament over sixteen keys: each internal node holds the smaller of its two children, so the root holds the smallest key. Building it takes 15 comparisons, one per internal node. Reading the smallest key removes its leaf, and only the 4 nodes on the path from that leaf to the root are replayed, each against its other child. The keys read so far are 10, 11, 12; the replays after each cost 3, 3, 3 comparisons (a replay against an empty side costs nothing), and the path replayed last is highlighted. The root now holds 13, the next smallest.13151315161413181516201914211318·2215·162025231914·21241317read so far: 10, 11, 12comparisons: 15 to build · 3 + 3 + 3 to replaysixteen keys · highlighted: the last path replayedroot: 13
Fig. 2 A knockout tournament on sixteen keys after three have been read. Each internal node holds the smaller of its two children, so the root holds the smallest key remaining. Building the tree took 15 comparisons, one per internal node. Reading a key removes its leaf and replays only the four nodes on the path from that leaf to the root, each against its other child; the three replays cost 3, 3 and 3 comparisons, because the bottom match is against an empty leaf and costs nothing. The keys read so far are 10, 11 and 12, the path replayed last is highlighted, and the root now holds 13.

The count can be read off the drawing. The first key costs n1n - 1 comparisons, one per internal node, and that already meets the loser floor exactly: every key but one has lost one match. Each later key costs one comparison per level of the tree above the bottom, which is log2n1\log_2 n - 1 — fifteen, for 65,536 keys. So the tournament’s count for the first kk is very nearly n1+15(k1)n - 1 + 15(k-1), and at k=1,000k = 1{,}000 that is 80,520 against a measured 80,512, the difference being the few replays that meet an emptied subtree higher up.

Set that beside the two floors. The first key costs exactly the loser floor. Each later key costs fifteen comparisons, and the information floor charges about sixteen bits per key. The tournament pays each later key’s information almost exactly once, and pays the losers once, and the reason it can do both is the one the sum of the floors missed: every one of its early matches is simultaneously a loss for one key and a bit of the order.

Merge sort, paid in instalments

A tournament that is never told to stop can be read all the way to the last key, and at that point it has produced a full sort.

Read one element at a time, a tournament reaches exactly merge sort's 43,953 comparisons at the last element; the heap pays 1.95 times thatThe running comparison count as a caller reads the 4,096 smallest elements of 4,096 one at a time, for three methods that can stop after any element. knockout tournament: 4,095 before the first element, 15,182 after 1,024, 43,953 after all. build a heap, pop k: 7,682 before the first element, 29,592 after 1,024, 85,634 after all. incremental quicksort: 9,117 before the first element, 19,444 after 1,024, 53,326 after all. Merge sort on the same keys makes 43,953, the dashed line.020,00040,00060,00080,00001,0002,0003,0004,000elements read so farcomparisons made so farknockout tournament · 43,953build a heap, pop k · 85,634incremental quicksort · 53,326merge sort · 43,9534,096 random distinct keysevery prefix checked
Fig. 3 The running comparison count as a caller reads all 4,096 of 4,096 keys one at a time, for three methods that can stop after any key. Knockout tournament: 4,095 comparisons before the first key, 15,182 after 1,024 keys, 43,953 after all. Build a heap and pop: 7,682 before the first, 29,592 after 1,024, 85,634 after all. Incremental quicksort, one run: 9,117, 19,444 and 53,326. Merge sort on the same keys makes 43,953, the dashed line.

The tournament’s line ends on the dashed line exactly, and not approximately: 43,953 comparisons against merge sort’s 43,953, and at 65,536 keys, 965,656 against 965,656. The checks require the two to be equal at three sizes, and they are.

The reason is structural. Each internal node of the tournament, replayed every time one of its keys leaves, is a merge of its two subtrees performed one output at a time. It compares the two current front keys until one side is exhausted and then passes the rest of the other side through without comparing anything, which is precisely merge sort’s merge. The tournament is merge sort in which each merge is carried out lazily — a comparison is made only when a key is actually needed above it. Read to the end, every merge is carried out in full and the counts must coincide.

That gives the first plate’s flat line a precise meaning. Sorting 65,536 keys and reading ten makes 965,656 comparisons. The same merges, carried out only as far as the ten keys require, make 65,670. The other 899,986 were spent merging keys nobody read, and the tournament is the proof that none of them was necessary for the first ten.

The heap, read to the end, is a heapsort. It front-loads the cost of building — 7,682 comparisons before the first key — and then pays about nineteen per key, finishing at 1.95 times merge sort. The incremental quicksort, read to the end, is a quicksort with random pivots; its one run here finishes at 1.21 times merge sort, a little under the 1.29 that quicksort’s expected count predicts at this size.

Knowing k in advance, and not

The six methods divide on a line the count does not show: whether they need to be told kk before they start.

Selecting then sorting needs kk to know where to partition. The bounded heap needs kk to know how large a heap to keep. Sorting everything does not need it, but ignores it. The other three — build-and-pop, incremental quicksort and the tournament — can hand back one key, stop, and resume later when asked for the next, without having done anything on the first call that the caller did not need for that call. That is the interface a caller actually has when it reads a result page by page, or stops scanning search results when it finds what it wanted, and it rules out two of the six.

Among the three that can stop, the instalments plate is also a schedule of payments, and the schedules differ. The tournament charges n1n - 1 for the first key and about fifteen for each key after it. The heap charges about 1.9n1.9n for the first and about nineteen after. Incremental quicksort charges about 2n2n for the first, on average, and then irregular amounts: a key that lands in a range already partitioned costs almost nothing, and a key that forces a new partition of a long range costs a great deal. What amortised means drew the sawtooth hidden under a dynamic array’s constant amortised append; the same distinction applies here. The tournament’s per-key cost after the first is nearly flat and nearly predictable. Incremental quicksort’s has a low average and a long tail, and a caller that must return each page within a deadline cares about the tail.

What the cheapest count is paid for in

Every plate on this page counts comparisons, and the tournament wins nearly all of them. It does not win on the other axis this site measures.

A tournament over nn keys keeps a tree with n1n - 1 internal nodes, each recording which key won there, beside the keys themselves — about 2n2n slots in all, and it must keep them for as long as the caller might ask for another key. The bounded heap holds exactly kk keys and nothing else, which for k=10k = 10 is ten slots against 131,071. Build-and-pop and incremental quicksort both work inside the array they were given, with a stack of pivot positions for the second that is short on average. Sorting everything with merge sort needs a buffer of nn.

So the method closest to the comparison floor is also the one that holds the most, and the method that holds the least — the bounded heap — is the one whose comparison count collapses as kk grows and on reversed input. The frontier between time and space drew that trade for full sorts, as comparisons against peak auxiliary space, and found that no sort is best on both. Handing back a prefix has the same frontier with kk added to it: at small kk the bounded heap is nearly optimal on both axes at once, and as kk grows the choice becomes a choice of which axis to lose on. Measuring what an algorithm keeps is the argument that this second axis has to be counted rather than assumed, and on this question it decides between the two best methods on the first plate.

Against the floor

Measured against the floor, the tournament stays within 1.96 of it at every k; sorting everything is 14.7 times it for the smallest oneEach method's comparisons for the k smallest of 65,536 divided by the floor at that k, on logarithmic axes; selection and incremental quicksort are means over 16 seeds. sort all, read k: 14.735 at 1, 14.736 at 4, 14.737 at 10, 14.744 at 40, 14.757 at 100, 14.825 at 400, 14.963 at 1,000, 15.131 at 4,000, 6.079 at 10,000, 1.565 at 40,000, 1.012 at 65,536. build a heap, pop k: 1.885 at 1, 1.886 at 4, 1.889 at 10, 1.904 at 40, 1.933 at 100, 2.080 at 400, 2.377 at 1,000, 3.802 at 4,000, 2.643 at 10,000, 2.063 at 40,000, 1.986 at 65,536. select k, sort those: 1.885 at 1, 1.887 at 4, 1.887 at 10, 1.892 at 40, 1.918 at 100, 1.978 at 400, 2.258 at 1,000, 3.260 at 4,000, 1.990 at 10,000, 1.245 at 40,000, 1.109 at 65,536. incremental quicksort: 1.885 at 1, 1.887 at 4, 1.887 at 10, 1.892 at 40, 1.911 at 100, 1.975 at 400, 2.234 at 1,000, 3.321 at 4,000, 2.167 at 10,000, 1.428 at 40,000, 1.337 at 65,536. keep the best k while scanning: 1.000 at 1, 1.001 at 4, 1.006 at 10, 1.041 at 40, 1.116 at 100, 1.505 at 400, 2.252 at 1,000, 5.411 at 4,000, 4.057 at 10,000, 1.971 at 40,000, 1.159 at 65,536. knockout tournament: 1.000 at 1, 1.001 at 4, 1.002 at 10, 1.010 at 40, 1.024 at 100, 1.098 at 400, 1.248 at 1,000, 1.965 at 4,000, 1.351 at 10,000, 1.049 at 40,000, 1.012 at 65,536.1101001,00010,00011.52351015k, the elements the caller readscomparisons, in multiples of the floorsort all, read kbuild a heap, pop kselect k, sort thoseincremental quicksortkeep the best k while scanningknockout tournamentlabels at k = 1,00065,536 random distinct keys1 = the floor
Fig. 4 Each method’s comparisons for the k smallest of 65,536 divided by the floor at that k, on logarithmic axes. Knockout tournament: 1.000 at k = 1, 1.002 at 10, 1.024 at 100, 1.248 at 1,000, 1.965 at 4,000, 1.351 at 10,000, 1.012 at 65,536. Keep the best k while scanning: 1.000 at 1, 1.041 at 40, 1.116 at 100, 2.252 at 1,000, 5.411 at 4,000. Build a heap and pop: 1.885 at 1. Select then sort: 1.885 at 1. Sort everything: 14.735 at 1 and 1.012 at 65,536.

Three kinds of behaviour are visible.

The tournament is within 2.5% of the floor up to k=100k = 100 and within 25% at k=1,000k = 1{,}000. Its worst ratio, 1.97 at k=4,000k = 4{,}000, is at the point where the two floors cross and neither is tight. That peak is a statement about the floor, not about the tournament: nothing on this page says whether a method exists that does much better at k=4,000k = 4{,}000, and the floor’s own kink is the likeliest reason the ratio rises there and falls again by k=10,000k = 10{,}000.

Scanning with a bounded heap is perfect at k=1k = 1 and good for small kk, and then it collapses. Every key that belongs among the best kk seen so far costs a sift through the heap, and on random input a key is among the best kk seen so far with a probability that grows with kk. By k=1,000k = 1{,}000 it is at 2.25 times the floor and by k=4,000k = 4{,}000 at 5.4. It is the right method for a small kk known in advance and the wrong one for anything else.

The heap and the two selection methods pay a fixed tax of nearly twice the floor before the first key. Building a heap costs about 1.9n1.9n; selection by random pivots costs about 2n2n in expectation, 1.885 times the floor over these sixteen seeds. Both are linear, both are unavoidable in those methods, and both are twice what a tournament pays for the same first key.

What the input does to each

The plates so far use random input. The methods respond to order very differently.

The smallest 10 of 65,536 on three inputs: the tournament's count moves by 13 comparisons, the bounded heap's by a factor of 6.1Comparisons each method makes to hand back the smallest 10 of 65,536 distinct keys in order, on sorted, random and reversed input, on a logarithmic axis, with the floor of 65,526 dashed; selection and incremental quicksort are means over 16 seeds. sort all, read k: sorted 524,288, random 965,656, reversed 524,288. build a heap, pop k: sorted 65,805, random 123,795, reversed 131,308. select k, sort those: sorted 133,630, random 123,676, reversed 127,480. incremental quicksort: sorted 133,613, random 123,670, reversed 128,477. keep the best k while scanning: sorted 65,563, random 65,919, reversed 397,550. knockout tournament: sorted 65,657, random 65,670, reversed 65,657.sortedrandomreversed100,000200,000500,0001,000,000comparisons, logarithmicsort all, read kbuild a heap, pop kselect k, sort thoseincremental quicksortkeep the best k while scanningknockout tournament65,536 keys · the smallest 10dashed: the floor, 65,526
Fig. 5 Comparisons to hand back the smallest ten of 65,536 keys in order on sorted, random and reversed input, on a logarithmic axis, with the floor of 65,526 dashed; the two methods with random pivots are means over sixteen seeds. Sort everything: 524,288, 965,656, 524,288. Build a heap and pop: 65,805, 123,795, 131,308. Select then sort: 133,630, 123,676, 127,480. Incremental quicksort: 133,613, 123,670, 128,477. Keep the best ten while scanning: 65,563, 65,919, 397,550. Knockout tournament: 65,657, 65,670, 65,657.

The tournament does not care. Its count moves by thirteen comparisons across the three inputs, because it plays every first-round match whatever the keys are and then replays one path per key read.

The bounded heap cares more than anything else here. On reversed input every key the scan meets is smaller than everything it holds, so every key enters the heap and costs a sift: 397,550 comparisons, six times its count on sorted input, where no key after the first ten gets in. A method whose cost is a function of how often a new key beats the current best is at the mercy of the order the keys arrive in, and reversed input is its worst case in exactly the sense a worst case ten positions wide called a region.

The heap is cheap on sorted input because a sorted array already satisfies the heap property: building it compares each internal node with its two children, two comparisons, and moves nothing.

The two selection methods are the same on all three, within their sampling error. That is a requirement, not an observation. A pivot drawn at a uniformly random position has a uniformly random rank whatever the arrangement of the keys, so the distribution of a randomised selection’s count cannot depend on the input; a measurement that shows it depending is measuring its random numbers. Seeds that differ by a small constant give a bare shift-register generator first draws that agree with one another, and a selection’s cost is decided almost entirely by its first few pivots, so sixteen such seeds are not sixteen independent runs. Each seed here is mixed through a hash before use. The adversary who knows the seed is about what a stated seed exposes to an opponent; this is the quieter hazard of stating several, which is that they may not be different enough to be several. On average is not a number is why the plates report means over seeds at all.

What to hand a caller who reads a prefix

If kk is not known in advance, use a tournament or anything built like one. It meets the floor for the first key, pays about log2n\log_2 n per key after that, never costs more than a merge sort if the caller reads everything, and does not care what order the input is in. Lazy merge sorts and tournament-based merges in external sorting are the same idea.

If kk is known and small, a bounded heap is simplest and nearly optimal — at k=10k = 10 it is within 0.6% of the floor on random input — but its worst case is reversed input, and a caller whose data arrives in descending order pays six times as much.

Build-and-pop and select-then-sort are never the cheapest method on these plates, at any kk. They are the methods most libraries offer for this job, and each pays nearly twice the floor before handing back anything.

And a full sort is the right call only when the caller will read nearly everything — at k=40,000k = 40{,}000 of 65,536 it is still 1.6 times the floor, and the tournament is 1.05. The cost that is the size of the answer found the same shape in a different structure: a method whose cost follows the output wins when the output is small and loses its advantage only as the output approaches everything.

Still open: the floor at the crossing

Every method on the ratio plate peaks near k=4,000k = 4{,}000, and the likeliest reason is that the floor used there is weak: the larger of two arguments that each ignore what the other counts. The tournament’s cost suggests what a better floor would look like — the first key charged n1n - 1 for its losers, and each later key charged the logarithm of how many keys remain — and floors of roughly that shape, which charge the losers and the later keys’ information together without double-counting the early matches, are known from the analysis of selection.

Computing the best such floor at each kk for 65,536 keys, and measuring how far the tournament sits above it at the crossing, would say whether the peak at 1.97 is the floor’s weakness or the method’s, and whether any method can hand back four thousand keys from 65,536 in substantially fewer than 125,388 comparisons. That is a question about the count of the part that was read which neither floor on this page can answer.

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

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Comparison countHeapInformation floorLower boundMerge sortOutput-sensitivePriority queueQuickselectRandomised algorithmSeeded randomnessSelectionWorst case