RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Concurrent Merge Sort

O
package com.thealgorithms.sorts;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * A concurrent implementation of the Merge Sort algorithm.
 *
 * <p>This implementation utilizes a divide-and-conquer strategy, distributing
 * the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}.
 * To prevent the overhead of thread creation and context switching from outweighing
 * the benefits of concurrency, it falls back to a standard sequential merge sort
 * when the sub-array size drops below a predefined threshold, or when the maximum
 * concurrency depth is reached (preventing thread starvation deadlocks).
 *
 * <p><strong>Complexity:</strong>
 * <ul>
 *   <li>Time Complexity: $O(N \log N)$</li>
 *   <li>Space Complexity: $O(N)$</li>
 * </ul>
 */
public final class ConcurrentMergeSort {

    private ConcurrentMergeSort() {
    }

    /**
     * Fallback threshold where the algorithm switches to standard sequential
     * Merge Sort to prevent thread-creation overhead from ruining performance.
     */
    private static final int SEQUENTIAL_THRESHOLD = 8192;

    /**
     * Sorts the specified array of integers concurrently using Merge Sort.
     *
     * @param array the array to be sorted
     */
    public static void sort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }

        int availableProcessors = Runtime.getRuntime().availableProcessors();

        // Calculate a safe maximum depth to prevent creating more tasks than the pool can handle.
        // This effectively prevents thread starvation deadlock in fixed-size thread pools,
        // by forcing leaf tasks to run sequentially and eventually complete.
        int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1;

        ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        try {
            int[] tempArray = new int[array.length];
            concurrentMergeSort(array, tempArray, 0, array.length - 1, executor, maxDepth);
        } finally {
            // Ensure the executor is gracefully shut down
            executor.shutdown();
        }
    }

    /**
     * Recursively sorts the array utilizing the provided executor for concurrency.
     *
     * @param array    the array to sort
     * @param temp     a temporary array for merging
     * @param left     the starting index of the sub-array
     * @param right    the ending index of the sub-array
     * @param executor the {@link ThreadPoolExecutor} to handle concurrent tasks
     * @param depth    the remaining depth for allowing concurrent execution
     */
    private static void concurrentMergeSort(int[] array, int[] temp, int left, int right, ThreadPoolExecutor executor, int depth) {
        int length = right - left + 1;

        // Switch to sequential sort if the array is small or we have reached the maximum concurrent depth
        if (length < SEQUENTIAL_THRESHOLD || depth <= 0) {
            sequentialMergeSort(array, temp, left, right);
            return;
        }

        int mid = left + (right - left) / 2;

        // Submit the left half for concurrent execution
        CompletableFuture<Void> leftTask = CompletableFuture.runAsync(() -> concurrentMergeSort(array, temp, left, mid, executor, depth - 1), executor);

        // Process the right half in the current thread to optimize resource usage
        concurrentMergeSort(array, temp, mid + 1, right, executor, depth - 1);

        // Wait for the concurrently executed left half to complete
        leftTask.join();

        merge(array, temp, left, mid, right);
    }

    /**
     * Sorts the specified sub-array sequentially using standard Merge Sort.
     *
     * @param array the array to sort
     * @param temp  a temporary array for merging
     * @param left  the starting index of the sub-array
     * @param right the ending index of the sub-array
     */
    private static void sequentialMergeSort(int[] array, int[] temp, int left, int right) {
        if (left >= right) {
            return;
        }

        int mid = left + (right - left) / 2;
        sequentialMergeSort(array, temp, left, mid);
        sequentialMergeSort(array, temp, mid + 1, right);
        merge(array, temp, left, mid, right);
    }

    /**
     * Merges two sorted sub-arrays into a single sorted sub-array.
     *
     * @param array the original array containing the sub-arrays
     * @param temp  a temporary array used for merging
     * @param left  the starting index of the first sub-array
     * @param mid   the ending index of the first sub-array (and the partition point)
     * @param right the ending index of the second sub-array
     */
    private static void merge(int[] array, int[] temp, int left, int mid, int right) {
        System.arraycopy(array, left, temp, left, right - left + 1);

        int i = left;
        int j = mid + 1;
        int k = left;

        while (i <= mid && j <= right) {
            if (temp[i] <= temp[j]) {
                array[k++] = temp[i++];
            } else {
                array[k++] = temp[j++];
            }
        }

        while (i <= mid) {
            array[k++] = temp[i++];
        }

        // Remaining elements from the right half are already in their correct relative positions
    }
}