Data Structures, Algorithms, & Applications in Java
Chapter 19, Exercise 17
We begin by making a pass over the input elements and saving the start
of each sorted segment in a queue. In a merge pass, we merge pairs of segments.
The start points of each of the resulting segments are saved on a
queue for use in
the next merge pass.
Merge passes cease when only one segment remains.
The code is structured in the same way as that for the merge sort described
in the text. We have three methods--naturalMergeSort
which is the counterpart of MergeSort.mergeSort,
naturalMergePass which is the counterpart of
MergeSort.mergePass, and merge
which is identical to MergeSort.merge.
The code is given below.
public class NaturalMergeSort
{
// define a queue used for the start positions of sorted segments
static ArrayQueue queue = new ArrayQueue(10);
static int numberOfSegments;
/** sort the elements a[0 : a.length - 1] using
* the natural merge sort method */
public static void naturalMergeSort(Comparable [] a)
{
if (a.length <= 1)
// already sorted
return;
// define an auxiliary array to faciltate merging
Comparable [] b = new Comparable [a.length];
// find the natural segments and save their start positions in a queue
queue.put(new Integer(0)); // first segment starts at 0
numberOfSegments = 1;
for (int i = 1; i < a.length; i++)
if (a[i - 1].compareTo(a[i]) > 0)
{// a new segment starts at i
numberOfSegments++;
queue.put(new Integer(i));
}
while (numberOfSegments > 1)
{// numberOfSegments is updated by naturalMergePass
naturalMergePass(a, b); // merge from a to b
naturalMergePass(b, a); // merge from b to a
}
}
/** merge pairs of adjacent segments from x to y*/
public static void naturalMergePass(Comparable [] x, Comparable [] y)
{
for (int i = 1; i < numberOfSegments; i += 2)
{// merge two segments
// find the start and end of the two segments
Object a = queue.remove();
int startA = ((Integer) a).intValue();
int startB = ((Integer) queue.remove()).intValue();
Object z = queue.getFrontElement();
int endB = (z == null || ((Integer) z).intValue() == 0) ?
x.length - 1 : ((Integer) z).intValue() - 1;
// merge them
merge(x, y, startA, startB - 1, endB);
// put segment start on queue
queue.put(a);
}
// one segment may remain
if (numberOfSegments % 2 == 1)
{// copy last segment to y
Object a = queue.remove();
int startA = ((Integer) a).intValue();
for (int i = startA; i < x.length; i++)
y[i] = x[i];
queue.put(a);
}
// update number of segments
if (numberOfSegments % 2 == 1)
numberOfSegments = numberOfSegments / 2 + 1;
else
numberOfSegments /= 2;
}
/** merge c[l:m] and c[m + 1:r] to d[l:r] */
public static void merge(Comparable [] c, Comparable [] d,
int l, int m, int r)
{
int i = l, // cursor for first segment
j = m + 1, // cursor for second
k = l; // cursor for result
// merge until i or j exits its segment
while ((i <= m) && (j <= r))
if (c[i].compareTo(c[j]) <= 0)
d[k++] = c[i++];
else
d[k++] = c[j++];
// take care of left overs
if (i > m)
for (int q = j; q <= r; q++)
d[k++] = c[q];
else
for (int q = i; q <= m; q++)
d[k++] = c[q];
}
}
Several alternative implementations are possible. For example,
we can save the start points of the initial segments in an array-based
linear list.
The start points for the first merge pass are
given by get(i), for i =
0, 1, 2, ...; for the second merge pass, the start points are
given by get(i), for i =
0, 2, 4, ...; for the third merge pass, the start points are
given by get(i), for i =
0, 4, 8, ...; and so on. This saves the overhead of
removing elements from a queue and adding elements to a queue.
Alternatively, we could determine the segment boundaries dynamically
and avoid the space overhead of a queue/linear list.
The firt segment starts at position 0.
We determine the start point of the second segment by scanning the
elements from left to right until the element value decreases.
Now, we proceed to merge the first two segments. The end of the second segment
is determined as the merge of the first two segments progresses.
Once the cursor for the second segment encounters a decrease in element value,
the second segment has finished. At this time, the start of the third
segment becomes known; we scan to the right to find the start of the fourth
segment; and then proceed to merge the third and fourth segments.
During this merge, we watch for a decrease is value in the fourth segment.
This decrease signals the start of the fifth segment.
And so on.