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 because halving the array gives levels, and the merging at each level touches all 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.
For the sequence is 8, 4, 2, 1: three halvings because . For it is four halvings because . The number of halvings is the exponent that must be put on 2 to get , and that is what means.
Start with . 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 splits is , and it reaches 1 when . That is between 19 and 20, because and . 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.
Write the level as . At level there are pieces of size , so the cost of that level is . There are such levels, where , so the sum is added to itself times.
| merges at level | size of each merged piece | cost of each merge | cost of level |
|---|---|---|---|
| 1 | 1048576 | 1048576 | 1048576 |
| 2 | 524288 | 524288 | 1048576 |
| 4 | 262144 | 262144 | 1048576 |
| 524288 | 2 | 2 | 1048576 |
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 . It is the number of times can be halved before reaching 1, which is . If the depth were , the total would be . If the per-level cost fell, the total would be less than . The cancellation is the whole reason the total is rather than either of those.
So the total is the cost of one level multiplied by the number of levels: . For the exact case , there are 20 levels and the total is units. For a million items it is about units. If there were a million levels, the total would be 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 at the top and the method is worse than the loop it replaced.
More generally, if merging a piece of size costs more than , the total changes. If it costs , the top level alone costs , and the method is not at all. The 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 courseOther questions people ask about this
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.