Merge sort
↑At a glance
Section titled “At a glance”Merge sort divides an array into smaller pieces, sorts each piece and merges the results. Its running time stays predictable as the input grows.
| Property | What to remember |
|---|---|
| Typical time | O(n log n) |
| Extra memory | O(n) |
| Stable? | Yes, with the implementation shown |
| Good choice when | You need predictable performance on large inputs |
Watch it work
Section titled “Watch it work”The bars show the values and their current positions. Use Next to inspect one action, or Play to follow the whole sequence. You can enter your own numbers and compare what happens with a nearly sorted or reversed list.
Explain the current action in your own words.
Why is this step necessary?
Predict what will happen next.
Purple: compared or moved | Green: completed portion | Orange: active work area
Splitting and merging at a glance
Section titled “Splitting and merging at a glance”Notice that the recursive split creates single-element ranges, while the merge phase produces progressively larger sorted runs.
Approach explained step by step
Section titled “Approach explained step by step”- Find the middle. Divide the current range into a left half and a right half.
- Sort each half. Apply the same procedure recursively, stopping at ranges of one element.
- Create a temporary buffer. During the merge, the two halves are already sorted, so their first unconsumed elements are enough to decide the next output.
- Compare the heads. Take the smaller of the two available values and append it to the buffer. When values are equal, taking from the left keeps the algorithm stable.
- Copy the remainder. Once one half runs out, append every remaining element of the other half.
- Write the merged sequence back. The entire range is now sorted. Repeat as recursive calls return.
Trace a small input by hand
Section titled “Trace a small input by hand”For [7, 3, 5], divide into [7] and [3, 5], then split the latter into [3] and [5]. Merge those into [3, 5]. Finally compare the first items of [7] and [3, 5], taking 3, then 5, then 7. The result is [3, 5, 7].
Implementations in C# and C++
Section titled “Implementations in C# and C++”static void MergeSort(int[] a){ int[] buffer = new int[a.Length]; Sort(0, a.Length);
void Sort(int start, int end) { if (end - start <= 1) return; int middle = start + (end - start) / 2; Sort(start, middle); Sort(middle, end);
int left = start, right = middle; for (int k = start; k < end; k++) { if (right == end || (left < middle && a[left] <= a[right])) buffer[k] = a[left++]; else buffer[k] = a[right++]; } Array.Copy(buffer, start, a, start, end - start); }}#include <vector>#include <algorithm>void mergeSort(std::vector<int>& values) { if (values.size() < 2) return; std::vector<int> buffer(values.size()); auto sort = [&](auto&& self, int lo, int hi) -> void { if (hi - lo < 2) return; int mid = lo + (hi - lo) / 2; self(self, lo, mid); self(self, mid, hi); std::merge(values.begin()+lo, values.begin()+mid, values.begin()+mid, values.begin()+hi, buffer.begin()+lo); std::copy(buffer.begin()+lo, buffer.begin()+hi, values.begin()+lo); }; sort(sort, 0, static_cast<int>(values.size()));}Complexity and trade-offs
Section titled “Complexity and trade-offs”| Measure | Cost | Reason |
|---|---|---|
| Best case | O(n log n) |
Splitting and merging still happen even when already sorted. |
| Average case | O(n log n) |
There are logarithmically many levels of merging. |
| Worst case | O(n log n) |
Each level visits all n items. |
| Auxiliary space | O(n) |
The implementation uses a temporary buffer. |
| Stability | Stable | Taking a value from the left half on a tie preserves order. |
Merge sort gives a strong worst-case bound but needs additional storage for the merge buffer. Unlike an in-place algorithm, it must allocate or reuse scratch space. It is often a good choice when stable ordering matters.
Common mistakes
Section titled “Common mistakes”- Merging halves before they have been sorted.
- Mishandling inclusive versus exclusive range boundaries. This example uses
[start, end). - Forgetting to copy remaining elements when one half runs out.
- Forgetting that the auxiliary buffer affects memory complexity.
Why the merge step works
Section titled “Why the merge step works”The two input halves are already sorted by their recursive calls. The next output item must therefore be the smaller of the two front items. Once an item has been copied to the output, it never needs to move again during this merge. When one half runs out, copy the rest of the other half.
Memory trade-offs
Section titled “Memory trade-offs”A typical array-based merge sort requires an auxiliary buffer proportional to the input size. Reusing a single buffer across recursive calls is generally preferable to allocating fresh arrays at every merge. The recursion itself adds a logarithmic call-stack depth, but the O(n) buffer dominates the auxiliary-space bound.
Stability matters
Section titled “Stability matters”When both front values are equal, take the item from the left half first. That maintains the relative ordering of equal keys. Stable sorting is useful when records were previously ordered by another field.
