What is an efficient way to implement the Mergesort algorithm in Java such that it meets the following criteria:
- Must be multi-threaded.
- Must retain the Mergesort time complexities.
- Must be in-place implementation, no copying arrays.
- Must use Java Collection classes, not an array.
- Must work on any Java Comparable.
My solution involves using an ArrayList and fork-join to create the in-place Mergesort. I've tested this on multiple inputs and they all work.
I'm curious if anyone can find a way I can improve my code. Either a more efficient solution or perhaps an edge case I didn't address.
import java.util.*;
import java.util.concurrent.*;
public class MergeSort<N extends Comparable<N>> extends RecursiveTask<List<N>> {
private List<N> elements;
public MergeSort(List<N> elements) {
this.elements = elements;
}
@Override
protected List<N> compute() {
if(this.elements.size() <= 1)
return this.elements;
else {
final int pivot = this.elements.size() / 2;
MergeSort<N> leftTask = new MergeSort<N>(this.elements.subList(0, pivot));
MergeSort<N> rightTask = new MergeSort<N>(this.elements.subList(pivot, this.elements.size()));
leftTask.fork();
rightTask.fork();
List<N> left = leftTask.join();
List<N> right = rightTask.join();
merge(left, right);
return this.elements;
}
}
private void merge(List<N> left, List<N> right) {
int leftIndex = 0;
int rightIndex = 0;
while(leftIndex < left.size()) {
if(rightIndex == 0) {
if( left.get(leftIndex).compareTo(right.get(rightIndex)) > 0 ) {
swap(left, leftIndex++, right, rightIndex++);
} else {
leftIndex++;
}
} else {
if( right.get(0).compareTo(right.get(rightIndex)) < 0 ) {
swap(left, leftIndex++, right, 0);
} else {
swap(left, leftIndex++, right, rightIndex++);
}
}
}
if(rightIndex < right.size() && rightIndex != 0)
merge(right.subList(0, rightIndex), right.subList(rightIndex, right.size()));
}
private void swap(List<N> left, int leftIndex, List<N> right, int rightIndex) {
//N leftElement = left.get(leftIndex);
left.set(leftIndex, right.set(rightIndex, left.get(leftIndex)));
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
List<Integer> result = forkJoinPool.invoke(new MergeSort<Integer>(new ArrayList<>(Arrays.asList(1,3,5,7,2,4,6))));
System.out.println("result: " + result);
}
}
Update: The code will fail on the edge case where the following input integers are used:
{5,9,8,7,6,1,2,3,4}
This occurs because the recursive call to merge only works successfully when the left and right lists are of equal length or differ by only one in length. This call however will result in the comparison of two lists that breaks that expectation. I have yet to find a solution which handles all edge cases properly.
merge()
function, because it doesn't work properly even on cases where the left and right sides are of equal length. The problem is that you swap elements from the left side to the right side, but other thanright[0]
, you don't revisit those elements and put them in the correct position. For example, once you swap some left element intoright[1]
, you will never move that element again. As far as I know, there is no simple in-place merge algorithm that can be done the way you are trying to do it. \$\endgroup\$