Contents
Main Idea
Dividing
- Idea that each length-1 array is inherently sorted
Merging(Conquering)
- Idea of merging two pre-sorted sub-arrays by always comparing the head element of the two arrays, and move the smaller(or larger depending on need) element into the correct position of another array to temporarily hold the result of this single merging.
- [Important] Note the difference in this version of merge sort, that all merging and sorting are performed directly on the reference to the input array object, it does not utilise the recursive
return
mechanism in Python to create appendable arrays as the result. Therefore in this version of the algorithm, we always directly change the entries in the input array. - Note the need of two arrays of size
n
to hold the results: One is the input array by user, another one is what we manually create in themerge()
function to temporarily hold an intermediate result. - However, there are two ways of utilising this manually-created auxiliary array:
Primitive & Time-wasting
In this version, we use the auxiliary only for storing the pre-sorted arrays before they are merged, so that the partially sorted entries are kept throughout this merge()
call, and after each merge()
call, the input array object always holds the more but still partially sorted result.
Therefore, at the beginning of each merge()
call, we only need to copy every element of the two pre-sorted parts in the input array object into the auxiliary array. We merge the entries from the auxiliary array, and store the merged result in the corresponding position in the input array.
Improved
The primitive version is time wasting, due to the copying process in every merge()
call. Therefore, a time-saving improvement would naturally be that to eliminate the copying process.
Observation from the previous method tells us that the input array already contains the two pre-sorted sub-arrays. This leaves us wonder: Instead of storing the merged result back to the original array object, what if we can utilise the auxiliary array to hold the merged result from this round? We only need to remember swapping their roles again onto the next merge()
call, so that the input object can hold the next merged result.
Using this approach, although we still need two array objects, the role of neither objects become fixed - instead, they are constantly swapped between the two objects: During this merge()
call, the input object holds the partially sorted sub-arrays, which are to be merged in the auxiliary object; when moving onto the next merge()
call, the auxiliary array becomes the one who holds the partially-sorted parts that are to be merged, and the input object now holds the merge result from this round.
Complexity
O(N(log N)), mathematically-proved optimal performance! However…
Memory
Because of the need for an auxiliary array in the size proportional to that of input array (sorting is not performed “locally” on the input array, in other words, not in-place), twice the memory space is needed, which is not ideal.
Stability
Definition: A sorting algorithm is said to be stable if two items with equal keys in the same order in the sorted output as they appear in the input array. That is, the order of elements with identical keys is preserved.
Merge sort is stable, as unlike selection sort, which contains nonsequential swapping (swap any smallest element found in the later array). Merge sort does only sequential operations, so elements with the same key is kept in order.