Merge Sort
Written and reviewed by Gagan Bhardwaj · Senior IT Faculty · 15+ years’ experience
Merge Sort and Divide-and-Conquer
Merge sort divides a sequence into two smaller ranges, recursively sorts each range, and merges the two sorted results. The base case is a range of zero or one element. Unlike elementary quadratic sorts, its running time remains Θ(n log n) even for reverse-ordered input.
- Divide: choose the midpoint of
[left,right). - Conquer: sort
[left,middle)and[middle,right). - Combine: merge both ordered ranges into one ordered range.
Half-open ranges make lengths, empty ranges and adjacent partitions precise. Review recursion and compare with bubble sort.
Recursive Trace
For {38,27,43,3,9,82,10}, the conceptual recursion tree is:
[38 27 43 3 9 82 10]
├─ [38 27 43] → [27 38 43]
│ ├─ [38] → [38]
│ └─ [27 43] → [27 43]
└─ [3 9 82 10] → [3 9 10 82]
├─ [3 9] → [3 9]
└─ [82 10] → [10 82]
final merge → [3 9 10 27 38 43 82]Splitting does not perform the ordering work. Order is established while recursion returns and sorted child ranges are merged.
How the Merge Procedure Works
Maintain indices into the left range, right range and buffer. Copy the smaller front element, advance that range, then copy any remaining tail. Finally copy the merged buffer segment back.
while both ranges have elements
copy the smaller front value to buffer
copy remaining left values
copy remaining right values
copy buffer segment back to valuesWhen values are equal, taking from the left range first preserves their original relative order. That one comparison choice—<= rather than <—is central to stability.
Complete Merge Sort Program with One Reusable Buffer
#include <iostream>
#include <vector>
void merge(std::vector<int>& values, std::vector<int>& buffer,
std::size_t left, std::size_t middle, std::size_t right) {
std::size_t i = left, j = middle, k = left;
while (i < middle && j < right) {
if (values[i] <= values[j]) buffer[k++] = values[i++];
else buffer[k++] = values[j++];
}
while (i < middle) buffer[k++] = values[i++];
while (j < right) buffer[k++] = values[j++];
for (std::size_t p = left; p < right; ++p) values[p] = buffer[p];
}
void merge_sort_range(std::vector<int>& values,
std::vector<int>& buffer,
std::size_t left, std::size_t right) {
if (right - left < 2) return;
const std::size_t middle = left + (right - left) / 2;
merge_sort_range(values, buffer, left, middle);
merge_sort_range(values, buffer, middle, right);
merge(values, buffer, left, middle, right);
}
void merge_sort(std::vector<int>& values) {
std::vector<int> buffer(values.size());
merge_sort_range(values, buffer, 0, values.size());
}
int main() {
std::vector<int> values{38, 27, 43, 3, 9, 82, 10};
merge_sort(values);
std::cout << "Sorted:";
for (int value : values) std::cout << ' ' << value;
std::cout << '\n';
}The buffer is allocated once, not at every recursive call. Midpoint calculation avoids adding two potentially large indices, and the half-open representation avoids middle+1 boundary errors.
Correctness and Stability
Use induction on range length. A range shorter than two is sorted. Assume both recursive calls correctly sort their shorter ranges. The merge procedure repeatedly selects the least unconsumed front value, so its output is ordered; every input element is copied exactly once, so the result is a permutation of the input. Therefore the parent range is sorted.
Stability matters for records sorted in stages. If two records have the same key and the left one originally appeared first, selecting left on equality retains that order. Reversing the tie choice makes this implementation unstable.
Time and Space Complexity
| Property | Array merge sort |
|---|---|
| Best/average/worst time | Θ(n log n) |
| Merge work per level | Θ(n) |
| Recursion depth | Θ(log n) |
| Auxiliary buffer | Θ(n) |
| Stable | Yes, with left-first ties |
The recurrence is T(n)=2T(n/2)+Θ(n). An optional check can skip merging when values[middle-1] <= values[middle]; that helps ordered regions but does not alter the standard worst-case bound.
Use Cases and Algorithm Choice
Merge sort offers predictable performance and natural stability. Its sequential merge pattern is useful for external data organized in sorted runs, and linked structures can merge by relinking nodes. For an ordinary C++ vector, library algorithms are normally preferable.
| Requirement | Choice |
|---|---|
| Fast general vector sort | std::sort |
| Stable standard-library sort | std::stable_sort |
| Predictable educational Θ(n log n) | Merge sort |
| Tiny/nearly sorted input | Insertion sort can be simpler |
See C++ algorithms and sorting/searching for production interfaces.
Common Mistakes, Testing and Practice
| Mistake | Correction |
|---|---|
| No base case | Return for length below two |
| Mixed inclusive/exclusive indices | Use [left,right) consistently |
| Forgetting remaining tail | Copy both possible leftovers |
| Allocating arrays in every call | Reuse one buffer |
| Right-first equality | Choose left first for stability |
| Variable-length C arrays | Use standard C++ containers |
Test empty, one item, odd/even lengths, sorted, reverse, duplicates and negative values. Practice descending order, sorting records by key, counting comparisons, bottom-up iterative merge sort and the already-ordered merge skip.
Authoritative References
The divide-and-conquer definition, merge behavior and standard-library comparison were verified with these references.
Frequently Asked Questions
What is the time complexity of merge sort?
Is merge sort stable?
Is merge sort in-place?
What is the recursion base case?
When is merge sort preferred?
Merge Sort और Divide-and-Conquer
Merge sort sequence को दो ranges में बांटता, दोनों को recursively sort करता और फिर sorted results merge करता है। Zero या one-element range base case है। Reverse input पर भी time Θ(n log n) रहता है।
- Midpoint पर divide करें।
- Left और right range sort करें।
- दोनों ordered ranges combine करें।
Recursion और bubble sort से तुलना करें।
Recursive Trace
[38 27 43 3 9 82 10]
left [38 27 43] → [27 38 43]
right [3 9 82 10] → [3 9 10 82]
final merge → [3 9 10 27 38 43 82]Splitting order नहीं बनाता; returning recursion में sorted child ranges का merge actual ordering करता है।
Merge Procedure
Left, right और buffer के indices रखें। छोटे front element को buffer में copy करें, उस index को आगे बढ़ाएं, फिर बचा tail copy करें। Equal values पर left पहले लेने से stability रहती है।
Complete C++ Program
void merge_sort_range(std::vector<int>& a,
std::vector<int>& temp,
std::size_t left, std::size_t right) {
if (right - left < 2) return;
auto mid = left + (right - left) / 2;
merge_sort_range(a, temp, left, mid);
merge_sort_range(a, temp, mid, right);
merge(a, temp, left, mid, right);
}ऊपर English section का complete compile-ready program एक reusable buffer लेता है। Half-open ranges off-by-one errors कम करती हैं।
Correctness और Stability
Induction से: length 0/1 range sorted है। Recursive calls छोटी ranges sort करती हैं। Merge हर बार least unconsumed front चुनता है, हर element exactly once copy करता है; इसलिए ordered permutation मिलता है। Equal keys पर left पहले चुनने से original relative order बचता है।
Time और Space
| Property | Result |
|---|---|
| Best/Average/Worst | Θ(n log n) |
| Buffer | Θ(n) |
| Recursion depth | Θ(log n) |
| Stable | हाँ, left-first tie |
हर level में total merge work linear और levels logarithmic हैं।
Use Cases और Comparison
Stable order और predictable performance में merge sort अच्छा है। General vector के लिए std::sort, standard stable sorting के लिए std::stable_sort लें। External sorted runs और linked structures में merge pattern natural है। STL algorithms देखें।
Mistakes, Testing और Practice
- Base case जरूर रखें।
- Inclusive और exclusive bounds mix न करें।
- Left/right leftover tail copy करें।
- हर recursion में नया buffer न बनाएं।
- Duplicates से stability test करें।
Empty, odd/even length, sorted, reverse और negative inputs test करें। Bottom-up merge sort practice करें।
Authoritative संदर्भ
Algorithm definition और complexity authoritative sources से verify किए गए हैं।