Where can I get a decent high-level description of the algorithm used in __merge_without_buffer() in the C++ STL? I’m trying to reimplement this code in the D programming language, with some enhancements. I can’t seem to grok what it’s doing at the algorithmic level from just reading the STL source code because there are too many low-level details obscuring it. Also, I don’t want to just blindly translate the code because then, if it didn’t work I wouldn’t know why, and I wouldn’t be able to add my enhancements.
Where can I get a decent high-level description of the algorithm used in __merge_without_buffer()
Share
__merge_without_buffer()is performing an in-place merge, as the merge step of an in-place merge sort. It takes as input two ranges of data[first, middle)and[middle, last)which are assumed to already be sorted. Thelen1andlen2parameters are equal to the lengths of the two input ranges, namely(middle - first)and(last - middle)respectively.First, it picks a pivot element. Then, it rearranges the data into the order
A1 B1 A2 B2, whereA1is the set of elements in[first, middle)that are less than the pivot,A2is the set of elements in[first, middle)greater than or equal to the pivot,B1is the set of elements in[middle, last)less than the pivot, andB2is the set of elements in[middle, last)greater than or equal to the pivot. Note that the data is originally in the orderA1 A2 B1 B2, so all we need to do is to turnA2 B1intoB1 A2. This is with a call tostd::rotate(), which does just that.Now we’ve separated out the elements which are less than the pivot (
A1andB1) from those that are greater than or equal to the pivot (A2andB2), so now we can recursively merge the two subrangesA1 A2andB1 B2.How do we choose a pivot? In the implementation I’m looking at, it chooses the median element from the larger subrange (i.e. if
[first, middle)has more elements than[middle, last), it chooses the median of[first, middle); otherwise, it chooses the median of[middle, last)). Since the subranges are already sorted, choosing the median is trivial. This pivot choice ensures that when recursively merging the two subranges, each subproblem is no more than 3/4 the size of the current problem, because in the worst case, at least 1/4 of the elements are larger than or smaller than the pivot.What is the running time of this? The
std::rotate()call takes O(N) time, and we make two recursive calls to ourselves. This equates to a running time of O(N log N). However, note that this is only one step in mergesort: remember that in mergesort you first recursively sort both halves and then merge. Thus, the recurrence relation for the running time of mergesort is now:T(N) = 2T(N/2) + O(N log N)Plug this into the Master theorem, and you get that mergesort now runs in O(N log2 N) time!
As an interesting final point, consider the following three qualities of a comparison-based sorting algorithm:
You can usually only get 2 of these at once – quicksort gets you (1) and (3), mergesort gets you (2) and (3), and an in-place mergesort gets you (1) and (2). Non-comparison-based sorts such as count sort can achieve all 3, but those sorts can only sort certain data types. It’s possible there exists a comparison-based sort which achieves all 3, but if there is, I’m not aware of its existence, and it’s almost certainly much more complicated.