Parallel Merge Sort (Fork/Join)

Problem

Sort the array with merge sort, and let the two halves be sorted in parallel when the part is bigger than 8 elements (ForkJoinPool in Java, threads in Python; JavaScript has one thread, so its version sorts the halves one after the other). Merge the two sorted halves into one sorted array. Print the sorted array.

Constraints

  • 1 <= n <= 10^5

Examples

Input

6
5 2 9 1 5 6

Output

1 2 5 5 6 9

Solutions

Best approachTime O(n log n) workSpace O(n)

Fork/join splits a big job into smaller jobs for several workers and then joins the results. It pays off only for big inputs and only when the parts are independent. Because the halves share no data, we need no locks. In real Java code, Arrays.parallelSort does this for you.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * Problem : Sorted array.
 * Input   : n, then n integers.
 * Output  : The sorted array.
 * Approach: Fork, sort, join, merge.
 * Time    : O(n log n) work
 * Space   : O(n)
 * Java    : 8 or newer.
 */
import java.util.Scanner;
import java.util.Arrays;
import java.util.stream.Collectors;

public class Main {
        // Step 1: A RecursiveTask returns a result. Each task sorts one part of the array.
        static class SortTask extends java.util.concurrent.RecursiveTask<int[]> {
            private final int[] part;

            SortTask(int[] part) {
                this.part = part;
            }

            @Override
            protected int[] compute() {
                // Step 2: Small parts are sorted directly. Forking tiny parts costs more than it saves.
                if (part.length <= 8) {
                    int[] copy = part.clone();
                    java.util.Arrays.sort(copy);
                    return copy;
                }
                // Step 3: Fork the left half to another worker, sort the right half here, then wait for the left half.
                int middle = part.length / 2;
                SortTask left = new SortTask(java.util.Arrays.copyOfRange(part, 0, middle));
                SortTask right = new SortTask(java.util.Arrays.copyOfRange(part, middle, part.length));
                left.fork();
                int[] sortedRight = right.compute();
                int[] sortedLeft = left.join();
                return merge(sortedLeft, sortedRight);
            }
        }

        // Step 4: Merge two sorted arrays: always take the smaller front element.
        static int[] merge(int[] x, int[] y) {
            int[] result = new int[x.length + y.length];
            int i = 0;
            int j = 0;
            int k = 0;
            while (i < x.length && j < y.length) {
                result[k++] = x[i] <= y[j] ? x[i++] : y[j++];
            }
            while (i < x.length) {
                result[k++] = x[i++];
            }
            while (j < y.length) {
                result[k++] = y[j++];
            }
            return result;
        }

    // This method solves the task. It receives the input and returns the answer.
    static int[] solve(int[] a) {
        return java.util.concurrent.ForkJoinPool.commonPool().invoke(new SortTask(a));
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int size = scanner.nextInt();
        int[] a = new int[size];
        for (int i = 0; i < size; i++) {
            a[i] = scanner.nextInt();
        }
        System.out.println(Arrays.stream(solve(a)).mapToObj(String::valueOf).collect(Collectors.joining(" ")));
    }
}
Hints (1)
  1. Why do we stop forking for small parts?