Skip to content

Commit 22e62e3

Browse files
feat: add ConcurrentMergeSort implementation
1 parent af9aad3 commit 22e62e3

2 files changed

Lines changed: 244 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package com.thealgorithms.sorts;
2+
3+
import java.util.concurrent.ExecutionException;
4+
import java.util.concurrent.Future;
5+
import java.util.concurrent.LinkedBlockingQueue;
6+
import java.util.concurrent.ThreadPoolExecutor;
7+
import java.util.concurrent.TimeUnit;
8+
9+
/**
10+
* A concurrent implementation of the Merge Sort algorithm.
11+
*
12+
* <p>This implementation utilizes a divide-and-conquer strategy, distributing
13+
* the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}.
14+
* To prevent the overhead of thread creation and context switching from outweighing
15+
* the benefits of concurrency, it falls back to a standard sequential merge sort
16+
* when the sub-array size drops below a predefined threshold, or when the maximum
17+
* concurrency depth is reached (preventing thread starvation deadlocks).
18+
*
19+
* <p><strong>Complexity:</strong>
20+
* <ul>
21+
* <li>Time Complexity: $O(N \log N)$</li>
22+
* <li>Space Complexity: $O(N)$</li>
23+
* </ul>
24+
*/
25+
public class ConcurrentMergeSort {
26+
27+
/**
28+
* Fallback threshold where the algorithm switches to standard sequential
29+
* Merge Sort to prevent thread-creation overhead from ruining performance.
30+
*/
31+
private static final int SEQUENTIAL_THRESHOLD = 8192;
32+
33+
/**
34+
* Sorts the specified array of integers concurrently using Merge Sort.
35+
*
36+
* @param array the array to be sorted
37+
*/
38+
public static void sort(int[] array) {
39+
if (array == null || array.length <= 1) {
40+
return;
41+
}
42+
43+
int availableProcessors = Runtime.getRuntime().availableProcessors();
44+
45+
// Calculate a safe maximum depth to prevent creating more tasks than the pool can handle.
46+
// This effectively prevents thread starvation deadlock in fixed-size thread pools,
47+
// by forcing leaf tasks to run sequentially and eventually complete.
48+
int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1;
49+
50+
ThreadPoolExecutor executor = new ThreadPoolExecutor(
51+
availableProcessors,
52+
availableProcessors,
53+
0L,
54+
TimeUnit.MILLISECONDS,
55+
new LinkedBlockingQueue<Runnable>()
56+
);
57+
58+
try {
59+
int[] tempArray = new int[array.length];
60+
concurrentMergeSort(array, tempArray, 0, array.length - 1, executor, maxDepth);
61+
} finally {
62+
// Ensure the executor is gracefully shut down
63+
executor.shutdown();
64+
}
65+
}
66+
67+
/**
68+
* Recursively sorts the array utilizing the provided executor for concurrency.
69+
*
70+
* @param array the array to sort
71+
* @param temp a temporary array for merging
72+
* @param left the starting index of the sub-array
73+
* @param right the ending index of the sub-array
74+
* @param executor the {@link ThreadPoolExecutor} to handle concurrent tasks
75+
* @param depth the remaining depth for allowing concurrent execution
76+
*/
77+
private static void concurrentMergeSort(int[] array, int[] temp, int left, int right, ThreadPoolExecutor executor, int depth) {
78+
if (left >= right) {
79+
return;
80+
}
81+
82+
int length = right - left + 1;
83+
84+
// Switch to sequential sort if the array is small or we have reached the maximum concurrent depth
85+
if (length < SEQUENTIAL_THRESHOLD || depth <= 0) {
86+
sequentialMergeSort(array, temp, left, right);
87+
return;
88+
}
89+
90+
int mid = left + (right - left) / 2;
91+
92+
// Submit the left half for concurrent execution
93+
Future<?> leftTask = executor.submit(() -> concurrentMergeSort(array, temp, left, mid, executor, depth - 1));
94+
95+
// Process the right half in the current thread to optimize resource usage
96+
concurrentMergeSort(array, temp, mid + 1, right, executor, depth - 1);
97+
98+
try {
99+
// Wait for the concurrently executed left half to complete
100+
leftTask.get();
101+
} catch (InterruptedException | ExecutionException e) {
102+
Thread.currentThread().interrupt();
103+
throw new RuntimeException("Concurrent execution interrupted or failed", e);
104+
}
105+
106+
merge(array, temp, left, mid, right);
107+
}
108+
109+
/**
110+
* Sorts the specified sub-array sequentially using standard Merge Sort.
111+
*
112+
* @param array the array to sort
113+
* @param temp a temporary array for merging
114+
* @param left the starting index of the sub-array
115+
* @param right the ending index of the sub-array
116+
*/
117+
private static void sequentialMergeSort(int[] array, int[] temp, int left, int right) {
118+
if (left >= right) {
119+
return;
120+
}
121+
122+
int mid = left + (right - left) / 2;
123+
sequentialMergeSort(array, temp, left, mid);
124+
sequentialMergeSort(array, temp, mid + 1, right);
125+
merge(array, temp, left, mid, right);
126+
}
127+
128+
/**
129+
* Merges two sorted sub-arrays into a single sorted sub-array.
130+
*
131+
* @param array the original array containing the sub-arrays
132+
* @param temp a temporary array used for merging
133+
* @param left the starting index of the first sub-array
134+
* @param mid the ending index of the first sub-array (and the partition point)
135+
* @param right the ending index of the second sub-array
136+
*/
137+
private static void merge(int[] array, int[] temp, int left, int mid, int right) {
138+
System.arraycopy(array, left, temp, left, right - left + 1);
139+
140+
int i = left;
141+
int j = mid + 1;
142+
int k = left;
143+
144+
while (i <= mid && j <= right) {
145+
if (temp[i] <= temp[j]) {
146+
array[k++] = temp[i++];
147+
} else {
148+
array[k++] = temp[j++];
149+
}
150+
}
151+
152+
while (i <= mid) {
153+
array[k++] = temp[i++];
154+
}
155+
156+
// Remaining elements from the right half are already in their correct relative positions
157+
}
158+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.thealgorithms.sorts;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
5+
import java.util.Arrays;
6+
import java.util.Random;
7+
import org.junit.jupiter.api.Test;
8+
9+
/**
10+
* JUnit 5 test class for {@link ConcurrentMergeSort}.
11+
*/
12+
public class ConcurrentMergeSortTest {
13+
14+
@Test
15+
public void testAlreadySortedArray() {
16+
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
17+
int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
18+
19+
ConcurrentMergeSort.sort(array);
20+
21+
assertArrayEquals(expected, array, "Already sorted array should remain unchanged.");
22+
}
23+
24+
@Test
25+
public void testReverseSortedArray() {
26+
int[] array = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
27+
int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
28+
29+
ConcurrentMergeSort.sort(array);
30+
31+
assertArrayEquals(expected, array, "Reverse sorted array should be sorted correctly.");
32+
}
33+
34+
@Test
35+
public void testIdenticalElementsArray() {
36+
int[] array = {5, 5, 5, 5, 5, 5, 5};
37+
int[] expected = {5, 5, 5, 5, 5, 5, 5};
38+
39+
ConcurrentMergeSort.sort(array);
40+
41+
assertArrayEquals(expected, array, "Array with identical elements should be sorted correctly (unchanged).");
42+
}
43+
44+
@Test
45+
public void testLargeRandomArray() {
46+
int size = 100_000;
47+
int[] array = new int[size];
48+
int[] expected = new int[size];
49+
// Using a fixed seed for deterministic testing
50+
Random random = new Random(42);
51+
52+
for (int i = 0; i < size; i++) {
53+
int value = random.nextInt();
54+
array[i] = value;
55+
expected[i] = value;
56+
}
57+
58+
// Generate the expected result using Java's highly optimized built-in sort
59+
Arrays.sort(expected);
60+
61+
// This will easily trigger the concurrency threshold (8192) in the implementation
62+
ConcurrentMergeSort.sort(array);
63+
64+
assertArrayEquals(expected, array, "Large random array should be sorted correctly utilizing concurrency.");
65+
}
66+
67+
@Test
68+
public void testEmptyArray() {
69+
int[] array = {};
70+
int[] expected = {};
71+
72+
ConcurrentMergeSort.sort(array);
73+
74+
assertArrayEquals(expected, array, "Empty array should be handled without errors.");
75+
}
76+
77+
@Test
78+
public void testSingleElementArray() {
79+
int[] array = {42};
80+
int[] expected = {42};
81+
82+
ConcurrentMergeSort.sort(array);
83+
84+
assertArrayEquals(expected, array, "Single element array should be handled without errors.");
85+
}
86+
}

0 commit comments

Comments
 (0)