Sorting algorithms | ![]() ![]() ![]() | |
Mergesort |
The sorting algorithm Mergesort produces a sorted sequence by sorting its two halves and merging them. With a time complexity of O(n log(n)) Mergesort is optimal. | ||
Idea | |||||||||
Similar to Quicksort, the Mergesort algorithm is based on a divide and conquer strategy![]() | |||||||||
| |||||||||
The following procedure mergesort sorts a sequence a from index lo to index hi. | |||||||||
| |||||||||
First, index m in the middle between lo and hi is determined. Then the lower part of the sequence (from lo to m) and the upper part (from m+1 to hi) are sorted by recursive calls of mergesort. Then the two sorted halves are merged by procedure merge. Recursion ends when lo = hi, i.e. when a subsequence consists of only one element. | |||||||||
The main work of the Mergesort algorithm is performed by function merge. There are different possibilities to implement this function. | |||||||||
a) Straightforward variant of function merge | |||||||||
Function merge is usually implemented in the following way: The two halves are first copied into an auxiliary array b. Then the two halves are scanned by pointers i and j and the respective next-greatest element at each time is copied back to array a (Figure 2). | |||||||||
| |||||||||
At the end a situation occurs where one index has reached the end of its half, while the other has not. Then, in principle, the rest of the elements of the corresponding half have to be copied back. Actually, this is not necessary for the upper half, since (copies of) the remaining elements are already at their proper places. | |||||||||
Simulation (straightforward variant of function merge) | |||||||||
The following program is an implementation of this approach. | |||||||||
| |||||||||
In Java the short form k++ is equivalent to k=k+1; the statement a[k++]=b[i++] ist equivalent to the sequence a[k]=b[i]; k=k+1; i=i+1. | |||||||||
b) Efficient variant of function merge | |||||||||
Actually, it is not necessary to copy the upper half of array a to the auxiliary array b. It can remain where it is in array a (Fig. 3). In this way, only half as much auxiliary memory space is required, and also only half as much time to copy array elements to b. Furthermore, if all elements of the lower half have been copied back to a, the remaining elements of the upper half need not be moved anymore since they are already at their proper places [So]. | |||||||||
| |||||||||
Observe that when index k reaches index j all elements of array b have been copied back. | |||||||||
Simulation (efficient variant of function merge) | |||||||||
The implementation of this approach follows. | |||||||||
| |||||||||
c) Bitonic variant of function merge | |||||||||
In this approach, the lower half of a is copied to auxiliary array b in its normal order, but the upper half is copied to b in opposite order [Se]. The result is a sequence that first monotonically increases and then monotonically decreases – a so-called bitonic sequence. Now this sequence is scanned by pointers i and j from both ends. Again, the respective next-greatest element at each time is copied back to array a. Copying is completed when i and j cross, i.e. when i > j (Figure 4). Observe that it is not necessary that i and j stay in "their" halves. | |||||||||
| |||||||||
Simulation (bitonic variant of function merge) | |||||||||
The implementation of the bitonic approach follows. | |||||||||
| |||||||||
This variant of function merge requires in each case exactly the same number of steps – no matter if the input sequence is random, sorted or sorted in opposite direction. | |||||||||
In contrast to the other variants, it is not stable, i.e. it is possible that the original order of equal elements is changed. | |||||||||
Program | |||
The following class MergeSorter encapsulates the functions mergesort and merge. | |||
The method sort passes the array to be sorted to array a, allocates the auxiliary array b and calls mergesort. | |||
The command for sorting an array c with Mergesort is | |||
MergeSorter.sort(c); | |||
The size of the auxiliary array b has to be chosen appropriately according to the implementation of function merge, namely n for variants a and c and (n+1)/2 for variant b. | |||
| |||
Analysis | ||
The straightforward version of function merge requires at most 2n steps (n steps for copying the sequence to the intermediate array b, and at most n steps for copying it back to array a). The time complexity of mergesort is therefore | ||
T(n) ![]() | ||
T(1) = 0 | ||
The solution of this recursion yields | ||
T(n) ![]() ![]() | ||
Thus, the Mergesort algorithm is optimal, since the lower bound for the sorting problem of ![]() | ||
In the more efficient variant, function merge requires at most 1.5n steps (n/2 steps for copying the lower half of the sequence to the intermediate array b, n/2 steps for copying it back to array a, and at most n/2 steps for processing the upper half). This yields a running time of mergesort of at most 1.5n log(n) steps. | ||
Conclusions | ||
Algorithm Mergesort has a time complexity of ![]() ![]() | ||
There are different possibilities to implement function merge. The most efficient of these is variant b. It requires only half as much additional space, it is faster than the other variants, and it is stable. | ||
References | ||
[Kn] | D.E. Knuth: The Art of Computer Programming, Vol. 3: Sorting and Searching. Addison-Wesley (1973) | |
[Se] | R. Sedgewick: Algorithms. Addison-Wesley (1988) | |
[So] | P. Sommerlad: (Private communication). Peter Sommerlad, Hochschule für Technik Rapperswil, Switzerland (2004) | |