#
Merge Sort (122A)
Given A[1...n]: List<number>
, the idea of merge sort is:
function MergeSort(A[1...n]):
sorted_left = sort left half // Recursion Magic
sorted_right = sort right half // Recursion Magic
merge(sorted_left, sorted_right)
#
Dividing the Problem
We need to split the array in half. Let low
and high
denote the current starting and ending indices of the array (inclusive). Then we can find the middle index mid
by:
mid = floor((low + high) / 2)
Note that this could cause integer overflow when low
and high
are large. A better way would be:
mid = floor(low + ((high - low) / 2))
And the recursive calls are:
MergeSort(A[1...mid])
MergeSort(A[mid + 1...high])
#
Base Cases
This is where we actually do the comparison. We have 2 base cases:
- The array is empty or has 1 element, then we do nothing.
if A is empty: return
- The array has exactly 2 elements, then we directly compare.
- This case could be implicitly handled by merging two 1–element arrays, so no need to explicitly implement this case.
#
Merge 2 sorted Arrays
Now that the recursion has sorted the left and right array for us, we need to merge them together. We can use the 2 pointer approach here.
Let l
and r
be the traversing pointers in the left & right sorted arrays respectively.
It’s probably easier to consider merging with 2 separate arrays first.
function Merge2SortedArrays(L[1...n], R[1...m]) -> List<number>:
l = 0 // traverses L array
r = 0 // traverses R array
output = []
while neither pointer has reached the end:
if L[l] should come before R[r]: // call the predicate here
push L[l] into output
l++
else:
push R[r] into output
r++
remaining_elements = L if l not at the end, R if r not at the end
push all of remaining_elements into output
return output
Translate into expressions: (Assuming we are sorting in ascending order. Flip < to > if reversed.)
function Merge2SortedArrays(L[1...n], R[1...m]) -> List<number>:
l = 0
r = 0
output = []
while l != n and r != m:
if L[l] < R[r]:
push L[l] into output
l++
else:
push R[r] into output
r++
while l != n:
push L[l] into output
l++
while r != m:
push R[r] into output
r++
return output
The 2nd and 3rd while
loops are safe because at most 1 of them will run, and the loop that runs corresponds to the array that has all its remaining elements \geqslant the last element in output
.
We can also see that this is a linear scan, so the runtime is O(n).
#
Implementation Detail
In actual implementation, don’t actually slice the main array into L and R because passing arrays on the stack is expensive. Instead, pass in an extra parameter mid
to indicate where the left half ends, then merge with the same 2 pointer approach.
At the end, instead of returning output, replace all elements in the parameter array with output
. This requires the main array to be passed as a reference so it may be different depending on the language.
#
Code
//pseudocode
function MergeSort(A[1...n]):
if n > 1:
mid = floor(n / 2)
MergeSort(A[1...mid])
MergeSort(A[mid + 1...n])
Merge(A, mid)