Basis Lab

Answers · Making it fast enough

Why is merge sort O(n log n) when it keeps splitting the array in half?

In shortSplitting in half gives about twenty levels for a million items, and merging at every level still touches all million items, so the total is twenty million rather than a million times a million.

Merge sort is O(nlog⁡n)O(n\log n) because halving the array gives log⁡2n\log_2 n levels, and the merging at each level touches all nn items once. For a million items that is about twenty levels of one million units each, so about twenty million units, not a million levels of a million items.

n2k=1⟹2k=n⟹k=log⁡2n\frac{n}{2^k}=1 \quad\Longrightarrow\quad 2^k=n \quad\Longrightarrow\quad k=\log_2 n

For n=8n=8 the sequence is 8, 4, 2, 1: three halvings because 23=82^3=8. For n=16n=16 it is four halvings because 24=162^4=16. The number of halvings is the exponent that must be put on 2 to get nn, and that is what log⁡2n\log_2 n means.

Start with n=1,000,000n=1{,}000{,}000. After one split there are 2 pieces of 500,000. After two splits there are 4 pieces of 250,000. After three splits there are 8 pieces of 125,000. The piece size after jj splits is 1,000,000/2j1{,}000{,}000/2^j, and it reaches 1 when 2j=1,000,0002^j=1{,}000{,}000. That is between 19 and 20, because 219=524,2882^{19}=524{,}288 and 220=1,048,5762^{20}=1{,}048{,}576. So the recursion has about 20 levels, not a million.

The work that matters is merging, not the act of splitting. At the top level one merge combines two halves of 500,000 and costs about 1,000,000 units. At the next level two merges of 250,000 each cost 500,000 each, which is 1,000,000 in total. At the next four merges of 125,000 cost 125,000 each, which is again 1,000,000. At the bottom there are 500,000 merges of pairs of single items; each costs 2 units, so the level still costs 1,000,000. The pieces shrink as fast as they multiply, so the level cost stays put.

2j×n2j=n2^j \times \frac{n}{2^j}=n

Write the level as jj. At level jj there are 2j2^j pieces of size n/2jn/2^j, so the cost of that level is 2j×n/2j=n2^j \times n/2^j = n. There are kk such levels, where k=log⁡2nk=\log_2 n, so the sum is nn added to itself kk times.

FIG 1
merges at levelsize of each merged piececost of each mergecost of level
1104857610485761048576
25242885242881048576
42621442621441048576
524288221048576

The natural guess is that because each half is half the size, the work should fall away, or that the number of levels is the number of items. Neither happens. The number of pieces doubles exactly as the size of each piece halves, so the two effects cancel at every level. The depth is not nn. It is the number of times nn can be halved before reaching 1, which is log⁡2n\log_2 n. If the depth were nn, the total would be n2n^2. If the per-level cost fell, the total would be less than nlog⁡nn\log n. The cancellation is the whole reason the total is nlog⁡nn\log n rather than either of those.

So the total is the cost of one level multiplied by the number of levels: n×log⁡2nn \times \log_2 n. For the exact case n=1,048,576=220n=1{,}048{,}576=2^{20}, there are 20 levels and the total is 20×1,048,576=20,971,52020 \times 1{,}048{,}576 = 20{,}971{,}520 units. For a million items it is about 20×1,000,000=20,000,00020 \times 1{,}000{,}000 = 20{,}000{,}000 units. If there were a million levels, the total would be 1,000,000×1,000,000=1,000,000,000,0001{,}000{,}000 \times 1{,}000{,}000 = 1{,}000{,}000{,}000{,}000 units.

This holds only when the two halves can be solved independently and combined cheaply. Sorting passes: two sorted halves merge in one pass by repeatedly taking the smaller front. Finding the largest value passes, but gains only depth, because the combining is one comparison per item either way. Finding the closest pair of points needs care: the closest pair may straddle the dividing line, and the merge must inspect a narrow strip near that line. If combining two halves needs every item of one compared with every item of the other, the merge costs n2n^2 at the top and the method is worse than the loop it replaced.

More generally, if merging a piece of size mm costs more than mm, the total changes. If it costs m2m^2, the top level alone costs n2n^2, and the method is not O(nlog⁡n)O(n\log n) at all. The nlog⁡nn\log n bound is a statement about one linear pass over the two sorted halves at each level, not about halving on its own.

Worth keeping

  • Halving an array of n items gives log base 2 of n levels, not n levels.
  • At every level the number of pieces doubles while each piece halves, so the total merging work at that level stays n.
  • If two sorted halves cannot be merged in one pass, the n log n total no longer follows.

Where this goes next

This question is one step of Answers Built From Smaller Answers, a course that works through its subject from the beginning. The page that teaches this part of it is Splitting the Work in Half, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. What's the difference between memoized recursion and filling a DP table bottom-up?Memoised recursion keeps the calls and only solves states it reaches; a bottom-up table removes the calls, fills every entry in a dependency-safe order, and is usually faster when most entries are needed.
  2. How does binary search find something in a sorted list so fast?Binary search is fast because each comparison halves the remaining sorted range, so a thousand items need about ten probes and a million need about twenty, rather than one probe per item.
  3. When is picking the locally best option actually guaranteed to give the best overall answer?Only when a proof shows that some best overall answer can contain the locally best first choice, and the same holds for every later step; otherwise one counterexample can break it.
  4. What is memoization and why does caching recursive results make it so much faster?Memoisation stores each answer once and reuses it, so a recursion that repeats the same subproblems stops redoing work and becomes a table lookup instead.
  5. Why doesn't breadth-first search give the cheapest path once the links have different costs?Breadth-first search counts links rather than costs, so it can return a route with fewer links that is more expensive than a longer route.
  6. Why is O(2n) treated the same as O(n)?Because it describes how cost grows rather than exact cost, doubling every cost does not change the growth class.

Read alongside