Definition · k-way mergeTo merge k sorted runs holding n items in total: place the first item of each run into a min-heap of size k, tagged with the run it came from. Repeatedly extract the minimum, append it to the output, and insert the next item of that run. Each item is inserted once and extracted once.
The heap holds k items, not n, so the extra memory is the fan-in rather than the data. Each extract costs a sift-down of depth ⌊log₂ k⌋, so the order of the comparison count is n log k — but the constant deserves to be measured rather than assumed, and the lab measures it.
Example · Eight runs of eight, both ways64 items. Through one heap: 302 comparisons, 64 pops, one pass over the data. Merging two runs at a time: 180 comparisons and seven passes, because each of the k − 1 rounds walks the whole accumulated prefix again. Both produce the same 64 items in the same order, and the lab checks that they agree rather than assuming it.
So in the comparison model the heap loses. n log₂ k = 192 at these sizes and the heap spends 302, which is between n log₂ k and 2n log₂ k = 384 — the extra factor being the comparison between the two children that a sift-down makes at every level before comparing with the parent. Heapsort paid the same factor in exactly the same place.
Why anyone uses the heap anyway
Definition · External merge sortWhen N records do not fit in the M of memory available, sort in two phases. Run formation: read M records at a time, sort them in memory, write them back as a sorted run, giving N/M runs. Merging: repeatedly merge runs F at a time, where the fan-in F is M/B for a block size of B, since each open run needs one block of buffer.
runs = N/M
fan-in F = M/B
passes = 1 + ⌈log_F(N/M)⌉ one to make the runs, then the merges
N = 10⁹, M = 10⁶, B = 4 F = 250 000, runs = 1000 2 passes
N = 10⁹, M = 10³, B = 4 F = 250, runs = 10⁶ 4 passes
N = 10⁹, M = 10³, B = 500 F = 2, runs = 10⁶ 21 passes
N = 10⁷, M = 10⁷ everything fits 1 pass, 0 merges
Read the third line against the second. The data is the same size, the algorithm is the same algorithm, and the pass count went from four to twenty-one because the fan-in — the base of the logarithm — fell from 250 to 2. A pass over a billion records is the expensive thing here, and the comparison count does not appear in the formula at all.
That is why the heap wins on disk and loses in the lab's comparison column: it makes k runs into one in a single pass, so the number of passes is the logarithm of the run count in base F rather than in base two. System Design's “Sequential versus Random I/O” prices exactly this count — it is the same number of sequential passes over the same data — and nothing on this page depends on that pricing.
The cutoff, and the shape of a real library sort
Merging singleton runs is wasteful: at the bottom of any merge sort the runs are tiny and insertion sort, which is Θ(m²) but with a small constant and no allocation, beats it there. A hybrid insertion-sorts blocks of size c and then merges them. On 64 shuffled keys the lab measures 594 comparisons at a cutoff of 1, 500 at 4, 448 at 11 and 527 at 20: the cheapest cutoff is strictly inside the range, which is what a crossover looks like.
Eleven is a measurement on one array, not a constant of nature. What generalises is the shape — form runs, insertion-sort below a cutoff, merge the runs — and that shape is Timsort's, which is the sort in the standard libraries of several languages. Timsort finds runs already present in the data rather than cutting fixed blocks, and merges them under a stack policy with galloping; neither of those is covered here.
Three figures on this page are of three different kinds, and the panel keeps them apart. 302 and 180 are comparison counts measured by running two merges on the same runs. 1 + ⌈log_F(N/M)⌉ is arithmetic: a count of passes over records, computed rather than measured, and refused outright when memory is too small to hold two blocks. And 11 is the low point of a curve sampled at twenty cutoffs on one array.