feat: SpreadSort implementation (#5308)

This commit is contained in:
Alex Klymenko 2024-08-08 09:45:33 +02:00 committed by GitHub
parent 357e15addd
commit 6e23e198ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 312 additions and 0 deletions

View File

@ -523,6 +523,7 @@
* [SortAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java) * [SortAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java)
* [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java) * [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java)
* [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) * [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java)
* [SpreadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SpreadSort.java)
* [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java) * [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java)
* [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java) * [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java)
* [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java) * [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java)
@ -893,6 +894,7 @@
* [SortingAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortingAlgorithmTest.java) * [SortingAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortingAlgorithmTest.java)
* [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) * [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java)
* [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) * [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java)
* [SpreadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java)
* [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) * [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java)
* [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java) * [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java)
* [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java) * [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java)

View File

@ -0,0 +1,273 @@
package com.thealgorithms.sorts;
import java.util.Arrays;
/**
* SpreadSort is a highly efficient sorting algorithm suitable for large datasets.
* It distributes elements into buckets and recursively sorts these buckets.
* This implementation is generic and can sort any array of elements that extend Comparable.
*/
public class SpreadSort implements SortAlgorithm {
private static final int MAX_INSERTION_SORT_THRESHOLD = 1000;
private static final int MAX_INITIAL_BUCKET_CAPACITY = 1000;
private static final int MAX_MIN_BUCKETS = 100;
private final int insertionSortThreshold;
private final int initialBucketCapacity;
private final int minBuckets;
/**
* Constructor to initialize the SpreadSort algorithm with custom parameters.
*
* @param insertionSortThreshold the threshold for using insertion sort for small segments (1-1000)
* @param initialBucketCapacity the initial capacity for each bucket (1-1000)
* @param minBuckets the minimum number of buckets to use (1-100)
*/
public SpreadSort(int insertionSortThreshold, int initialBucketCapacity, int minBuckets) {
if (insertionSortThreshold < 1 || insertionSortThreshold > MAX_INSERTION_SORT_THRESHOLD) {
throw new IllegalArgumentException("Insertion sort threshold must be between 1 and " + MAX_INSERTION_SORT_THRESHOLD);
}
if (initialBucketCapacity < 1 || initialBucketCapacity > MAX_INITIAL_BUCKET_CAPACITY) {
throw new IllegalArgumentException("Initial bucket capacity must be between 1 and " + MAX_INITIAL_BUCKET_CAPACITY);
}
if (minBuckets < 1 || minBuckets > MAX_MIN_BUCKETS) {
throw new IllegalArgumentException("Minimum number of buckets must be between 1 and " + MAX_MIN_BUCKETS);
}
this.insertionSortThreshold = insertionSortThreshold;
this.initialBucketCapacity = initialBucketCapacity;
this.minBuckets = minBuckets;
}
/**
* Default constructor with predefined values.
*/
public SpreadSort() {
this(16, 16, 2);
}
/**
* Sorts an array using the SpreadSort algorithm.
*
* @param array the array to be sorted
* @param <T> the type of elements in the array
* @return the sorted array
*/
@Override
public <T extends Comparable<T>> T[] sort(T[] array) {
if (array.length == 0) {
return array;
}
spreadSort(array, 0, array.length - 1);
return array;
}
/**
* Internal method to sort an array segment using the SpreadSort algorithm.
*
* @param array the array to be sorted
* @param left the left boundary of the segment
* @param right the right boundary of the segment
* @param <T> the type of elements in the array
*/
private <T extends Comparable<T>> void spreadSort(final T[] array, final int left, final int right) {
if (left >= right) {
return;
}
// Base case for small segments
if (right - left < insertionSortThreshold) {
insertionSort(array, left, right);
return;
}
T min = findMin(array, left, right);
T max = findMax(array, left, right);
if (min.equals(max)) {
return; // All elements are the same
}
int numBuckets = calculateNumBuckets(right - left + 1);
final Bucket<T>[] buckets = createBuckets(numBuckets);
distributeElements(array, left, right, min, max, numBuckets, buckets);
collectElements(array, left, buckets);
}
/**
* Finds the minimum element in the specified segment of the array.
*
* @param array the array to search
* @param left the left boundary of the segment
* @param right the right boundary of the segment
* @param <T> the type of elements in the array
* @return the minimum element
*/
private <T extends Comparable<T>> T findMin(final T[] array, final int left, final int right) {
T min = array[left];
for (int i = left + 1; i <= right; i++) {
if (SortUtils.less(array[i], min)) {
min = array[i];
}
}
return min;
}
/**
* Finds the maximum element in the specified segment of the array.
*
* @param array the array to search
* @param left the left boundary of the segment
* @param right the right boundary of the segment
* @param <T> the type of elements in the array
* @return the maximum element
*/
private <T extends Comparable<T>> T findMax(final T[] array, final int left, final int right) {
T max = array[left];
for (int i = left + 1; i <= right; i++) {
if (SortUtils.greater(array[i], max)) {
max = array[i];
}
}
return max;
}
/**
* Calculates the number of buckets needed based on the size of the segment.
*
* @param segmentSize the size of the segment
* @return the number of buckets
*/
private int calculateNumBuckets(final int segmentSize) {
int numBuckets = segmentSize / insertionSortThreshold;
return Math.max(numBuckets, minBuckets);
}
/**
* Creates an array of buckets.
*
* @param numBuckets the number of buckets to create
* @param <T> the type of elements in the buckets
* @return an array of buckets
*/
@SuppressWarnings("unchecked")
private <T extends Comparable<T>> Bucket<T>[] createBuckets(final int numBuckets) {
final Bucket<T>[] buckets = new Bucket[numBuckets];
for (int i = 0; i < numBuckets; i++) {
buckets[i] = new Bucket<>(initialBucketCapacity);
}
return buckets;
}
/**
* Distributes elements of the array segment into buckets.
*
* @param array the array to be sorted
* @param left the left boundary of the segment
* @param right the right boundary of the segment
* @param min the minimum element in the segment
* @param max the maximum element in the segment
* @param numBuckets the number of buckets
* @param buckets the array of buckets
* @param <T> the type of elements in the array
*/
private <T extends Comparable<T>> void distributeElements(final T[] array, final int left, final int right, final T min, final T max, final int numBuckets, final Bucket<T>[] buckets) {
final double range = max.compareTo(min);
for (int i = left; i <= right; i++) {
final int scaleRangeDifference = array[i].compareTo(min) * numBuckets;
int bucketIndex = (int) (scaleRangeDifference / (range + 1));
buckets[bucketIndex].add(array[i]);
}
}
/**
* Collects elements from the buckets back into the array.
*
* @param array the array to be sorted
* @param left the left boundary of the segment
* @param buckets the array of buckets
* @param <T> the type of elements in the array
*/
private <T extends Comparable<T>> void collectElements(final T[] array, final int left, final Bucket<T>[] buckets) {
int index = left;
for (Bucket<T> bucket : buckets) {
if (bucket.size() > 0) {
T[] bucketArray = bucket.toArray();
spreadSort(bucketArray, 0, bucketArray.length - 1);
for (T element : bucketArray) {
array[index++] = element;
}
}
}
}
/**
* Insertion sort implementation for small segments.
*
* @param array the array to be sorted
* @param left the left boundary of the segment
* @param right the right boundary of the segment
* @param <T> the type of elements in the array
*/
private <T extends Comparable<T>> void insertionSort(final T[] array, final int left, final int right) {
for (int i = left + 1; i <= right; i++) {
T key = array[i];
int j = i - 1;
while (j >= left && SortUtils.greater(array[j], key)) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
/**
* Bucket class to hold elements during sorting.
*
* @param <T> the type of elements in the bucket
*/
private static class Bucket<T extends Comparable<T>> {
private T[] elements;
private int size;
/**
* Constructs a new bucket with initial capacity.
*/
@SuppressWarnings("unchecked")
Bucket(int initialBucketCapacity) {
elements = (T[]) new Comparable[initialBucketCapacity];
size = 0;
}
/**
* Adds an element to the bucket.
*
* @param element the element to add
*/
void add(T element) {
if (size == elements.length) {
elements = Arrays.copyOf(elements, size * 2);
}
elements[size++] = element;
}
/**
* Returns the number of elements in the bucket.
*
* @return the size of the bucket
*/
int size() {
return size;
}
/**
* Returns an array containing all elements in the bucket.
*
* @return an array containing all elements in the bucket
*/
@SuppressWarnings("unchecked")
T[] toArray() {
return Arrays.copyOf(elements, size);
}
}
}

View File

@ -0,0 +1,37 @@
package com.thealgorithms.sorts;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.stream.Stream;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
public class SpreadSortTest extends SortingAlgorithmTest {
protected int getGeneratedArraySize() {
return 1000;
}
@Override
SortAlgorithm getSortAlgorithm() {
return new SpreadSort();
}
static class ConstructorArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(org.junit.jupiter.api.extension.ExtensionContext context) {
return Stream.of(Arguments.of(0, 16, 2, IllegalArgumentException.class), Arguments.of(16, 0, 2, IllegalArgumentException.class), Arguments.of(16, 16, 0, IllegalArgumentException.class), Arguments.of(1001, 16, 2, IllegalArgumentException.class),
Arguments.of(16, 1001, 2, IllegalArgumentException.class), Arguments.of(16, 16, 101, IllegalArgumentException.class));
}
}
@ParameterizedTest
@ArgumentsSource(ConstructorArgumentsProvider.class)
void testConstructor(int insertionSortThreshold, int initialBucketCapacity, int minBuckets, Class<Exception> expectedException) {
Executable executable = () -> new SpreadSort(insertionSortThreshold, initialBucketCapacity, minBuckets);
assertThrows(expectedException, executable);
}
}