Arrays.sort.
8 | *
9 | * @author Sven Woltmann
10 | */
11 | public class JavaArraysSort implements SortAlgorithm {
12 |
13 | @Override
14 | public void sort(int[] elements) {
15 | Arrays.sort(elements);
16 | }
17 |
18 | @Override
19 | public void sortWithCounters(int[] elements, Counters counters) {
20 | throw new NotImplementedException();
21 | }
22 |
23 | @Override
24 | public boolean supportsCounting() {
25 | return false;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/quicksort/DualPivotQuicksortLeftRightTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.quicksort;
2 |
3 | import eu.happycoders.sort.method.*;
4 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksort.PivotStrategy;
5 | import java.util.concurrent.ThreadLocalRandom;
6 |
7 | class DualPivotQuicksortLeftRightTest extends SortTest {
8 |
9 | @Override
10 | protected SortAlgorithm getSortAlgorithm() {
11 | return new DualPivotQuicksort(PivotStrategy.LEFT_RIGHT);
12 | }
13 |
14 | @Override
15 | protected int randomSize() {
16 | // Not more than 1000; going into recursion this deep, because partitioning
17 | // will always partition at the left or right side.
18 | return ThreadLocalRandom.current().nextInt(2, 1_000);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/quicksort/DualPivotQuicksortImprovedThirdsTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.quicksort;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.SortTest;
5 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksort.PivotStrategy;
6 | import java.util.concurrent.ThreadLocalRandom;
7 |
8 | class DualPivotQuicksortImprovedThirdsTest extends SortTest {
9 |
10 | @Override
11 | protected SortAlgorithm getSortAlgorithm() {
12 | return new DualPivotQuicksortImproved(64, PivotStrategy.THIRDS);
13 | }
14 |
15 | @Override
16 | protected int randomSize() {
17 | // Not more than 1000; going into recursion this deep, because partitioning
18 | // will always partition at the left or right side.
19 | return ThreadLocalRandom.current().nextInt(2, 1_000);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/radixsort/RadixSortHelperTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.radixsort;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4 | import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class RadixSortHelperTest {
9 |
10 | @Test
11 | void checkIfContainsNegativesThrowsAnExceptionIfInputContainsANegativeNumber() {
12 | int[] elements = {5, -1, 8};
13 | assertThrowsExactly(
14 | IllegalArgumentException.class, () -> RadixSortHelper.checkIfContainsNegatives(elements));
15 | }
16 |
17 | @Test
18 | void checkIfContainsNegativesDoesNotThrowAnExceptionIfInputContainsNoNegativeNumber() {
19 | int[] elements = {0, 1, 2};
20 | assertDoesNotThrow(() -> RadixSortHelper.checkIfContainsNegatives(elements));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/quicksort/DualPivotQuicksortImprovedLeftRightTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.quicksort;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.SortTest;
5 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksort.PivotStrategy;
6 | import java.util.concurrent.ThreadLocalRandom;
7 |
8 | class DualPivotQuicksortImprovedLeftRightTest extends SortTest {
9 |
10 | @Override
11 | protected SortAlgorithm getSortAlgorithm() {
12 | return new DualPivotQuicksortImproved(64, PivotStrategy.LEFT_RIGHT);
13 | }
14 |
15 | @Override
16 | protected int randomSize() {
17 | // Not more than 1000; going into recursion this deep, because partitioning
18 | // will always partition at the left or right side.
19 | return ThreadLocalRandom.current().nextInt(2, 1_000);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/comparisons/CompareMergeSorts.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.comparisons;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.mergesort.MergeSort;
5 | import eu.happycoders.sort.method.mergesort.MergeSort2;
6 | import eu.happycoders.sort.method.mergesort.MergeSort3;
7 |
8 | /**
9 | * Compares the three merge sort algorithms.
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class CompareMergeSorts extends DirectComparison {
14 |
15 | private static final int SIZE = 4_444_444; // ~600 ms for Merge Sort
16 |
17 | public static void main(String[] args) {
18 | new CompareMergeSorts().run();
19 | }
20 |
21 | private void run() {
22 | SortAlgorithm[] algorithms = {new MergeSort(), new MergeSort2(), new MergeSort3()};
23 |
24 | runTest(algorithms, SIZE);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/countingsort/CountingSortTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.countingsort;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.SortTest;
5 | import eu.happycoders.sort.utils.ArrayUtils;
6 | import org.junit.jupiter.api.RepeatedTest;
7 |
8 | @SuppressWarnings("java:S2699") // The assertions are in the sortAndTestIfSorted() method
9 | class CountingSortTest extends SortTest {
10 |
11 | @RepeatedTest(100)
12 | void sort_randomNumbers_sorted() {
13 | sortAndTestIfSorted(ArrayUtils::createRandomArrayIncludingNegatives);
14 | }
15 |
16 | @RepeatedTest(100)
17 | void sort_sortedNumbers_sorted() {
18 | sortAndTestIfSorted(ArrayUtils::createSortedArrayIncludingNegatives);
19 | }
20 |
21 | @RepeatedTest(100)
22 | void sort_reverseNumbers_sorted() {
23 | sortAndTestIfSorted(ArrayUtils::createReversedArrayIncludingNegatives);
24 | }
25 |
26 | @Override
27 | protected SortAlgorithm getSortAlgorithm() {
28 | return new CountingSort();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: dependabot auto-merge
2 | # Using a short name here for two reasons:
3 | # 1) The "Workflows" column on the "Actions" tab is quite narrow
4 | # 2) The name is used as label on the README badge
5 |
6 | on: pull_request
7 |
8 | permissions:
9 | pull-requests: write
10 | contents: write
11 |
12 | jobs:
13 | dependabot:
14 | runs-on: ubuntu-latest
15 | if: ${{ github.actor == 'dependabot[bot]' }}
16 | steps:
17 | - name: Dependabot metadata
18 | id: metadata
19 | uses: dependabot/fetch-metadata@v2.4.0
20 | with:
21 | github-token: "${{ secrets.GITHUB_TOKEN }}"
22 |
23 | - name: Approve a PR
24 | run: gh pr review --approve "$PR_URL"
25 | env:
26 | PR_URL: ${{github.event.pull_request.html_url}}
27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
28 |
29 | - name: Enable auto-merge for Dependabot PRs
30 | if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
31 | run: gh pr merge --auto --rebase "$PR_URL"
32 | env:
33 | PR_URL: ${{github.event.pull_request.html_url}}
34 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
35 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/radixsort/RadixSortHelper.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.radixsort;
2 |
3 | final class RadixSortHelper {
4 |
5 | private RadixSortHelper() {}
6 |
7 | static void checkIfContainsNegatives(int[] elements) {
8 | for (int element : elements) {
9 | if (element < 0) {
10 | throw new IllegalArgumentException("Negative elements are not allowed");
11 | }
12 | }
13 | }
14 |
15 | static int getNumberOfDigits(int number) {
16 | int numberOfDigits = 1;
17 | while (number >= 10) {
18 | number /= 10;
19 | numberOfDigits++;
20 | }
21 | return numberOfDigits;
22 | }
23 |
24 | static int getNumberOfDigits(int number, int base) {
25 | int numberOfDigits = 1;
26 | while (number >= base) {
27 | number /= base;
28 | numberOfDigits++;
29 | }
30 | return numberOfDigits;
31 | }
32 |
33 | static int calculateDivisor(int digitIndex) {
34 | int divisor = 1;
35 | for (int i = 0; i < digitIndex; i++) {
36 | divisor *= 10;
37 | }
38 | return divisor;
39 | }
40 |
41 | static int calculateDivisor(int digitIndex, int base) {
42 | int divisor = 1;
43 | for (int i = 0; i < digitIndex; i++) {
44 | divisor *= base;
45 | }
46 | return divisor;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/PartitioningAlgorithm.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method;
2 |
3 | /**
4 | * Partitioning algorithm interface; having all Quicksort algorithms implement this interface makes
5 | * it easier to compare the performance of their partitioning algorithms.
6 | *
7 | * @author Sven Woltmann
8 | */
9 | public interface PartitioningAlgorithm extends SortAlgorithm {
10 |
11 | /**
12 | * Partitions the given elements from the left to the right position and returns the position of
13 | * the pivot element.
14 | *
15 | * @param elements the elements to partition
16 | * @param left the left position in the index
17 | * @param right the right position in the index
18 | * @return the position of the pivot element
19 | */
20 | int partition(int[] elements, int left, int right);
21 |
22 | /**
23 | * Partitions the given elements from the left to the right position and returns the position of
24 | * the pivot element.
25 | *
26 | * @param elements the elements to partition
27 | * @param left the left position in the index
28 | * @param right the right position in the index
29 | * @return the position of the pivot element
30 | */
31 | int partitionWithCounters(int[] elements, int left, int right, Counters counters);
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/bubblesort/BubbleSortParallelOddEven.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.bubblesort;
2 |
3 | /**
4 | * Parallel Bubble Sort implementation using the "odd-even" algorithm.
5 | *
6 | * @author Sven Woltmann
7 | */
8 | public class BubbleSortParallelOddEven extends BubbleSortParallel {
9 |
10 | /**
11 | * Sorts a partition of the elements.
12 | *
13 | * @param elements the elements
14 | * @param startPos the partition's start position
15 | * @param endPos the partition's end position
16 | * @param even whether it's the even or odd step of an iteration
17 | * @return whether any elements were swapped
18 | */
19 | @Override
20 | boolean sortPartition(int[] elements, int startPos, int endPos, boolean even) {
21 | boolean swapped = false;
22 |
23 | // Odd steps 1, 3, 5, ... --> start at the first element
24 | // Even steps 2, 4, 6, ... --> start at the second element
25 | if (even) {
26 | startPos++;
27 | }
28 |
29 | for (int i = startPos; i < endPos && i < elements.length - 1; i += 2) {
30 | int left = elements[i];
31 | int right = elements[i + 1];
32 | if (left > right) {
33 | elements[i + 1] = left;
34 | elements[i] = right;
35 | swapped = true;
36 | }
37 | }
38 |
39 | return swapped;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/comparisons/CompareBubbleSorts.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.comparisons;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.bubblesort.BubbleSort;
5 | import eu.happycoders.sort.method.bubblesort.BubbleSortOpt1;
6 | import eu.happycoders.sort.method.bubblesort.BubbleSortOpt2;
7 | import eu.happycoders.sort.method.bubblesort.BubbleSortParallelDivideAndConquer;
8 | import eu.happycoders.sort.method.bubblesort.BubbleSortParallelOddEven;
9 | import java.util.List;
10 |
11 | /**
12 | * Compares the regular Quicksort with the improved Quicksort for various thresholds at which the
13 | * algorithm switches from Quicksort to Insertion Sort.
14 | *
15 | * @author Sven Woltmann
16 | */
17 | public class CompareBubbleSorts extends DirectComparison {
18 |
19 | private static final int SIZE = 40_000; // ~500 ms for Bubble Sort
20 |
21 | public static void main(String[] args) {
22 | new CompareBubbleSorts().run();
23 | }
24 |
25 | private void run() {
26 | ListVariant 1: swaps pivot element with rightmost element first 9 | * 10 | * @author Sven Woltmann 11 | */ 12 | public class QuicksortVariant1 extends QuicksortSimple { 13 | 14 | private final PivotStrategy pivotStrategy; 15 | 16 | public QuicksortVariant1(PivotStrategy pivotStrategy) { 17 | this.pivotStrategy = pivotStrategy; 18 | } 19 | 20 | @Override 21 | public String getName() { 22 | return this.getClass().getSimpleName() + "(pivot: " + pivotStrategy + ")"; 23 | } 24 | 25 | @Override 26 | public boolean isSuitableForSortedInput(int size) { 27 | return pivotStrategy != PivotStrategy.LEFT && pivotStrategy != PivotStrategy.RIGHT 28 | || size <= 2 << 12; 29 | } 30 | 31 | @Override 32 | public int partition(int[] elements, int left, int right) { 33 | PivotHelper.findPivotAndMoveRight(elements, left, right, pivotStrategy); 34 | return super.partition(elements, left, right); 35 | } 36 | 37 | @Override 38 | public int partitionWithCounters(int[] elements, int left, int right, Counters counters) { 39 | PivotHelper.findPivotAndMoveRight(elements, left, right, pivotStrategy); 40 | return super.partitionWithCounters(elements, left, right, counters); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/eu/happycoders/sort/method/mergesort/MergeSortMergeTest.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.mergesort; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | 5 | import eu.happycoders.sort.utils.ArrayUtils; 6 | import java.util.Arrays; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import org.junit.jupiter.api.*; 9 | 10 | class MergeSortMergeTest { 11 | 12 | @Test 13 | void merge_twoSortedElements_merged() { 14 | testMerge(new int[] {8}, new int[] {15}); 15 | } 16 | 17 | @Test 18 | void merge_twoUnsortedElements_merged() { 19 | testMerge(new int[] {17}, new int[] {4}); 20 | } 21 | 22 | @RepeatedTest(100) 23 | void merge_twoRandomSectionsOnAList_merged() { 24 | ThreadLocalRandom rand = ThreadLocalRandom.current(); 25 | int[] leftArray = ArrayUtils.createRandomArray(rand.nextInt(1, 1000)); 26 | Arrays.sort(leftArray); 27 | int[] rightArray = ArrayUtils.createRandomArray(rand.nextInt(1, 1000)); 28 | Arrays.sort(rightArray); 29 | testMerge(leftArray, rightArray); 30 | } 31 | 32 | private void testMerge(int[] leftArray, int[] rightArray) { 33 | int[] merged = new MergeSort().merge(leftArray, rightArray); 34 | 35 | int[] expectedArray = new int[leftArray.length + rightArray.length]; 36 | System.arraycopy(leftArray, 0, expectedArray, 0, leftArray.length); 37 | System.arraycopy(rightArray, 0, expectedArray, leftArray.length, rightArray.length); 38 | Arrays.sort(expectedArray); 39 | assertArrayEquals(expectedArray, merged); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-jvm: 11 | name: Build 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v6 18 | with: 19 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 20 | 21 | - name: Set up JDK 17 22 | uses: actions/setup-java@v5 23 | with: 24 | distribution: 'adopt' 25 | java-version: 17 26 | 27 | - name: Cache SonarCloud packages 28 | uses: actions/cache@v5 29 | with: 30 | path: ~/.sonar/cache 31 | key: ${{ runner.os }}-sonar 32 | restore-keys: ${{ runner.os }}-sonar 33 | 34 | - name: Cache Maven packages 35 | uses: actions/cache@v5 36 | with: 37 | path: ~/.m2 38 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 39 | restore-keys: ${{ runner.os }}-m2 40 | 41 | - name: Verify code format 42 | run: mvn -B spotless:check 43 | 44 | - name: Compile, test and verify 45 | run: mvn -B verify -Ptest-coverage,code-analysis 46 | 47 | - name: Analyze code with Sonar 48 | if: ${{ env.SONAR_TOKEN }} # the token is not available in Dependabot-triggered builds 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 52 | run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar 53 | -------------------------------------------------------------------------------- /src/test/java/eu/happycoders/sort/method/mergesort/MergeSort3MergeTest.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.mergesort; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | 5 | import eu.happycoders.sort.utils.ArrayUtils; 6 | import java.util.Arrays; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import org.junit.jupiter.api.*; 9 | 10 | class MergeSort3MergeTest { 11 | 12 | @Test 13 | void merge_twoSortedElements_merged() { 14 | testMerge(new int[] {8}, new int[] {15}); 15 | } 16 | 17 | @Test 18 | void merge_twoUnsortedElements_merged() { 19 | testMerge(new int[] {17}, new int[] {4}); 20 | } 21 | 22 | @RepeatedTest(100) 23 | void merge_twoRandomSectionsOnAList_merged() { 24 | ThreadLocalRandom rand = ThreadLocalRandom.current(); 25 | int[] leftArray = ArrayUtils.createRandomArray(rand.nextInt(1, 1000)); 26 | Arrays.sort(leftArray); 27 | int[] rightArray = ArrayUtils.createRandomArray(rand.nextInt(1, 1000)); 28 | Arrays.sort(rightArray); 29 | testMerge(leftArray, rightArray); 30 | } 31 | 32 | private void testMerge(int[] leftArray, int[] rightArray) { 33 | int[] merged = new int[leftArray.length + rightArray.length]; 34 | new MergeSort3().merge(merged, leftArray, rightArray); 35 | 36 | int[] expectedArray = new int[leftArray.length + rightArray.length]; 37 | System.arraycopy(leftArray, 0, expectedArray, 0, leftArray.length); 38 | System.arraycopy(rightArray, 0, expectedArray, leftArray.length, rightArray.length); 39 | Arrays.sort(expectedArray); 40 | assertArrayEquals(expectedArray, merged); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/eu/happycoders/sort/method/mergesort/MergeSort2MergeTest.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.mergesort; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | 5 | import eu.happycoders.sort.utils.ArrayUtils; 6 | import java.util.Arrays; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import org.junit.jupiter.api.*; 9 | 10 | class MergeSort2MergeTest { 11 | 12 | @Test 13 | void merge_twoSortedElements_merged() { 14 | testMerge(new int[] {8, 15}); 15 | } 16 | 17 | @Test 18 | void merge_twoUnsortedElements_merged() { 19 | testMerge(new int[] {17, 4}); 20 | } 21 | 22 | @RepeatedTest(100) 23 | void merge_twoRandomSectionsOnAList_merged() { 24 | int length = ThreadLocalRandom.current().nextInt(2, 20); 25 | int[] numbers = ArrayUtils.createRandomArray(length); 26 | testMerge(numbers); 27 | } 28 | 29 | private void testMerge(int[] numbers) { 30 | ThreadLocalRandom rand = ThreadLocalRandom.current(); 31 | int length = numbers.length; 32 | int left = rand.nextInt(0, length / 2); 33 | int right = rand.nextInt(length / 2, length); 34 | int mid = left + (right - left) / 2; 35 | 36 | int[] arrayBefore = Arrays.copyOfRange(numbers, left, right + 1); 37 | Arrays.sort(numbers, left, mid + 1); 38 | Arrays.sort(numbers, mid + 1, right + 1); 39 | new MergeSort2().merge(numbers, left, mid, right); 40 | int[] arrayAfter = Arrays.copyOfRange(numbers, left, right + 1); 41 | 42 | int[] expectedArray = arrayBefore.clone(); 43 | Arrays.sort(expectedArray); 44 | assertArrayEquals(expectedArray, arrayAfter); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/eu/happycoders/sort/method/mergesort/InPlaceMergeSortMergeTest.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.mergesort; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 4 | 5 | import eu.happycoders.sort.utils.ArrayUtils; 6 | import java.util.Arrays; 7 | import java.util.concurrent.ThreadLocalRandom; 8 | import org.junit.jupiter.api.RepeatedTest; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class InPlaceMergeSortMergeTest { 12 | 13 | @Test 14 | void merge_twoSortedElements_merged() { 15 | testMerge(new int[] {8, 15}); 16 | } 17 | 18 | @Test 19 | void merge_twoUnsortedElements_merged() { 20 | testMerge(new int[] {17, 4}); 21 | } 22 | 23 | @RepeatedTest(100) 24 | void merge_twoRandomSectionsOnAList_merged() { 25 | int length = ThreadLocalRandom.current().nextInt(2, 20); 26 | int[] numbers = ArrayUtils.createRandomArray(length); 27 | testMerge(numbers); 28 | } 29 | 30 | private void testMerge(int[] numbers) { 31 | ThreadLocalRandom rand = ThreadLocalRandom.current(); 32 | int length = numbers.length; 33 | int left = rand.nextInt(0, length / 2); 34 | int right = rand.nextInt(length / 2, length); 35 | int mid = left + (right - left) / 2; 36 | 37 | int[] arrayBefore = Arrays.copyOfRange(numbers, left, right + 1); 38 | Arrays.sort(numbers, left, mid + 1); 39 | Arrays.sort(numbers, mid + 1, right + 1); 40 | new InPlaceMergeSort().merge(numbers, left, mid + 1, right); 41 | int[] arrayAfter = Arrays.copyOfRange(numbers, left, right + 1); 42 | 43 | int[] expectedArray = arrayBefore.clone(); 44 | Arrays.sort(expectedArray); 45 | assertArrayEquals(expectedArray, arrayAfter); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/eu/happycoders/sort/method/bubblesort/BubbleSortParallelDivideAndConquer.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.bubblesort; 2 | 3 | /** 4 | * Parallel Bubble Sort implementation using a divide-and-conquer approach. 5 | * 6 | * @author Sven Woltmann 7 | */ 8 | public class BubbleSortParallelDivideAndConquer extends BubbleSortParallel { 9 | 10 | /** 11 | * Sorts a partition of the elements. 12 | * 13 | * @param elements the elements 14 | * @param startPos the partition's start position 15 | * @param endPos the partition's end position 16 | * @param even whether it's the even or odd step of an iteration 17 | * @return whether any elements were swapped 18 | */ 19 | @Override 20 | boolean sortPartition(int[] elements, int startPos, int endPos, boolean even) { 21 | boolean swapped = false; 22 | 23 | // Step 1, 3, 5, ... 24 | // iterate over all elements of the partition 25 | if (!even) { 26 | for (int i = startPos; i < endPos - 1; i++) { 27 | int left = elements[i]; 28 | int right = elements[i + 1]; 29 | if (left > right) { 30 | elements[i + 1] = left; 31 | elements[i] = right; 32 | swapped = true; 33 | } 34 | } 35 | } 36 | 37 | // Step 2, 4, 6, ... 38 | // compare this partition's last element with the next partition's first 39 | else if (endPos < elements.length - 1) { 40 | int left = elements[endPos - 1]; 41 | int right = elements[endPos]; 42 | if (left > right) { 43 | elements[endPos] = left; 44 | elements[endPos - 1] = right; 45 | swapped = true; 46 | } 47 | } 48 | 49 | return swapped; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/eu/happycoders/sort/method/bubblesort/BubbleSort.java: -------------------------------------------------------------------------------- 1 | package eu.happycoders.sort.method.bubblesort; 2 | 3 | import eu.happycoders.sort.method.Counters; 4 | import eu.happycoders.sort.method.SortAlgorithm; 5 | 6 | /** 7 | * Bubble Sort implementation for performance tests. 8 | * 9 | *
Unoptimized variant.
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class BubbleSort implements SortAlgorithm {
14 |
15 | @Override
16 | public void sort(int[] elements) {
17 | int numElements = elements.length;
18 | for (; ; ) {
19 | boolean swapped = false;
20 | for (int i = 0; i < numElements - 1; i++) {
21 | int left = elements[i];
22 | int right = elements[i + 1];
23 | if (left > right) {
24 | elements[i + 1] = left;
25 | elements[i] = right;
26 | swapped = true;
27 | }
28 | }
29 | if (!swapped) {
30 | break;
31 | }
32 | }
33 | }
34 |
35 | @Override
36 | public void sortWithCounters(int[] elements, Counters counters) {
37 | int numElements = elements.length;
38 | for (; ; ) {
39 | counters.incIterations();
40 |
41 | boolean swapped = false;
42 | for (int i = 0; i < numElements - 1; i++) {
43 | counters.incIterations();
44 |
45 | int left = elements[i];
46 | int right = elements[i + 1];
47 | counters.addReads(2);
48 | counters.incComparisons();
49 | if (left > right) {
50 | elements[i + 1] = left;
51 | elements[i] = right;
52 | counters.addWrites(2);
53 | swapped = true;
54 | }
55 | }
56 | if (!swapped) {
57 | break;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/comparisons/CompareImprovedDualPivotQuicksort.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.comparisons;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksort;
5 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksortImproved;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * Compares Dual-Pivot Quicksort with the improved version for various thresholds at which the
11 | * algorithm switches from Quicksort to Insertion Sort.
12 | *
13 | * @author Sven Woltmann
14 | */
15 | public class CompareImprovedDualPivotQuicksort extends DirectComparison {
16 |
17 | private static final int SIZE = 5_555_555; // ~500 ms for Quicksort
18 |
19 | public static void main(String[] args) {
20 | new CompareImprovedDualPivotQuicksort().run();
21 | }
22 |
23 | @SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
24 | private void run() {
25 | List Optimized: in the n-th iteration, the n-th largest element is put into place, so we can ignore
10 | * the last n-1 elements.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | public class BubbleSortOpt1 implements SortAlgorithm {
15 |
16 | @Override
17 | public void sort(int[] elements) {
18 | for (int max = elements.length - 1; max > 0; max--) {
19 | boolean swapped = false;
20 | for (int i = 0; i < max; i++) {
21 | int left = elements[i];
22 | int right = elements[i + 1];
23 | if (left > right) {
24 | elements[i + 1] = left;
25 | elements[i] = right;
26 | swapped = true;
27 | }
28 | }
29 | if (!swapped) {
30 | break;
31 | }
32 | }
33 | }
34 |
35 | @Override
36 | public void sortWithCounters(int[] elements, Counters counters) {
37 | for (int max = elements.length - 1; max > 0; max--) {
38 | counters.incIterations();
39 |
40 | boolean swapped = false;
41 | for (int i = 0; i < max; i++) {
42 | counters.incIterations();
43 |
44 | int left = elements[i];
45 | int right = elements[i + 1];
46 | counters.addReads(2);
47 | counters.incComparisons();
48 | if (left > right) {
49 | elements[i + 1] = left;
50 | elements[i] = right;
51 | counters.addWrites(2);
52 | swapped = true;
53 | }
54 | }
55 | if (!swapped) {
56 | break;
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/FindMinimumTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos;
2 |
3 | import eu.happycoders.sort.utils.ArrayUtils;
4 | import java.util.Locale;
5 |
6 | /**
7 | * Counts minPos/min assignments for finding the smallest element in an unsorted array.
8 | *
9 | * @author Sven Woltmann
10 | */
11 | public class FindMinimumTest {
12 | private static final int NUM_SIZES = 29;
13 | private static final int NUM_TESTS = 100;
14 | private final int[] counts = new int[NUM_SIZES];
15 |
16 | public static void main(String[] args) {
17 | new FindMinimumTest().run();
18 | }
19 |
20 | private void run() {
21 | for (int i = 0; i < NUM_TESTS; i++) {
22 | test();
23 | printResults(i + 1);
24 | }
25 | }
26 |
27 | private void test() {
28 | for (int i = 0; i < NUM_SIZES; i++) {
29 | int size = 2 << i;
30 | int assignments = countAssignmentsForSize(size);
31 | counts[i] += assignments;
32 | }
33 | }
34 |
35 | private int countAssignmentsForSize(int size) {
36 | int[] array = ArrayUtils.createRandomArray(size);
37 | int min = Integer.MAX_VALUE;
38 | int assignments = 0;
39 | for (int i = 0; i < size; i++) {
40 | int element = array[i];
41 | if (element < min) {
42 | min = element;
43 | assignments++;
44 | }
45 | }
46 | return assignments;
47 | }
48 |
49 | @SuppressWarnings({"PMD.SystemPrintln", "java:S106"})
50 | private void printResults(int iterations) {
51 | System.out.printf(Locale.US, "Results after %d iterations:%n", iterations);
52 | for (int i = 0; i < NUM_SIZES; i++) {
53 | int size = 2 << i;
54 | double avg = (double) counts[i] / iterations;
55 | System.out.printf(Locale.US, "- size: %,11d --> avg. no of assignments: %5.2f%n", size, avg);
56 | }
57 | System.out.println();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/bubblesort/BubbleSortOpt2.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.bubblesort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 |
6 | /**
7 | * Bubble Sort implementation for performance tests.
8 | *
9 | * Optimized: in each iteration, more than one element can be placed in its final position; we
10 | * assume all elements after the last swap to be sorted.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | public class BubbleSortOpt2 implements SortAlgorithm {
15 |
16 | @Override
17 | public void sort(int[] elements) {
18 | int max = elements.length - 1;
19 | for (; ; ) {
20 | int lastSwapped = 0;
21 | for (int i = 0; i < max; i++) {
22 | int left = elements[i];
23 | int right = elements[i + 1];
24 | if (left > right) {
25 | elements[i + 1] = left;
26 | elements[i] = right;
27 | lastSwapped = i;
28 | }
29 | }
30 | if (lastSwapped == 0) {
31 | break;
32 | }
33 | max = lastSwapped;
34 | }
35 | }
36 |
37 | @Override
38 | public void sortWithCounters(int[] elements, Counters counters) {
39 | int max = elements.length - 1;
40 | for (; ; ) {
41 | counters.incIterations();
42 | int lastSwapped = 0;
43 | for (int i = 0; i < max; i++) {
44 | counters.incIterations();
45 |
46 | int left = elements[i];
47 | int right = elements[i + 1];
48 | counters.addReads(2);
49 | counters.incComparisons();
50 | if (left > right) {
51 | elements[i + 1] = left;
52 | elements[i] = right;
53 | counters.addWrites(2);
54 | lastSwapped = i;
55 | }
56 | }
57 | if (lastSwapped == 0) {
58 | break;
59 | }
60 | max = lastSwapped;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/countingsort/CountingSortGeneral.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.countingsort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 | import eu.happycoders.sort.utils.NotImplementedException;
6 |
7 | /**
8 | * General Counting Sort implementation.
9 | *
10 | * For simplicity, this implementation allows only elements >= 0.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | public class CountingSortGeneral implements SortAlgorithm {
15 |
16 | @Override
17 | public void sort(int[] elements) {
18 | int maxValue = findMax(elements);
19 | int[] counts = new int[maxValue + 1];
20 |
21 | // Phase 1: Count
22 | for (int element : elements) {
23 | counts[element]++;
24 | }
25 |
26 | // Phase 2: Aggregate
27 | for (int i = 1; i <= maxValue; i++) {
28 | counts[i] += counts[i - 1];
29 | }
30 |
31 | // Phase 3: Write to target array
32 | int[] target = new int[elements.length];
33 | for (int i = elements.length - 1; i >= 0; i--) {
34 | int element = elements[i];
35 | target[--counts[element]] = element;
36 | }
37 |
38 | // Copy target back to input array
39 | System.arraycopy(target, 0, elements, 0, elements.length);
40 | }
41 |
42 | private int findMax(int[] elements) {
43 | int max = 0;
44 | for (int element : elements) {
45 | if (element < 0) {
46 | throw new IllegalArgumentException("This implementation does not support negative values.");
47 | }
48 | if (element > max) {
49 | max = element;
50 | }
51 | }
52 | return max;
53 | }
54 |
55 | @Override
56 | public void sortWithCounters(int[] elements, Counters counters) {
57 | throw new NotImplementedException();
58 | }
59 |
60 | @Override
61 | public boolean supportsCounting() {
62 | return false;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/heapsort/BottomUpHeapsortSlowComparisons.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.heapsort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.utils.NotImplementedException;
5 |
6 | /**
7 | * Bottom-up heapsort implementation with slow comparisons (to show that bottom-up heapsort is
8 | * faster than regular heapsort if comparisons are expensive).
9 | *
10 | * @author Sven Woltmann
11 | */
12 | public class BottomUpHeapsortSlowComparisons extends BottomUpHeapsort {
13 |
14 | @Override
15 | int findLeaf(int[] heap, int length, int rootPos) {
16 | int pos = rootPos;
17 | int leftChildPos = pos * 2 + 1;
18 | int rightChildPos = pos * 2 + 2;
19 |
20 | while (rightChildPos < length) {
21 | slowDown();
22 | if (heap[rightChildPos] > heap[leftChildPos]) {
23 | pos = rightChildPos;
24 | } else {
25 | pos = leftChildPos;
26 | }
27 | leftChildPos = pos * 2 + 1;
28 | rightChildPos = pos * 2 + 2;
29 | }
30 |
31 | if (leftChildPos < length) {
32 | pos = leftChildPos;
33 | }
34 |
35 | return pos;
36 | }
37 |
38 | @Override
39 | int findTargetNodeBottomUp(int[] heap, int rootPos, int leafPos) {
40 | int parent = heap[rootPos];
41 | while (leafPos != rootPos) {
42 | slowDown();
43 | if (heap[leafPos] < parent) {
44 | leafPos = getParentPos(leafPos);
45 | } else {
46 | break;
47 | }
48 | }
49 | return leafPos;
50 | }
51 |
52 | @Override
53 | public void sortWithCounters(int[] elements, Counters counters) {
54 | throw new NotImplementedException();
55 | }
56 |
57 | private void slowDown() {
58 | Thread.onSpinWait();
59 | }
60 |
61 | @Override
62 | public boolean isSuitableForSortedInput(int size) {
63 | return false;
64 | }
65 |
66 | @Override
67 | public boolean supportsCounting() {
68 | return false;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/InsertionSort.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method;
2 |
3 | /**
4 | * Insertion Sort implementation for performance tests.
5 | *
6 | * @author Sven Woltmann
7 | */
8 | // We don't want to use System.arraycopy - we want to demonstrate how the algorithm works
9 | @SuppressWarnings("PMD.AvoidArrayLoops")
10 | public class InsertionSort implements SortAlgorithm {
11 |
12 | @Override
13 | public void sort(int[] elements) {
14 | sort(elements, 0, elements.length);
15 | }
16 |
17 | public void sort(int[] elements, int fromIndex, int toIndex) {
18 | for (int i = fromIndex + 1; i < toIndex; i++) {
19 | int elementToSort = elements[i];
20 |
21 | // Move element to the left until it's at the right position
22 | int j = i;
23 | while (j > fromIndex && elementToSort < elements[j - 1]) {
24 | elements[j] = elements[j - 1];
25 | j--;
26 | }
27 | elements[j] = elementToSort;
28 | }
29 | }
30 |
31 | @Override
32 | public void sortWithCounters(int[] elements, Counters counters) {
33 | sortWithCounters(elements, 0, elements.length, counters);
34 | }
35 |
36 | public void sortWithCounters(int[] elements, int fromIndex, int toIndex, Counters counters) {
37 | for (int i = fromIndex + 1; i < toIndex; i++) {
38 | counters.incIterations();
39 |
40 | int number = elements[i];
41 | counters.incReads();
42 |
43 | // Move element to the left until it's at the right position
44 | int j = i;
45 | while (j > fromIndex) {
46 | counters.incIterations();
47 |
48 | counters.incComparisons();
49 | if (number < elements[j - 1]) {
50 | elements[j] = elements[j - 1];
51 | counters.incReads();
52 | counters.incWrites();
53 | j--;
54 | } else {
55 | break;
56 | }
57 | }
58 | elements[j] = number;
59 | counters.incWrites();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/comparisons/CompareQuicksorts.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.comparisons;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.quicksort.PivotStrategy;
5 | import eu.happycoders.sort.method.quicksort.QuicksortSimple;
6 | import eu.happycoders.sort.method.quicksort.QuicksortVariant1;
7 | import eu.happycoders.sort.method.quicksort.QuicksortVariant2;
8 | import eu.happycoders.sort.method.quicksort.QuicksortVariant3;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * Compares the various Quicksort algorithm variants.
14 | *
15 | * @author Sven Woltmann
16 | */
17 | public class CompareQuicksorts extends DirectComparison {
18 |
19 | private static final int SIZE = 5_555_555; // ~500 ms for Quicksort
20 |
21 | public static void main(String[] args) {
22 | new CompareQuicksorts().run();
23 | }
24 |
25 | private void run() {
26 | List If not for a test, I would omit the interface and make the sort methods static instead.
8 | *
9 | * @author Sven Woltmann
10 | */
11 | public interface SortAlgorithm {
12 |
13 | void sort(int[] elements);
14 |
15 | void sortWithCounters(int[] elements, Counters counters);
16 |
17 | /**
18 | * Returns the name, which is the class name by default, but can also be overridden, e.g., in
19 | * Quicksort to include the pivot strategy.
20 | *
21 | * @return the name
22 | */
23 | default String getName() {
24 | return this.getClass().getSimpleName();
25 | }
26 |
27 | /**
28 | * Indicates whether this test is suitable for pre-sorted input.
29 | *
30 | * This is, for example, not the case for Quicksort using the left-most or right-most element
31 | * as pivot element, as the recursion would be too deep and we would get a StackOverflowException.
32 | *
33 | * @param size the number of elements
34 | * @return whether this test is suitable for pre-sorted input
35 | */
36 | default boolean isSuitableForSortedInput(int size) {
37 | return true;
38 | }
39 |
40 | /**
41 | * Indicates whether this test is suitable for the given input size.
42 | *
43 | * CountingSort, for example, should be limited to a specific size.
44 | *
45 | * @param size the number of elements
46 | * @return whether this test is suitable for pre-sorted input
47 | */
48 | default boolean isSuitableForInputSize(int size) {
49 | return true;
50 | }
51 |
52 | /**
53 | * Indicates whether this algorithm supports counting operations.
54 | *
55 | * @return whether this algorithm supports counting operations
56 | */
57 | default boolean supportsCounting() {
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/eu/happycoders/sort/method/SortTest.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4 | import static org.junit.jupiter.api.Assertions.assertThrows;
5 |
6 | import eu.happycoders.sort.utils.ArrayUtils;
7 | import eu.happycoders.sort.utils.NotImplementedException;
8 | import java.util.Arrays;
9 | import java.util.concurrent.ThreadLocalRandom;
10 | import java.util.function.Function;
11 | import org.junit.jupiter.api.RepeatedTest;
12 | import org.junit.jupiter.api.Test;
13 |
14 | public abstract class SortTest {
15 |
16 | @RepeatedTest(100)
17 | void sort_randomNumbers_sorted() {
18 | sortAndTestIfSorted(ArrayUtils::createRandomArray);
19 | }
20 |
21 | @RepeatedTest(100)
22 | void sort_sortedNumbers_sorted() {
23 | sortAndTestIfSorted(ArrayUtils::createSortedArray);
24 | }
25 |
26 | @RepeatedTest(100)
27 | void sort_reverseNumbers_sorted() {
28 | sortAndTestIfSorted(ArrayUtils::createReversedArray);
29 | }
30 |
31 | protected void sortAndTestIfSorted(Function For simplicity, this implementation allows only elements >= 0.
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class CountingSortSimple implements SortAlgorithm {
14 |
15 | @Override
16 | public void sort(int[] elements) {
17 | int maxValue = findMax(elements);
18 | int[] counts = new int[maxValue + 1];
19 |
20 | // Phase 1: Count
21 | for (int element : elements) {
22 | counts[element]++;
23 | }
24 |
25 | // Phase 2: Write results back
26 | int targetPos = 0;
27 | for (int i = 0; i < counts.length; i++) {
28 | for (int j = 0; j < counts[i]; j++) {
29 | elements[targetPos++] = i;
30 | }
31 | }
32 | }
33 |
34 | private int findMax(int[] elements) {
35 | int max = 0;
36 | for (int element : elements) {
37 | if (element < 0) {
38 | throw new IllegalArgumentException("This implementation does not support negative values.");
39 | }
40 | if (element > max) {
41 | max = element;
42 | }
43 | }
44 | return max;
45 | }
46 |
47 | @Override
48 | public void sortWithCounters(int[] elements, Counters counters) {
49 | int maxValue = findMax(elements);
50 |
51 | int length = elements.length;
52 | counters.addReads(length);
53 | counters.addIterations(length);
54 | counters.addComparisons(length);
55 |
56 | int[] counts = new int[maxValue + 1];
57 |
58 | // Phase 1: Count
59 | counters.addIterations(length);
60 | counters.addReads(length); // read elements[i]
61 | counters.addReadsAndWrites(length); // inc counts[...]
62 | for (int i = 0; i < length; i++) {
63 | counts[elements[i]]++;
64 | }
65 |
66 | // Phase 2: Write results back
67 | Counters countersPhase2 = counters.getPhase2();
68 | int targetPos = 0;
69 | countersPhase2.addIterations(counts.length); // outer
70 | countersPhase2.addIterations(length); // all inner combined
71 | countersPhase2.addReads(counts.length); // read counts[i]
72 | countersPhase2.addWrites(length); // write elements[targetPos++]
73 | for (int i = 0; i < counts.length; i++) {
74 | for (int j = 0; j < counts[i]; j++) {
75 | elements[targetPos++] = i;
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/comparisons/CompareRadixSorts.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.comparisons;
2 |
3 | import eu.happycoders.sort.method.SortAlgorithm;
4 | import eu.happycoders.sort.method.radixsort.RadixSortWithArrays;
5 | import eu.happycoders.sort.method.radixsort.RadixSortWithArraysAndCustomBase;
6 | import eu.happycoders.sort.method.radixsort.RadixSortWithCountingSort;
7 | import eu.happycoders.sort.method.radixsort.RadixSortWithCountingSortAndCustomBase;
8 | import eu.happycoders.sort.method.radixsort.RadixSortWithDynamicLists;
9 | import eu.happycoders.sort.method.radixsort.RadixSortWithDynamicListsAndCustomBase;
10 | import eu.happycoders.sort.method.radixsort.RecursiveMsdRadixSortWithArrays;
11 | import eu.happycoders.sort.method.radixsort.RecursiveMsdRadixSortWithArraysAndCustomBase;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.function.IntFunction;
15 |
16 | /**
17 | * Compares the various Radix Sort algorithm variants.
18 | *
19 | * @author Sven Woltmann
20 | */
21 | public class CompareRadixSorts extends DirectComparison {
22 |
23 | private static final int SIZE = 5_555_555;
24 | private static final int MAX_BASE = 1 << 20;
25 |
26 | public static void main(String[] args) {
27 | new CompareRadixSorts().run();
28 | }
29 |
30 | private void run() {
31 | List Not thread-safe!
73 | *
74 | * @return the second set of counters
75 | */
76 | @SuppressFBWarnings("EI_EXPOSE_REP") // We're intentionally exposing the "phase2" object
77 | public Counters getPhase2() {
78 | if (phase2 == null) {
79 | phase2 = new Counters();
80 | }
81 | return phase2;
82 | }
83 |
84 | @Override
85 | public String toString() {
86 | String result =
87 | String.format(
88 | Locale.US,
89 | "iterations = %,11d, comparisons = %,11d, "
90 | + "reads = %,11d, writes = %,11d, var.assignments = %,11d",
91 | iterations,
92 | comparisons,
93 | reads,
94 | writes,
95 | localVariableAssignments);
96 | if (phase2 != null) {
97 | result += "; Phase2: " + phase2.toString();
98 | }
99 | return result;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/results/2020-05-30/Test_Results_Selection_Sort.txt:
--------------------------------------------------------------------------------
1 | --- Results for iteration 50 for: SelectionSort (order: random) ---
2 | SelectionSort/random/1024 -> fastest: 0.155 ms, median: 0.162 ms
3 | SelectionSort/random/2048 -> fastest: 0.512 ms, median: 0.530 ms
4 | SelectionSort/random/4096 -> fastest: 1.825 ms, median: 1.882 ms
5 | SelectionSort/random/8192 -> fastest: 6.847 ms, median: 7.114 ms
6 | SelectionSort/random/16384 -> fastest: 27.239 ms, median: 27.888 ms
7 | SelectionSort/random/32768 -> fastest: 106.386 ms, median: 107.985 ms
8 | SelectionSort/random/65536 -> fastest: 425.302 ms, median: 434.026 ms
9 | SelectionSort/random/131072 -> fastest: 1,705.741 ms, median: 1,729.812 ms
10 | SelectionSort/random/262144 -> fastest: 6,843.655 ms, median: 6,913.384 ms
11 | SelectionSort/random/524288 -> fastest: 27,414.730 ms, median: 27,649.758 ms
12 |
13 | --- Results for iteration 50 for: SelectionSort (order: ascending) ---
14 | SelectionSort/ascending/1024 -> fastest: 0.111 ms, median: 0.115 ms
15 | SelectionSort/ascending/2048 -> fastest: 0.418 ms, median: 0.426 ms
16 | SelectionSort/ascending/4096 -> fastest: 1.605 ms, median: 1.645 ms
17 | SelectionSort/ascending/8192 -> fastest: 6.352 ms, median: 6.611 ms
18 | SelectionSort/ascending/16384 -> fastest: 26.036 ms, median: 26.768 ms
19 | SelectionSort/ascending/32768 -> fastest: 103.999 ms, median: 105.355 ms
20 | SelectionSort/ascending/65536 -> fastest: 417.469 ms, median: 424.329 ms
21 | SelectionSort/ascending/131072 -> fastest: 1,694.837 ms, median: 1,714.145 ms
22 | SelectionSort/ascending/262144 -> fastest: 6,820.222 ms, median: 6,880.154 ms
23 | SelectionSort/ascending/524288 -> fastest: 27,320.116 ms, median: 27,568.692 ms
24 |
25 | --- Results for iteration 50 for: SelectionSort (order: descending) ---
26 | SelectionSort/descending/1024 -> fastest: 0.265 ms, median: 0.279 ms
27 | SelectionSort/descending/2048 -> fastest: 1.006 ms, median: 1.063 ms
28 | SelectionSort/descending/4096 -> fastest: 3.976 ms, median: 4.184 ms
29 | SelectionSort/descending/8192 -> fastest: 16.022 ms, median: 16.522 ms
30 | SelectionSort/descending/16384 -> fastest: 65.071 ms, median: 65.647 ms
31 | SelectionSort/descending/32768 -> fastest: 260.293 ms, median: 265.407 ms
32 | SelectionSort/descending/65536 -> fastest: 1,043.764 ms, median: 1,052.185 ms
33 | SelectionSort/descending/131072 -> fastest: 4,172.432 ms, median: 4,209.876 ms
34 | SelectionSort/descending/262144 -> fastest: 16,787.269 ms, median: 16,863.748 ms
35 | SelectionSort/descending/524288 -> fastest: 67,219.754 ms, median: 67,537.755 ms
36 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/CountOperations.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 | import eu.happycoders.sort.utils.ArrayUtils;
6 | import java.util.Locale;
7 | import java.util.function.IntFunction;
8 |
9 | /**
10 | * Measures the performance of all sorting algorithms for various input sizes.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | public class CountOperations {
15 |
16 | private static final int MIN_SORTING_SIZE = 1 << 3;
17 | private static final int MAX_SORTING_SIZE = 1 << 26;
18 |
19 | // Stop when counting takes longer than 20 seconds
20 | private static final int MAX_COUNTING_TIME_SECS = 20;
21 |
22 | public static void main(String[] args) {
23 | new CountOperations().run();
24 | }
25 |
26 | private void run() {
27 | for (SortAlgorithm algorithm : UltimateTest.ALGORITHMS) {
28 | if (algorithm.supportsCounting()) {
29 | countOps(algorithm);
30 | }
31 | }
32 | }
33 |
34 | private void countOps(SortAlgorithm algorithm) {
35 | // Test with a random, a sorted, and a reversed (= sorted descending) array
36 | countOps(algorithm, false, "random", ArrayUtils::createRandomArray);
37 | countOps(algorithm, true, "ascending", ArrayUtils::createSortedArray);
38 | countOps(algorithm, true, "descending", ArrayUtils::createReversedArray);
39 | }
40 |
41 | @SuppressWarnings({"PMD.SystemPrintln", "java:S106"})
42 | private void countOps(
43 | SortAlgorithm algorithm,
44 | boolean sorted,
45 | String inputOrder,
46 | IntFunction For simplicity, this implementation allows only elements >= 0.
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class CountingSort implements SortAlgorithm {
14 |
15 | private static final int MAX_VALUE_TO_SORT = Integer.MAX_VALUE / 2;
16 | private static final int MIN_VALUE_TO_SORT = Integer.MIN_VALUE / 2;
17 |
18 | @Override
19 | public void sort(int[] elements) {
20 | Boundaries boundaries = findBoundaries(elements);
21 | int[] counts = new int[boundaries.max - boundaries.min + 1];
22 |
23 | // Phase 1: Count
24 | for (int element : elements) {
25 | counts[element - boundaries.min]++;
26 | }
27 |
28 | // Phase 2: Write results back
29 | int targetPos = 0;
30 | for (int i = 0; i < counts.length; i++) {
31 | for (int j = 0; j < counts[i]; j++) {
32 | elements[targetPos++] = i + boundaries.min;
33 | }
34 | }
35 | }
36 |
37 | private Boundaries findBoundaries(int[] elements) {
38 | int min = Integer.MAX_VALUE;
39 | int max = Integer.MIN_VALUE;
40 | for (int element : elements) {
41 | if (element > MAX_VALUE_TO_SORT) {
42 | throw new IllegalArgumentException(
43 | "Element " + element + " is greater than maximum " + MAX_VALUE_TO_SORT);
44 | }
45 | if (element < MIN_VALUE_TO_SORT) {
46 | throw new IllegalArgumentException(
47 | "Element " + element + " is less than minimum " + MIN_VALUE_TO_SORT);
48 | }
49 | if (element > max) {
50 | max = element;
51 | }
52 | if (element < min) {
53 | min = element;
54 | }
55 | }
56 | return new Boundaries(min, max);
57 | }
58 |
59 | private static class Boundaries {
60 | private final int min;
61 | private final int max;
62 |
63 | public Boundaries(int min, int max) {
64 | this.min = min;
65 | this.max = max;
66 | }
67 | }
68 |
69 | @Override
70 | public void sortWithCounters(int[] elements, Counters counters) {
71 | Boundaries boundaries = findBoundaries(elements);
72 |
73 | int length = elements.length;
74 | counters.addReads(length);
75 | counters.addIterations(length);
76 | counters.addComparisons(length);
77 |
78 | int[] counts = new int[boundaries.max - boundaries.min + 1];
79 |
80 | // Phase 1: Count
81 | counters.addIterations(length);
82 | counters.addReads(length); // read elements[i]
83 | counters.addReadsAndWrites(length); // inc counts[...]
84 | for (int i = 0; i < length; i++) {
85 | counts[elements[i] - boundaries.min]++;
86 | }
87 |
88 | // Phase 2: Write results back
89 | int targetPos = 0;
90 | counters.addIterations(counts.length); // outer
91 | counters.addIterations(length); // all inner combined
92 | counters.addReads(counts.length); // read counts[i]
93 | counters.addWrites(length); // write elements[targetPos++]
94 | for (int i = 0; i < counts.length; i++) {
95 | for (int j = 0; j < counts[i]; j++) {
96 | elements[targetPos++] = i + boundaries.min;
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/radixsort/RadixSortWithArrays.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.radixsort;
2 |
3 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.calculateDivisor;
4 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.checkIfContainsNegatives;
5 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.getNumberOfDigits;
6 | import static eu.happycoders.sort.utils.ArrayUtils.getMaximum;
7 |
8 | import eu.happycoders.sort.method.Counters;
9 | import eu.happycoders.sort.method.SortAlgorithm;
10 | import eu.happycoders.sort.utils.NotImplementedException;
11 |
12 | /**
13 | * Radix Sort implementation using arrays as buckets.
14 | *
15 | * @author Sven Woltmann
16 | */
17 | public class RadixSortWithArrays implements SortAlgorithm {
18 |
19 | @Override
20 | public void sort(int[] elements) {
21 | checkIfContainsNegatives(elements);
22 | int max = getMaximum(elements);
23 | int numberOfDigits = getNumberOfDigits(max);
24 |
25 | for (int digitIndex = 0; digitIndex < numberOfDigits; digitIndex++) {
26 | sortByDigit(elements, digitIndex);
27 | }
28 | }
29 |
30 | private void sortByDigit(int[] elements, int digitIndex) {
31 | Bucket[] buckets = partition(elements, digitIndex);
32 | collect(buckets, elements);
33 | }
34 |
35 | private Bucket[] partition(int[] elements, int digitIndex) {
36 | int[] counts = countDigits(elements, digitIndex);
37 | Bucket[] buckets = createBuckets(counts);
38 | distributeToBuckets(elements, digitIndex, buckets);
39 | return buckets;
40 | }
41 |
42 | private int[] countDigits(int[] elements, int digitIndex) {
43 | int[] counts = new int[10];
44 | int divisor = calculateDivisor(digitIndex);
45 | for (int element : elements) {
46 | int digit = element / divisor % 10;
47 | counts[digit]++;
48 | }
49 | return counts;
50 | }
51 |
52 | private Bucket[] createBuckets(int[] counts) {
53 | Bucket[] buckets = new Bucket[10];
54 | for (int i = 0; i < 10; i++) {
55 | buckets[i] = new Bucket(counts[i]);
56 | }
57 | return buckets;
58 | }
59 |
60 | private void distributeToBuckets(int[] elements, int digitIndex, Bucket[] buckets) {
61 | int divisor = calculateDivisor(digitIndex);
62 |
63 | for (int element : elements) {
64 | int digit = element / divisor % 10;
65 | buckets[digit].add(element);
66 | }
67 | }
68 |
69 | private void collect(Bucket[] buckets, int[] elements) {
70 | int targetIndex = 0;
71 | for (Bucket bucket : buckets) {
72 | for (int element : bucket.getElements()) {
73 | elements[targetIndex] = element;
74 | targetIndex++;
75 | }
76 | }
77 | }
78 |
79 | @Override
80 | public void sortWithCounters(int[] elements, Counters counters) {
81 | throw new NotImplementedException();
82 | }
83 |
84 | @Override
85 | public boolean supportsCounting() {
86 | return false;
87 | }
88 |
89 | private static class Bucket {
90 | private final int[] elements;
91 | private int addIndex;
92 |
93 | private Bucket(int size) {
94 | elements = new int[size];
95 | }
96 |
97 | private void add(int element) {
98 | elements[addIndex] = element;
99 | addIndex++;
100 | }
101 |
102 | private int[] getElements() {
103 | return elements;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/results/2020-05-30/Test_Results_Insertion_Sort.txt:
--------------------------------------------------------------------------------
1 | --- Results for iteration 50 for: InsertionSort (order: random) ---
2 | InsertionSort/random/1024 -> fastest: 0.088 ms, median: 0.095 ms
3 | InsertionSort/random/2048 -> fastest: 0.339 ms, median: 0.355 ms
4 | InsertionSort/random/4096 -> fastest: 1.323 ms, median: 1.378 ms
5 | InsertionSort/random/8192 -> fastest: 5.292 ms, median: 5.494 ms
6 | InsertionSort/random/16384 -> fastest: 21.349 ms, median: 21.890 ms
7 | InsertionSort/random/32768 -> fastest: 85.546 ms, median: 87.859 ms
8 | InsertionSort/random/65536 -> fastest: 343.144 ms, median: 350.428 ms
9 | InsertionSort/random/131072 -> fastest: 1,378.011 ms, median: 1,398.919 ms
10 | InsertionSort/random/262144 -> fastest: 5,636.412 ms, median: 5,706.817 ms
11 | InsertionSort/random/524288 -> fastest: 22,749.327 ms, median: 23,009.682 ms
12 |
13 | --- Results for iteration 50 for: InsertionSort (order: ascending) ---
14 | InsertionSort/ascending/1024 -> fastest: 0.002 ms, median: 0.002 ms
15 | InsertionSort/ascending/2048 -> fastest: 0.003 ms, median: 0.003 ms
16 | InsertionSort/ascending/4096 -> fastest: 0.005 ms, median: 0.006 ms
17 | InsertionSort/ascending/8192 -> fastest: 0.011 ms, median: 0.011 ms
18 | InsertionSort/ascending/16384 -> fastest: 0.021 ms, median: 0.021 ms
19 | InsertionSort/ascending/32768 -> fastest: 0.041 ms, median: 0.042 ms
20 | InsertionSort/ascending/65536 -> fastest: 0.082 ms, median: 0.084 ms
21 | InsertionSort/ascending/131072 -> fastest: 0.163 ms, median: 0.168 ms
22 | InsertionSort/ascending/262144 -> fastest: 0.331 ms, median: 0.351 ms
23 | InsertionSort/ascending/524288 -> fastest: 0.670 ms, median: 0.710 ms
24 | InsertionSort/ascending/1048576 -> fastest: 1.334 ms, median: 1.419 ms
25 | InsertionSort/ascending/2097152 -> fastest: 2.667 ms, median: 2.811 ms
26 | InsertionSort/ascending/4194304 -> fastest: 5.343 ms, median: 5.621 ms
27 | InsertionSort/ascending/8388608 -> fastest: 10.745 ms, median: 11.190 ms
28 | InsertionSort/ascending/16777216 -> fastest: 21.748 ms, median: 22.290 ms
29 | InsertionSort/ascending/33554432 -> fastest: 43.500 ms, median: 44.455 ms
30 | InsertionSort/ascending/67108864 -> fastest: 86.143 ms, median: 87.079 ms
31 | InsertionSort/ascending/134217728 -> fastest: 172.431 ms, median: 173.980 ms
32 | InsertionSort/ascending/268435456 -> fastest: 343.419 ms, median: 346.236 ms
33 | InsertionSort/ascending/536870912 -> fastest: 688.349 ms, median: 693.310 ms
34 |
35 | --- Results for iteration 50 for: InsertionSort (order: descending) ---
36 | InsertionSort/descending/1024 -> fastest: 0.177 ms, median: 0.178 ms
37 | InsertionSort/descending/2048 -> fastest: 0.679 ms, median: 0.690 ms
38 | InsertionSort/descending/4096 -> fastest: 2.676 ms, median: 2.704 ms
39 | InsertionSort/descending/8192 -> fastest: 10.629 ms, median: 10.858 ms
40 | InsertionSort/descending/16384 -> fastest: 42.709 ms, median: 43.609 ms
41 | InsertionSort/descending/32768 -> fastest: 171.607 ms, median: 175.800 ms
42 | InsertionSort/descending/65536 -> fastest: 684.408 ms, median: 697.590 ms
43 | InsertionSort/descending/131072 -> fastest: 2,807.428 ms, median: 2,839.991 ms
44 | InsertionSort/descending/262144 -> fastest: 11,379.433 ms, median: 11,517.355 ms
45 | InsertionSort/descending/524288 -> fastest: 45,939.779 ms, median: 46,309.272 ms
46 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/mergesort/NaturalMergeSort.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.mergesort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 | import eu.happycoders.sort.utils.NotImplementedException;
6 |
7 | /**
8 | * Natural merge sort implementation for performance tests.
9 | *
10 | * @author Sven Woltmann
11 | */
12 | public class NaturalMergeSort implements SortAlgorithm {
13 |
14 | @Override
15 | public void sort(int[] elements) {
16 | int numElements = elements.length;
17 |
18 | int[] tmp = new int[numElements];
19 | int[] starts = new int[numElements + 1];
20 |
21 | // Step 1: identify runs
22 | int runCount = 0;
23 | starts[0] = 0;
24 | for (int i = 1; i <= numElements; i++) {
25 | if (i == numElements || elements[i] < elements[i - 1]) {
26 | starts[++runCount] = i;
27 | }
28 | }
29 |
30 | // Step 2: merge runs, until only 1 run is left
31 | int[] from = elements;
32 | int[] to = tmp;
33 |
34 | while (runCount > 1) {
35 | int newRunCount = 0;
36 |
37 | // Merge two runs each
38 | for (int i = 0; i < runCount - 1; i += 2) {
39 | merge(from, to, starts[i], starts[i + 1], starts[i + 2]);
40 | starts[newRunCount++] = starts[i];
41 | }
42 |
43 | // Odd number of runs? Copy the last one
44 | if (isOdd(runCount)) {
45 | int lastStart = starts[runCount - 1];
46 | System.arraycopy(from, lastStart, to, lastStart, numElements - lastStart);
47 | starts[newRunCount++] = lastStart;
48 | }
49 |
50 | // Prepare for next round...
51 | starts[newRunCount] = numElements;
52 | runCount = newRunCount;
53 |
54 | // Swap "from" and "to" arrays
55 | int[] help = from;
56 | from = to;
57 | to = help;
58 | }
59 |
60 | // If final run is not in "elements", copy it there
61 | if (isNotSameArray(from, elements)) {
62 | System.arraycopy(from, 0, elements, 0, numElements);
63 | }
64 | }
65 |
66 | private void merge(int[] source, int[] target, int startLeft, int startRight, int endRight) {
67 | int leftPos = startLeft;
68 | int rightPos = startRight;
69 | int targetPos = startLeft;
70 |
71 | // As long as both arrays contain elements...
72 | while (leftPos < startRight && rightPos < endRight) {
73 | // Which one is smaller?
74 | int leftValue = source[leftPos];
75 | int rightValue = source[rightPos];
76 | if (leftValue <= rightValue) {
77 | target[targetPos++] = leftValue;
78 | leftPos++;
79 | } else {
80 | target[targetPos++] = rightValue;
81 | rightPos++;
82 | }
83 | }
84 | // Copy the rest
85 | while (leftPos < startRight) {
86 | target[targetPos++] = source[leftPos++];
87 | }
88 | while (rightPos < endRight) {
89 | target[targetPos++] = source[rightPos++];
90 | }
91 | }
92 |
93 | private boolean isOdd(int number) {
94 | return number % 2 != 0;
95 | }
96 |
97 | @SuppressWarnings("PMD.CompareObjectsWithEquals") // We want to know if it's the same instance!
98 | private boolean isNotSameArray(int[] array1, int[] array2) {
99 | return array1 != array2;
100 | }
101 |
102 | @Override
103 | public void sortWithCounters(int[] elements, Counters counters) {
104 | throw new NotImplementedException();
105 | }
106 |
107 | @Override
108 | public boolean supportsCounting() {
109 | return false;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/quicksort/DualPivotQuicksortImproved.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.quicksort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.InsertionSort;
5 | import eu.happycoders.sort.method.SortAlgorithm;
6 | import eu.happycoders.sort.method.quicksort.DualPivotQuicksort.PivotStrategy;
7 |
8 | /**
9 | * Dual-pivot Quicksort implementation for performance tests.
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class DualPivotQuicksortImproved implements SortAlgorithm {
14 |
15 | private final int threshold;
16 | private final PivotStrategy pivotStrategy; // just for the getName() method
17 | private final DualPivotQuicksort standardQuicksort;
18 | private final InsertionSort insertionSort;
19 |
20 | /**
21 | * Constructs the Dual-pivot Quicksort instance.
22 | *
23 | * @param threshold when the array to be sorted is not longer than this threshold, the algorithm
24 | * switches to Insertion Sort
25 | * @param pivotStrategy the pivot strategy to use
26 | */
27 | public DualPivotQuicksortImproved(int threshold, PivotStrategy pivotStrategy) {
28 | this.threshold = threshold;
29 | this.pivotStrategy = pivotStrategy;
30 | this.standardQuicksort = new DualPivotQuicksort(pivotStrategy);
31 | this.insertionSort = new InsertionSort();
32 | }
33 |
34 | @Override
35 | public String getName() {
36 | return this.getClass().getSimpleName()
37 | + "(threshold: "
38 | + threshold
39 | + ", pivot: "
40 | + pivotStrategy
41 | + ")";
42 | }
43 |
44 | @Override
45 | public void sort(int[] elements) {
46 | quicksort(elements, 0, elements.length - 1);
47 | }
48 |
49 | private void quicksort(int[] elements, int left, int right) {
50 | // End of recursion reached?
51 | if (left >= right) {
52 | return;
53 | }
54 |
55 | // Threshold for insertion sort reached?
56 | if (right - left < threshold) {
57 | insertionSort.sort(elements, left, right + 1);
58 | return;
59 | }
60 |
61 | int[] pivotPos = standardQuicksort.partition(elements, left, right);
62 | int p0 = pivotPos[0];
63 | int p1 = pivotPos[1];
64 | quicksort(elements, left, p0 - 1);
65 | quicksort(elements, p0 + 1, p1 - 1);
66 | quicksort(elements, p1 + 1, right);
67 | }
68 |
69 | @Override
70 | public void sortWithCounters(int[] elements, Counters counters) {
71 | quicksortWithCounters(elements, 0, elements.length - 1, counters);
72 | }
73 |
74 | private void quicksortWithCounters(int[] elements, int left, int right, Counters counters) {
75 | // End of recursion reached?
76 | if (left >= right) {
77 | return;
78 | }
79 |
80 | // Threshold for insertion sort reached?
81 | if (right - left < threshold) {
82 | insertionSort.sortWithCounters(elements, left, right + 1, counters);
83 | return;
84 | }
85 |
86 | int[] pivotPos = standardQuicksort.partitionWithCounters(elements, left, right, counters);
87 | int p0 = pivotPos[0];
88 | int p1 = pivotPos[1];
89 | quicksortWithCounters(elements, left, p0 - 1, counters);
90 | quicksortWithCounters(elements, p0 + 1, p1 - 1, counters);
91 | quicksortWithCounters(elements, p1 + 1, right, counters);
92 | }
93 |
94 | @Override
95 | public boolean isSuitableForInputSize(int size) {
96 | return standardQuicksort.isSuitableForInputSize(size);
97 | }
98 |
99 | @Override
100 | public boolean isSuitableForSortedInput(int size) {
101 | return standardQuicksort.isSuitableForSortedInput(size);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/demos/pivot/PivotComparator.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.demos.pivot;
2 |
3 | import eu.happycoders.sort.demos.Scorecard;
4 | import eu.happycoders.sort.method.PartitioningAlgorithm;
5 | import eu.happycoders.sort.method.SortAlgorithm;
6 | import eu.happycoders.sort.method.quicksort.PivotStrategy;
7 | import eu.happycoders.sort.method.quicksort.QuicksortVariant1;
8 | import eu.happycoders.sort.utils.ArrayUtils;
9 | import java.util.HashMap;
10 | import java.util.Locale;
11 | import java.util.Map;
12 | import java.util.concurrent.ThreadLocalRandom;
13 |
14 | /**
15 | * Compares several pivot strategies: in how many samples will we have a specific distribution of
16 | * elements (1.5:1, 2:1, 3:1) or better?
17 | *
18 | * @author Sven Woltmann
19 | */
20 | public class PivotComparator {
21 |
22 | private static final int ITERATIONS = 500_000;
23 | private static final int MIN_SIZE = 500;
24 | private static final int MAX_SIZE = 1_000;
25 |
26 | @SuppressWarnings("PMD.UseConcurrentHashMap") // Not accessed concurrently
27 | private final Map This implementation has a space complexity of O(1), however its time complexity is O(n² log n)
10 | * due to the two nested loops in the merge() method.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | // We don't want to use System.arraycopy - we want to demonstrate how the algorithm works
15 | @SuppressWarnings("PMD.AvoidArrayLoops")
16 | public class InPlaceMergeSort implements SortAlgorithm {
17 |
18 | @Override
19 | public void sort(int[] elements) {
20 | int length = elements.length;
21 | mergeSort(elements, 0, length - 1);
22 | }
23 |
24 | private void mergeSort(int[] elements, int left, int right) {
25 | // End of recursion reached?
26 | if (left == right) {
27 | return;
28 | }
29 |
30 | int middle = left + (right - left) / 2;
31 | mergeSort(elements, left, middle);
32 | mergeSort(elements, middle + 1, right);
33 | merge(elements, left, middle + 1, right);
34 | }
35 |
36 | void merge(int[] elements, int leftPos, int rightPos, int rightEnd) {
37 | int leftEnd = rightPos - 1;
38 |
39 | while (leftPos <= leftEnd && rightPos <= rightEnd) {
40 | // Which one is smaller?
41 | int leftValue = elements[leftPos];
42 | int rightValue = elements[rightPos];
43 | if (leftValue <= rightValue) {
44 | leftPos++;
45 | } else {
46 | // Move all the elements from leftPos to excluding rightPos one field
47 | // to the right
48 | int movePos = rightPos;
49 | while (movePos > leftPos) {
50 | elements[movePos] = elements[movePos - 1];
51 | movePos--;
52 | }
53 | elements[leftPos] = rightValue;
54 | leftPos++;
55 | leftEnd++;
56 | rightPos++;
57 | }
58 | }
59 | }
60 |
61 | @Override
62 | public void sortWithCounters(int[] elements, Counters counters) {
63 | int length = elements.length;
64 | mergeSortWithCounters(elements, 0, length - 1, counters);
65 | }
66 |
67 | private void mergeSortWithCounters(int[] elements, int left, int right, Counters counters) {
68 | // End of recursion reached?
69 | if (left == right) {
70 | return;
71 | }
72 |
73 | int middle = left + (right - left) / 2;
74 | mergeSortWithCounters(elements, left, middle, counters);
75 | mergeSortWithCounters(elements, middle + 1, right, counters);
76 | mergeWithCounters(elements, left, middle + 1, right, counters);
77 | }
78 |
79 | private void mergeWithCounters(
80 | int[] elements, int leftPos, int rightPos, int rightEnd, Counters counters) {
81 | int leftEnd = rightPos - 1;
82 |
83 | while (isLessThanOrEqual(leftPos, leftEnd, counters)
84 | && isLessThanOrEqual(rightPos, rightEnd, counters)) {
85 | // Which one is smaller?
86 | int leftValue = elements[leftPos];
87 | int rightValue = elements[rightPos];
88 | counters.addReads(2);
89 |
90 | counters.incComparisons();
91 | if (leftValue <= rightValue) {
92 | leftPos++;
93 | } else {
94 | // Move all the elements from leftPos to excluding rightPos one field
95 | // to the right
96 | int movePos = rightPos;
97 | while (isGreaterThan(movePos, leftPos, counters)) {
98 | counters.incReadsAndWrites();
99 | elements[movePos] = elements[movePos - 1];
100 | movePos--;
101 | }
102 | counters.incWrites();
103 | elements[leftPos] = rightValue;
104 | leftPos++;
105 | leftEnd++;
106 | rightPos++;
107 | }
108 | }
109 | }
110 |
111 | private boolean isLessThanOrEqual(int a, int b, Counters counters) {
112 | counters.incComparisons();
113 | return a <= b;
114 | }
115 |
116 | private boolean isGreaterThan(int a, int b, Counters counters) {
117 | counters.incComparisons();
118 | return a > b;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/bubblesort/BubbleSortParallel.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.bubblesort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 | import eu.happycoders.sort.utils.NotImplementedException;
6 | import java.util.concurrent.Phaser;
7 | import java.util.concurrent.atomic.AtomicInteger;
8 |
9 | /**
10 | * Abstract base class for parallel Bubble Sort implementations using partitions.
11 | *
12 | * @author Sven Woltmann
13 | */
14 | public abstract class BubbleSortParallel implements SortAlgorithm {
15 |
16 | @Override
17 | public void sort(int[] elements) {
18 | AtomicInteger lastSwappedInRound = new AtomicInteger();
19 |
20 | int[] startPositions = partition(elements);
21 | int numThreads = startPositions.length - 1;
22 |
23 | Phaser phaser = new Phaser(numThreads);
24 | Thread[] threads = new Thread[numThreads];
25 | for (int i = 0; i < numThreads; i++) {
26 | int startPos = startPositions[i];
27 | int endPos = startPositions[i + 1];
28 | threads[i] = createThread(elements, startPos, endPos, lastSwappedInRound, phaser);
29 | }
30 |
31 | for (int i = 0; i < numThreads; i++) {
32 | threads[i].start();
33 | }
34 |
35 | for (int i = 0; i < numThreads; i++) {
36 | try {
37 | threads[i].join();
38 | } catch (InterruptedException e) {
39 | Thread.currentThread().interrupt();
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * Partitions the elements.
46 | *
47 | * @param elements the elements
48 | * @return an array of start positions; the array's length is one more than the number of
49 | * partitions; the last element contains the length of the array.
50 | */
51 | private int[] partition(int[] elements) {
52 | int numPartitions = Math.min(Runtime.getRuntime().availableProcessors(), elements.length / 2);
53 | int remainingElements = elements.length;
54 | int remainingPartitions = numPartitions;
55 | int[] startPositions = new int[numPartitions + 1];
56 | for (int i = 0; i < numPartitions; i++) {
57 | int partitionSize = remainingElements / remainingPartitions;
58 | if (isOdd(partitionSize) && startPositions[i] + partitionSize < elements.length) {
59 | partitionSize++;
60 | }
61 |
62 | remainingElements -= partitionSize;
63 | remainingPartitions--;
64 | startPositions[i + 1] = startPositions[i] + partitionSize;
65 | }
66 | return startPositions;
67 | }
68 |
69 | private boolean isOdd(int number) {
70 | return number % 2 != 0;
71 | }
72 |
73 | private Thread createThread(
74 | int[] elements, int startPos, int endPos, AtomicInteger lastSwappedInRound, Phaser phaser) {
75 | return new Thread(
76 | () -> {
77 | for (int round = 1; ; round++) {
78 | phaser.arriveAndAwaitAdvance();
79 |
80 | boolean swapped = sortPartition(elements, startPos, endPos, false);
81 |
82 | phaser.arriveAndAwaitAdvance();
83 |
84 | swapped |= sortPartition(elements, startPos, endPos, true);
85 | if (swapped) {
86 | lastSwappedInRound.set(round);
87 | }
88 |
89 | phaser.arriveAndAwaitAdvance();
90 |
91 | if (lastSwappedInRound.get() < round) {
92 | break;
93 | }
94 | }
95 | });
96 | }
97 |
98 | /**
99 | * Sorts a partition of the elements.
100 | *
101 | * @param elements the elements
102 | * @param startPos the partition's start position within the elements
103 | * @param endPos the partition's end position within the elements
104 | * @param even whether it's the even or odd step of an iteration
105 | * @return whether any elements were swapped
106 | */
107 | abstract boolean sortPartition(int[] elements, int startPos, int endPos, boolean even);
108 |
109 | @Override
110 | public void sortWithCounters(int[] elements, Counters counters) {
111 | throw new NotImplementedException();
112 | }
113 |
114 | @Override
115 | public boolean supportsCounting() {
116 | return false;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/radixsort/RecursiveMsdRadixSortWithArraysAndCustomBase.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.radixsort;
2 |
3 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.calculateDivisor;
4 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.checkIfContainsNegatives;
5 | import static eu.happycoders.sort.method.radixsort.RadixSortHelper.getNumberOfDigits;
6 | import static eu.happycoders.sort.utils.ArrayUtils.getMaximum;
7 |
8 | import eu.happycoders.sort.method.Counters;
9 | import eu.happycoders.sort.method.SortAlgorithm;
10 | import eu.happycoders.sort.utils.NotImplementedException;
11 |
12 | /**
13 | * Recursive MSD (most significant digit) Radix Sort implementation using arrays as buckets and with
14 | * a customizable base.
15 | *
16 | * @author Sven Woltmann
17 | */
18 | public class RecursiveMsdRadixSortWithArraysAndCustomBase implements SortAlgorithm {
19 |
20 | private final int base;
21 |
22 | public RecursiveMsdRadixSortWithArraysAndCustomBase(int base) {
23 | this.base = base;
24 | }
25 |
26 | @Override
27 | public void sort(int[] elements) {
28 | checkIfContainsNegatives(elements);
29 | int max = getMaximum(elements);
30 | int numberOfDigits = getNumberOfDigits(max, base);
31 |
32 | sortByDigit(elements, numberOfDigits - 1);
33 | }
34 |
35 | private void sortByDigit(int[] elements, int digitIndex) {
36 | Bucket[] buckets = partition(elements, digitIndex);
37 |
38 | // If we haven't reached the last digit, sort the buckets by the next digit, recursively
39 | if (digitIndex > 0) {
40 | for (Bucket bucket : buckets) {
41 | if (bucket.needsToBeSorted()) {
42 | sortByDigit(bucket.getElements(), digitIndex - 1);
43 | }
44 | }
45 | }
46 |
47 | collect(buckets, elements);
48 | }
49 |
50 | private Bucket[] partition(int[] elements, int digitIndex) {
51 | int[] counts = countDigits(elements, digitIndex);
52 | Bucket[] buckets = createBuckets(counts);
53 | distributeToBuckets(elements, digitIndex, buckets);
54 | return buckets;
55 | }
56 |
57 | private int[] countDigits(int[] elements, int digitIndex) {
58 | int[] counts = new int[base];
59 | int divisor = calculateDivisor(digitIndex, base);
60 | for (int element : elements) {
61 | int digit = element / divisor % base;
62 | counts[digit]++;
63 | }
64 | return counts;
65 | }
66 |
67 | private Bucket[] createBuckets(int[] counts) {
68 | Bucket[] buckets = new Bucket[base];
69 | for (int i = 0; i < base; i++) {
70 | buckets[i] = new Bucket(counts[i]);
71 | }
72 | return buckets;
73 | }
74 |
75 | private void distributeToBuckets(int[] elements, int digitIndex, Bucket[] buckets) {
76 | int divisor = calculateDivisor(digitIndex, base);
77 |
78 | for (int element : elements) {
79 | int digit = element / divisor % base;
80 | buckets[digit].add(element);
81 | }
82 | }
83 |
84 | private void collect(Bucket[] buckets, int[] elements) {
85 | int targetIndex = 0;
86 | for (Bucket bucket : buckets) {
87 | for (int element : bucket.getElements()) {
88 | elements[targetIndex] = element;
89 | targetIndex++;
90 | }
91 | }
92 | }
93 |
94 | @Override
95 | public void sortWithCounters(int[] elements, Counters counters) {
96 | throw new NotImplementedException();
97 | }
98 |
99 | @Override
100 | public String getName() {
101 | return this.getClass().getSimpleName() + "(" + base + ")";
102 | }
103 |
104 | @Override
105 | public boolean supportsCounting() {
106 | return false;
107 | }
108 |
109 | private static class Bucket {
110 | private final int[] elements;
111 | private int addIndex;
112 |
113 | private Bucket(int size) {
114 | elements = new int[size];
115 | }
116 |
117 | private void add(int element) {
118 | elements[addIndex] = element;
119 | addIndex++;
120 | }
121 |
122 | private int[] getElements() {
123 | return elements;
124 | }
125 |
126 | private boolean needsToBeSorted() {
127 | return elements.length > 1;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/ruleset.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | This implementation creates new arrays in the divide phase, so the additional space complexity
11 | * is O(n log n).
12 | *
13 | * @author Sven Woltmann
14 | */
15 | public class MergeSort3 implements SortAlgorithm {
16 |
17 | // Otherwise we run out of heap
18 | private static final int MAX_INPUT_SIZE = 1 << 28;
19 |
20 | @Override
21 | public void sort(int[] elements) {
22 | mergeSort(elements);
23 | }
24 |
25 | private void mergeSort(int[] elements) {
26 | // End of recursion reached?
27 | int length = elements.length;
28 | if (length == 1) {
29 | return;
30 | }
31 |
32 | int middle = length / 2;
33 |
34 | // Create left + right arrays
35 | int[] left = Arrays.copyOfRange(elements, 0, middle);
36 | int[] right = Arrays.copyOfRange(elements, middle, length);
37 |
38 | // Merge sort them
39 | mergeSort(left);
40 | mergeSort(right);
41 | merge(elements, left, right);
42 | }
43 |
44 | void merge(int[] target, int[] leftArray, int[] rightArray) {
45 | int leftLen = leftArray.length;
46 | int rightLen = rightArray.length;
47 |
48 | int targetPos = 0;
49 | int leftPos = 0;
50 | int rightPos = 0;
51 |
52 | // As long as both lists contain elements...
53 | while (leftPos < leftLen && rightPos < rightLen) {
54 | // Which one is smaller?
55 | int leftValue = leftArray[leftPos];
56 | int rightValue = rightArray[rightPos];
57 | if (leftValue <= rightValue) {
58 | target[targetPos++] = leftValue;
59 | leftPos++;
60 | } else {
61 | target[targetPos++] = rightValue;
62 | rightPos++;
63 | }
64 | }
65 | // Copying the rest
66 | while (leftPos < leftLen) {
67 | target[targetPos++] = leftArray[leftPos++];
68 | }
69 | while (rightPos < rightLen) {
70 | target[targetPos++] = rightArray[rightPos++];
71 | }
72 | }
73 |
74 | @Override
75 | public void sortWithCounters(int[] elements, Counters counters) {
76 | mergeSortWithCounters(elements, counters);
77 | }
78 |
79 | private void mergeSortWithCounters(int[] elements, Counters counters) {
80 | // End of recursion reached?
81 | int length = elements.length;
82 | if (length == 1) {
83 | return;
84 | }
85 |
86 | int middle = length / 2;
87 |
88 | // Create left + right arrays
89 | int[] left = Arrays.copyOfRange(elements, 0, middle);
90 | int[] right = Arrays.copyOfRange(elements, middle, length);
91 | counters.addReadsAndWrites(length);
92 |
93 | // Merge sort them
94 | mergeSortWithCounters(left, counters);
95 | mergeSortWithCounters(right, counters);
96 | mergeWithCounters(elements, left, right, counters);
97 | }
98 |
99 | void mergeWithCounters(int[] target, int[] leftArray, int[] rightArray, Counters counters) {
100 | int leftLen = leftArray.length;
101 | int rightLen = rightArray.length;
102 |
103 | int targetPos = 0;
104 | int leftPos = 0;
105 | int rightPos = 0;
106 |
107 | // As long as both lists contain elements...
108 | while (leftPos < leftLen && rightPos < rightLen) {
109 | counters.incIterations();
110 |
111 | // Which one is smaller?
112 | counters.addReads(2);
113 | int leftValue = leftArray[leftPos];
114 | int rightValue = rightArray[rightPos];
115 |
116 | counters.incComparisons();
117 | counters.incWrites();
118 | if (leftValue <= rightValue) {
119 | target[targetPos++] = leftValue;
120 | leftPos++;
121 | } else {
122 | target[targetPos++] = rightValue;
123 | rightPos++;
124 | }
125 | }
126 | // Copying the rest
127 | while (leftPos < leftLen) {
128 | counters.incIterations();
129 | target[targetPos++] = leftArray[leftPos++];
130 | counters.incReadsAndWrites();
131 | }
132 | while (rightPos < rightLen) {
133 | counters.incIterations();
134 | target[targetPos++] = rightArray[rightPos++];
135 | counters.incReadsAndWrites();
136 | }
137 | }
138 |
139 | @Override
140 | public boolean isSuitableForInputSize(int size) {
141 | return size <= MAX_INPUT_SIZE;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/quicksort/QuicksortSimple.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.quicksort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.PartitioningAlgorithm;
5 | import eu.happycoders.sort.method.SortAlgorithm;
6 | import eu.happycoders.sort.utils.ArrayUtils;
7 |
8 | /**
9 | * Simple Quicksort implementation (pivot element is always the rightmost element).
10 | *
11 | * @author Sven Woltmann
12 | */
13 | public class QuicksortSimple implements SortAlgorithm, PartitioningAlgorithm {
14 |
15 | @Override
16 | public void sort(int[] elements) {
17 | quicksort(elements, 0, elements.length - 1);
18 | }
19 |
20 | @Override
21 | public boolean isSuitableForSortedInput(int size) {
22 | return size <= 2 << 12;
23 | }
24 |
25 | private void quicksort(int[] elements, int left, int right) {
26 | // End of recursion reached?
27 | if (left >= right) {
28 | return;
29 | }
30 |
31 | int pivotPos = partition(elements, left, right);
32 | quicksort(elements, left, pivotPos - 1);
33 | quicksort(elements, pivotPos + 1, right);
34 | }
35 |
36 | @Override
37 | public int partition(int[] elements, int left, int right) {
38 | int pivot = elements[right];
39 |
40 | int i = left;
41 | int j = right - 1;
42 | while (i < j) {
43 | // Find the first element >= pivot
44 | while (elements[i] < pivot) {
45 | i++;
46 | }
47 |
48 | // Find the last element < pivot
49 | while (j > left && elements[j] >= pivot) {
50 | j--;
51 | }
52 |
53 | // If the greater element is left of the lesser element, switch them
54 | if (i < j) {
55 | ArrayUtils.swap(elements, i, j);
56 | i++;
57 | j--;
58 | }
59 | }
60 |
61 | // i == j means we haven't checked this index yet.
62 | // Move i right if necessary so that i marks the start of the right array.
63 | if (i == j && elements[i] < pivot) {
64 | i++;
65 | }
66 |
67 | // Move pivot element to its final position
68 | if (elements[i] != pivot) {
69 | ArrayUtils.swap(elements, i, right);
70 | }
71 | return i;
72 | }
73 |
74 | @Override
75 | public void sortWithCounters(int[] elements, Counters counters) {
76 | quicksortWithCounters(elements, 0, elements.length - 1, counters);
77 | }
78 |
79 | private void quicksortWithCounters(int[] elements, int left, int right, Counters counters) {
80 | // End of recursion reached?
81 | if (left >= right) {
82 | return;
83 | }
84 |
85 | int pivotPos = partitionWithCounters(elements, left, right, counters);
86 | quicksortWithCounters(elements, left, pivotPos - 1, counters);
87 | quicksortWithCounters(elements, pivotPos + 1, right, counters);
88 | }
89 |
90 | @Override
91 | public int partitionWithCounters(int[] elements, int left, int right, Counters counters) {
92 | int pivot = elements[right];
93 |
94 | int i = left;
95 | int j = right - 1;
96 | while (i < j) {
97 | counters.incIterations();
98 |
99 | // Find the first element >= pivot
100 | while (true) {
101 | counters.incComparisons();
102 | counters.incReads();
103 | if (elements[i] < pivot) {
104 | i++;
105 | } else {
106 | break;
107 | }
108 | }
109 |
110 | // Find the last element < pivot
111 | while (true) {
112 | counters.incComparisons();
113 | counters.incReads();
114 | if (j > left && elements[j] >= pivot) {
115 | j--;
116 | } else {
117 | break;
118 | }
119 | }
120 |
121 | // If the greater element is left of the lesser element, switch them
122 | if (i < j) {
123 | ArrayUtils.swap(elements, i, j);
124 | counters.addReadsAndWrites(2);
125 | i++;
126 | j--;
127 | }
128 | }
129 |
130 | // i == j means we haven't checked this index yet.
131 | // Move i right if necessary so that i marks the start of the right array.
132 | if (i == j) {
133 | counters.incReads();
134 | counters.incComparisons();
135 | if (elements[i] < pivot) {
136 | i++;
137 | }
138 | }
139 |
140 | // Move pivot element to its final position
141 | counters.incReads();
142 | counters.incComparisons();
143 | if (elements[i] != pivot) {
144 | ArrayUtils.swap(elements, i, right);
145 | counters.addReadsAndWrites(2);
146 | }
147 | return i;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/heapsort/BottomUpHeapsort.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.heapsort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 |
5 | /**
6 | * Bottom-up heapsort implementation.
7 | *
8 | * @author Sven Woltmann
9 | */
10 | public class BottomUpHeapsort extends Heapsort {
11 |
12 | /**
13 | * "Fixes" a max heap starting at the given parent position.
14 | *
15 | * @param heap the heap to be fixed
16 | * @param length the number of elements in the array that belong to the heap
17 | * @param rootPos the parent position
18 | */
19 | @Override
20 | void heapify(int[] heap, int length, int rootPos) {
21 | int leafPos = findLeaf(heap, length, rootPos);
22 | int nodePos = findTargetNodeBottomUp(heap, rootPos, leafPos);
23 |
24 | if (rootPos == nodePos) {
25 | return;
26 | }
27 |
28 | // Move all elements starting at nodePos to parent, move root to nodePos
29 | int nodeValue = heap[nodePos];
30 | heap[nodePos] = heap[rootPos];
31 |
32 | while (nodePos > rootPos) {
33 | int parentPos = getParentPos(nodePos);
34 | int parentValue = heap[parentPos];
35 | heap[parentPos] = nodeValue;
36 | nodePos = getParentPos(nodePos);
37 | nodeValue = parentValue;
38 | }
39 | }
40 |
41 | int findLeaf(int[] heap, int length, int rootPos) {
42 | int pos = rootPos;
43 | int leftChildPos = pos * 2 + 1;
44 | int rightChildPos = pos * 2 + 2;
45 |
46 | // Two child exist?
47 | while (rightChildPos < length) {
48 | if (heap[rightChildPos] > heap[leftChildPos]) {
49 | pos = rightChildPos;
50 | } else {
51 | pos = leftChildPos;
52 | }
53 | leftChildPos = pos * 2 + 1;
54 | rightChildPos = pos * 2 + 2;
55 | }
56 |
57 | // One child exist?
58 | if (leftChildPos < length) {
59 | pos = leftChildPos;
60 | }
61 |
62 | return pos;
63 | }
64 |
65 | int findTargetNodeBottomUp(int[] heap, int rootPos, int leafPos) {
66 | int parent = heap[rootPos];
67 | while (leafPos != rootPos && heap[leafPos] < parent) {
68 | leafPos = getParentPos(leafPos);
69 | }
70 | return leafPos;
71 | }
72 |
73 | int getParentPos(int pos) {
74 | return (pos - 1) / 2;
75 | }
76 |
77 | @Override
78 | void heapifyWithCounters(int[] heap, int length, int rootPos, Counters counters) {
79 | int leafPos = findLeafWithCounters(heap, length, rootPos, counters);
80 | int nodePos = findTargetNodeBottomUpWithCounters(heap, rootPos, leafPos, counters);
81 |
82 | if (rootPos == nodePos) {
83 | return;
84 | }
85 |
86 | // Move all elements starting at nodePos to parent, move root to nodePos
87 | counters.incReads();
88 | int nodeValue = heap[nodePos];
89 | counters.incReadsAndWrites();
90 | heap[nodePos] = heap[rootPos];
91 |
92 | while (nodePos > rootPos) {
93 | counters.incIterations();
94 | int parentPos = getParentPos(nodePos);
95 | counters.incReadsAndWrites();
96 | int parentValue = heap[parentPos];
97 | heap[parentPos] = nodeValue;
98 | nodePos = parentPos;
99 | nodeValue = parentValue;
100 | }
101 | }
102 |
103 | private int findLeafWithCounters(int[] heap, int length, int rootPos, Counters counters) {
104 | int pos = rootPos;
105 | int leftChildPos = pos * 2 + 1;
106 | int rightChildPos = pos * 2 + 2;
107 |
108 | while (rightChildPos < length) {
109 | counters.incIterations();
110 | counters.addReads(2);
111 | counters.incComparisons();
112 | if (heap[rightChildPos] > heap[leftChildPos]) {
113 | pos = rightChildPos;
114 | } else {
115 | pos = leftChildPos;
116 | }
117 | leftChildPos = pos * 2 + 1;
118 | rightChildPos = pos * 2 + 2;
119 | }
120 |
121 | if (leftChildPos < length) {
122 | pos = leftChildPos;
123 | }
124 |
125 | return pos;
126 | }
127 |
128 | private int findTargetNodeBottomUpWithCounters(
129 | int[] heap, int rootPos, int leafPos, Counters counters) {
130 | counters.incReads();
131 | int parentValue = heap[rootPos];
132 | int nodePos = leafPos;
133 | while (nodePos != rootPos && nodeSmallerThanParent(heap[nodePos], parentValue, counters)) {
134 | counters.incIterations();
135 | nodePos = getParentPos(nodePos);
136 | }
137 | return nodePos;
138 | }
139 |
140 | private boolean nodeSmallerThanParent(int nodeValue, int parentValue, Counters counters) {
141 | counters.incReads();
142 | counters.incComparisons();
143 | return nodeValue < parentValue;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/mergesort/MergeSort2.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.mergesort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 |
6 | /**
7 | * Merge sort implementation for performance tests.
8 | *
9 | * This implementation divides directly on the input array and creates new arrays only in the
10 | * merge phase, copying them immediately back into the input array after merging.
11 | *
12 | * Additional space complexity is O(n).
13 | *
14 | * @author Sven Woltmann
15 | */
16 | public class MergeSort2 implements SortAlgorithm {
17 |
18 | // Otherwise we run out of heap
19 | private static final int MAX_INPUT_SIZE = 1 << 28;
20 |
21 | @Override
22 | public void sort(int[] elements) {
23 | mergeSort(elements, 0, elements.length - 1);
24 | }
25 |
26 | private void mergeSort(int[] elements, int left, int right) {
27 | // End of recursion reached?
28 | if (left == right) {
29 | return;
30 | }
31 |
32 | int middle = left + (right - left) / 2;
33 | mergeSort(elements, left, middle);
34 | mergeSort(elements, middle + 1, right);
35 | merge(elements, left, middle, right);
36 | }
37 |
38 | void merge(int[] elements, int leftStart, int leftEnd, int rightEnd) {
39 | int leftPos = leftStart;
40 | int rightPos = leftEnd + 1;
41 |
42 | int length = rightEnd + 1 - leftPos;
43 | int[] target = new int[length];
44 | int targetPos = 0;
45 |
46 | // As long as both lists contain elements...
47 | while (leftPos <= leftEnd && rightPos <= rightEnd) {
48 | // Which one is smaller?
49 | int leftValue = elements[leftPos];
50 | int rightValue = elements[rightPos];
51 | if (leftValue <= rightValue) {
52 | target[targetPos++] = leftValue;
53 | leftPos++;
54 | } else {
55 | target[targetPos++] = rightValue;
56 | rightPos++;
57 | }
58 | }
59 | // Copying the rest
60 | while (leftPos <= leftEnd) {
61 | target[targetPos++] = elements[leftPos++];
62 | }
63 | while (rightPos <= rightEnd) {
64 | target[targetPos++] = elements[rightPos++];
65 | }
66 | // Write temporary array back into array to be sorted
67 | System.arraycopy(target, 0, elements, leftStart, length);
68 | }
69 |
70 | @Override
71 | public void sortWithCounters(int[] elements, Counters counters) {
72 | mergeSortWithCounters(elements, 0, elements.length - 1, counters);
73 | }
74 |
75 | private void mergeSortWithCounters(int[] elements, int left, int right, Counters counters) {
76 | // End of recursion reached?
77 | if (left == right) {
78 | return;
79 | }
80 |
81 | int middle = left + (right - left) / 2;
82 | mergeSortWithCounters(elements, left, middle, counters);
83 | mergeSortWithCounters(elements, middle + 1, right, counters);
84 | mergeWithCounters(elements, left, middle, right, counters);
85 | }
86 |
87 | void mergeWithCounters(
88 | int[] elements, int leftStart, int leftEnd, int rightEnd, Counters counters) {
89 | int leftPos = leftStart;
90 | int rightPos = leftEnd + 1;
91 |
92 | int length = rightEnd + 1 - leftPos;
93 | int[] target = new int[length];
94 | int targetPos = 0;
95 |
96 | // As long as both lists contain elements...
97 | while (leftPos <= leftEnd && rightPos <= rightEnd) {
98 | counters.incIterations();
99 |
100 | // Which one is smaller?
101 | counters.addReads(2);
102 | int leftValue = elements[leftPos];
103 | int rightValue = elements[rightPos];
104 |
105 | counters.incComparisons();
106 | counters.incWrites();
107 | if (leftValue <= rightValue) {
108 | target[targetPos++] = leftValue;
109 | leftPos++;
110 | } else {
111 | target[targetPos++] = rightValue;
112 | rightPos++;
113 | }
114 | }
115 | // Copying the rest
116 | while (leftPos <= leftEnd) {
117 | counters.incIterations();
118 | target[targetPos++] = elements[leftPos++];
119 | counters.incReadsAndWrites();
120 | }
121 | while (rightPos <= rightEnd) {
122 | counters.incIterations();
123 | target[targetPos++] = elements[rightPos++];
124 | counters.incReadsAndWrites();
125 | }
126 | // Write temporary array back into array to be sorted
127 | System.arraycopy(target, 0, elements, leftStart, length);
128 | counters.addReadsAndWrites(length);
129 | }
130 |
131 | @Override
132 | public boolean isSuitableForInputSize(int size) {
133 | return size <= MAX_INPUT_SIZE;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
true if the time is a new record; false otherwise
35 | */
36 | public boolean add(long time) {
37 | times.add(time);
38 | if (time < fastest) {
39 | fastest = time;
40 | return true;
41 | }
42 | return false;
43 | }
44 |
45 | /**
46 | * Prints the fastest and median times; followed by an optional label.
47 | *
48 | * @param longestNameLength the length to which the name is padded
49 | * @param label the optional label
50 | */
51 | @SuppressWarnings({"PMD.SystemPrintln", "java:S106"})
52 | public void printResult(int longestNameLength, String label) {
53 | String format =
54 | "%-" + longestNameLength + "s -> " + "fastest: %,10.3f ms, median: %,10.3f ms %s%n";
55 | System.out.printf(
56 | Locale.US,
57 | format,
58 | name,
59 | fastest / 1_000_000.0,
60 | getMedian() / 1_000_000.0,
61 | label != null ? label : "");
62 | }
63 |
64 | /**
65 | * Returns the median time.
66 | *
67 | * @return the median time
68 | */
69 | public long getMedian() {
70 | int len = times.size();
71 | long[] array = new long[len];
72 | for (int i = 0; i < len; i++) {
73 | array[i] = times.get(i);
74 | }
75 | return ArrayUtils.median(array);
76 | }
77 |
78 | public static int findLongestAlgorithmName(SortAlgorithm[] algorithms) {
79 | int max = 0;
80 | for (SortAlgorithm algorithm : algorithms) {
81 | int nameLength = algorithm.getName().length();
82 | if (nameLength > max) {
83 | max = nameLength;
84 | }
85 | }
86 | return max;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/eu/happycoders/sort/method/countingsort/CountingSortSimple.java:
--------------------------------------------------------------------------------
1 | package eu.happycoders.sort.method.countingsort;
2 |
3 | import eu.happycoders.sort.method.Counters;
4 | import eu.happycoders.sort.method.SortAlgorithm;
5 |
6 | /**
7 | * Counting Sort implementation for performance tests.
8 | *
9 | *