4.2 Mergesort
A natural approach to problem solving is divide and conquer. To use divide and conquer when sorting, we might consider breaking the list to be sorted into pieces, process the pieces, and then put them back together somehow. A simple way to do this would be to split the list in half, sort the halves, and then merge the sorted halves together. This is the idea behind Mergesort.
Mergesort is one of the simplest sorting algorithms conceptually, and has good performance both in the asymptotic sense and in empirical running time. Unfortunately, even though it is based on a simple concept, it is relatively difficult to implement in practice. Here is a pseudocode sketch of Mergesort:
function mergeSort(arr):
if A.size <= 1:
return
= half of arr
L1 = other half of arr
L2
mergeSort(L1)
mergeSort(L2) merge(L1, L2)
Here is a visualisation that illustrates how Mergesort works.
Merge
The hardest step to understand about Mergesort is the merge function. The merge function starts by examining the first record of each sublist and picks the smaller value as the smallest record overall. This smaller value is removed from its sublist and placed into the output list. Merging continues in this way, comparing the front records of the sublists and continually appending the smaller to the output list until no more input records remain.
Here is pseudocode for merging two lists:
function merge(L1, L2):
= new empty list
answer while L1 and L2 are nonempty:
= first element of L1
x = first element of L2
y if x <= y:
append x to answer
remove x from L1
else:
append y to answer
remove y from L2
// Now only one of L1 and L2 is nonempty, so append its remaining elements
append all elements of L1 or L2 to answer
return answer
Here is a visualisation for the merge operation.
Practice exercise: Merging
Practice exercise: Merging
Here is a Mergesort warmup exercise to practice merging.
4.2.1
Practice exercise: Merge sort
4.2.1 Practice exercise: Merge sort
Now here is a full proficiency exercise to put it all together.
4.2.2 Complexity analysis
Consider repeatedly splitting an array of elements, where is a power of 2. The recursion will go down levels until there are only -element arrays left. Each recursion level is shown as one row in this table:
Level | How many parts | Size per part | Splitting each part |
---|---|---|---|
1 | The full array | elements | units of work |
2 | halves | elements | units of work |
3 | quarters | elements | units of work |
… | … | … | … |
subarrays | elements | units of work |
After a sub-array has been split, the next level is called recursively which returns the sorted splits. Then we have to merge them before returning the sorted sub-array to the previous level, which is shown in the following table:
Level | How many parts | Size per part | Merging pairs |
---|---|---|---|
singleton arrays | element each | units of work | |
pairs | elements each | units of work | |
… | … | … | … |
2 | subarrays | elements each | units of work |
1 | subarrays | elements each | units of work |
In summary, each level spends time, and there are levels. So the total running time of Mergesort is . Note that this cost is unaffected by the relative order of the values being sorted, thus this analysis holds for the best, average, and worst cases.
This visualisation provides a running time analysis for
Mergesort.