...
 
Commits (2)
.classpath
.idea
.project
.settings
out
target
**/gitignore
*.iml
*.class
*.bin
src/eu/happycoders/cds/sandbox
# Ultimate Guide - Sorting in Java
# Sorting in Java - Ultimate Guide
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>eu.happycoders</groupId>
<artifactId>sort</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.target>14</maven.compiler.target>
<maven.compiler.source>14</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<scope>test</scope>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package eu.happycoders.sort;
import eu.happycoders.sort.method.*;
import eu.happycoders.sort.utils.ArrayUtils;
import java.util.Locale;
import java.util.function.Function;
/**
* Measures the performance of all sorting algorithms for various input sizes.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CountOperations {
private static final int MIN_SORTING_SIZE = 1 << 3;
private static final int MAX_SORTING_SIZE = 1 << 15;
// Stop when counting takes longer than 20 seconds
private static final int MAX_COUNTING_TIME_SECS = 20;
public static void main(String[] args) {
for (SortAlgorithm algorithm : UltimateTest.ALGORITHMS) {
if (algorithm.supportsCounting()) {
countOps(algorithm);
}
}
}
private static void countOps(SortAlgorithm algorithm) {
// Test with a random, a sorted, and a reversed (= sorted descending) array
countOps(algorithm, "random", ArrayUtils::createRandomArray);
// Quicksort with right pivot element would go n x into recursion here...
if (algorithm.isSuitableForSortedInput()) {
countOps(algorithm, "ascending", ArrayUtils::createSortedArray);
countOps(algorithm, "descending", ArrayUtils::createReversedArray);
}
}
private static void countOps(SortAlgorithm algorithm,
String inputOrder,
Function<Integer, int[]> arraySupplier) {
System.out.printf("%n--- %s (order: %s) ---%n",
algorithm.getName(), inputOrder);
// Sort until sorting takes more than MAX_SORTING_TIME_SECS
// Upper limit used by insertion sort on already sorted data
for (int size = MIN_SORTING_SIZE;
size <= MAX_SORTING_SIZE && algorithm.isSuitableForInputSize(size);
size <<= 1) {
long time = System.currentTimeMillis();
Counters counters = countOps(algorithm, arraySupplier.apply(size));
time = System.currentTimeMillis() - time;
System.out.printf(Locale.US,
"%s (order: %s): size = %,11d --> %s%n",
algorithm.getName(),
inputOrder,
size,
counters);
// Stop after specified time
if (time > MAX_COUNTING_TIME_SECS * 1_000L) {
break;
}
}
}
private static Counters countOps(SortAlgorithm algorithm, int[] elements) {
Counters counters = new Counters();
algorithm.sort(elements, counters);
return counters;
}
}
package eu.happycoders.sort;
import eu.happycoders.sort.method.DualPivotQuicksort;
import eu.happycoders.sort.method.*;
import eu.happycoders.sort.utils.*;
import java.util.*;
import java.util.function.Function;
/**
* Measures the performance of all sorting algorithms for various input sizes.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class UltimateTest {
static final SortAlgorithm[] ALGORITHMS = {
new InsertionSort(),
new SelectionSort(),
new BubbleSort(),
new Quicksort(Quicksort.PivotStrategy.MIDDLE),
new QuicksortImproved(48, Quicksort.PivotStrategy.MIDDLE),
new DualPivotQuicksort(DualPivotQuicksort.PivotStrategy.MIDDLES),
new DualPivotQuicksortImproved(48,
DualPivotQuicksort.PivotStrategy.MIDDLES),
new MergeSort(),
new HeapSort(),
new CountingSort(),
new JavaArraysSort()
};
private static final int WARM_UPS = 2;
private static final int MIN_SORTING_SIZE = 1 << 10;
private static final int MAX_SORTING_SIZE = 1 << 29;
// Stop when sorting takes longer than 20 seconds
private static final int MAX_SORTING_TIME_SECS = 20;
private static final boolean TEST_SORTED_INPUT = true;
private static final Map<String, Scorecard> scorecards = new HashMap<>();
public static void main(String[] args) {
for (int i = 1; i <= WARM_UPS; i++) {
System.out.printf("%n===== Warm up %d of %d =====%n", i, WARM_UPS);
for (SortAlgorithm algorithm : ALGORITHMS) {
test(algorithm, true);
}
}
for (int i = 1; ; i++) {
System.out.printf("%n===== Iteration %d =====%n", i);
for (SortAlgorithm algorithm : ALGORITHMS) {
test(algorithm, false);
}
System.out.printf("%n===== Results for iteration %d =====%n", i);
for (SortAlgorithm algorithm : ALGORITHMS) {
printResults(i, algorithm, "random");
if (TEST_SORTED_INPUT) {
printResults(i, algorithm, "ascending");
printResults(i, algorithm, "descending");
}
}
}
}
private static void test(SortAlgorithm algorithm, boolean warmingUp) {
// Test with a random, a sorted, and a reversed (= sorted descending) array
test(algorithm, "random", ArrayUtils::createRandomArray, warmingUp);
// Quicksort would go n x into recursion here...
if (TEST_SORTED_INPUT && algorithm.isSuitableForSortedInput()) {
test(algorithm, "ascending", ArrayUtils::createSortedArray, warmingUp);
test(algorithm, "descending", ArrayUtils::createReversedArray,
warmingUp);
}
}
private static void test(SortAlgorithm algorithm,
String inputOrder,
Function<Integer, int[]> arraySupplier,
boolean warmingUp) {
System.out.printf("%n--- %s (order: %s) ---%n",
algorithm.getName(), inputOrder);
// Sort until sorting takes more than MAX_SORTING_TIME_SECS
// Upper limit used by insertion sort on already sorted data
for (int size = MIN_SORTING_SIZE;
size <= MAX_SORTING_SIZE && algorithm.isSuitableForInputSize(size);
size <<= 1) {
long time = measureTime(algorithm, arraySupplier.apply(size));
boolean newRecord = !warmingUp
&& scorecard(algorithm, inputOrder, size, true).add(time);
System.out.printf(Locale.US,
"%s (order: %s): size = %,11d --> time = %,10.3f ms %s%n",
algorithm.getName(),
inputOrder,
size,
time / 1_000_000.0,
newRecord ? "<<< NEW RECORD :-)" : "");
// Stop after specified time
if (time > MAX_SORTING_TIME_SECS * 1_000_000_000L) {
break;
}
}
}
private static long measureTime(SortAlgorithm algorithm, int[] elements) {
System.gc();
long time = System.nanoTime();
algorithm.sort(elements);
return System.nanoTime() - time;
}
private static Scorecard scorecard(SortAlgorithm algorithm,
String inputOrder, int size,
boolean create) {
String key = algorithm.getName() + "/" + inputOrder + "/" + size;
return create
? scorecards.computeIfAbsent(key, Scorecard::new)
: scorecards.get(key);
}
private static void printResults(int iteration, SortAlgorithm algorithm,
String inputOrder) {
System.out.printf("%n--- Results for iteration %d for: %s (order: %s) ---%n",
iteration, algorithm.getName(), inputOrder);
int longestNameLength = 0;
for (int size = MIN_SORTING_SIZE;
size <= MAX_SORTING_SIZE && algorithm.isSuitableForInputSize(size);
size <<= 1) {
Scorecard scorecard = scorecard(algorithm, inputOrder, size, false);
if (scorecard != null) {
int nameLength = scorecard.getName().length();
if (nameLength > longestNameLength) {
longestNameLength = nameLength;
}
}
}
for (int size = MIN_SORTING_SIZE;
size <= MAX_SORTING_SIZE && algorithm.isSuitableForInputSize(size);
size <<= 1) {
Scorecard scorecard = scorecard(algorithm, inputOrder, size, false);
if (scorecard != null) {
scorecard.printResult(longestNameLength, "");
}
}
}
}
package eu.happycoders.sort.comparisons;
import eu.happycoders.sort.method.DualPivotQuicksort;
import eu.happycoders.sort.method.*;
import java.util.*;
/**
* Compares Dual-Pivot Quicksort with the improved version for various
* thresholds at which the algorithm switches from Quicksort to Insertion Sort.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CompareImprovedDualPivotQuickSort extends DirectComparison {
private static final int SIZE = 5_555_555; // ~500 ms for Quicksort
public static void main(String[] args) {
List<SortAlgorithm> algorithms = new ArrayList<>();
algorithms.add(new DualPivotQuicksort(DualPivotQuicksort.PivotStrategy.MIDDLES));
for (int i = 2; i < 1 << 8; i <<= 1) {
algorithms.add(new DualPivotQuicksortImproved(i,
DualPivotQuicksort.PivotStrategy.MIDDLES));
// From 16 elements on, add i+i/2 (-> 16, 24, 32, 48, 64, 96, 128, ...)
if (i >= 16) {
int i2 = i + i / 2;
algorithms.add(new DualPivotQuicksortImproved(i2,
DualPivotQuicksort.PivotStrategy.MIDDLES));
}
}
runTest(algorithms.toArray(SortAlgorithm[]::new), SIZE);
}
}
package eu.happycoders.sort.comparisons;
import eu.happycoders.sort.method.*;
import eu.happycoders.sort.method.Quicksort.PivotStrategy;
import java.util.*;
/**
* Compares the regular Quicksort with the improved Quicksort for various
* thresholds at which the algorithm switches from Quicksort to Insertion Sort.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CompareImprovedQuickSort extends DirectComparison {
private static final int SIZE = 5_555_555; // ~500 ms for Quicksort
public static void main(String[] args) {
List<SortAlgorithm> algorithms = new ArrayList<>();
algorithms.add(new Quicksort(PivotStrategy.MIDDLE));
algorithms.add(new Quicksort(PivotStrategy.MEDIAN3));
for (int i = 2; i < 1 << 8; i <<= 1) {
algorithms.add(new QuicksortImproved(i, PivotStrategy.MIDDLE));
algorithms.add(new QuicksortImproved(i, PivotStrategy.MEDIAN3));
// From 16 elements on, add i+i/2 (-> 16, 24, 32, 48, 64, 96, 128, ...)
if (i >= 16) {
int i2 = i + i / 2;
algorithms.add(new QuicksortImproved(i2, PivotStrategy.MIDDLE));
algorithms.add(new QuicksortImproved(i2, PivotStrategy.MEDIAN3));
}
}
runTest(algorithms.toArray(SortAlgorithm[]::new), SIZE);
}
}
package eu.happycoders.sort.comparisons;
import eu.happycoders.sort.method.*;
/**
* Compares the three merge sort algorithms.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CompareMergeSorts extends DirectComparison {
private static final int SIZE = 5_555_555; // ~840 ms for Merge Sort
public static void main(String[] args) {
SortAlgorithm[] algorithms = new SortAlgorithm[]{
new MergeSort(),
new MergeSort2(),
new MergeSort3()
};
runTest(algorithms, SIZE);
}
}
package eu.happycoders.sort.comparisons;
import eu.happycoders.sort.method.*;
import eu.happycoders.sort.method.Quicksort.PivotStrategy;
import java.util.*;
/**
* Compares the regular Quicksort with the improved Quicksort for various
* thresholds at which the algorithm switches from Quicksort to Insertion Sort.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CompareQuickSorts extends DirectComparison {
private static final int SIZE = 5_555_555; // ~500 ms for Quicksort
public static void main(String[] args) {
List<SortAlgorithm> algorithms = new ArrayList<>();
algorithms.add(new Quicksort(PivotStrategy.MIDDLE));
algorithms.add(new Quicksort(PivotStrategy.MEDIAN3));
runTest(algorithms.toArray(SortAlgorithm[]::new), SIZE);
}
}
package eu.happycoders.sort.comparisons;
import eu.happycoders.sort.method.SortAlgorithm;
import eu.happycoders.sort.utils.*;
import java.util.*;
/**
* Base class to directly compare two or more sort algorithms.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class DirectComparison {
private static final int WARM_UPS = 10;
private static final int ITERATIONS = 100;
private static final int PRINT_RESULTS_ALL_X_ITERATIONS = 10;
private static final Map<String, Scorecard> scorecards = new HashMap<>();
private static int longestNameLength;
public static void runTest(SortAlgorithm[] algorithms, int size) {
longestNameLength = Scorecard.findLongestAlgorithmName(algorithms);
int numAlgorithms = algorithms.length;
System.out.println("----- Warming up -----");
for (int i = 1; i <= WARM_UPS; i++) {
System.out.printf("Iteration %d%n", i);
int[] elements = ArrayUtils.createRandomArray(size);
// The order in which we test affects which test is the fastest.
// Maybe the JDK optimizes some parts specific to the input data!?
// --> Shuffle the algorithms!
int[] indices = getShuffledIndices(numAlgorithms);
for (int j = 0; j < numAlgorithms; j++) {
SortAlgorithm algorithm = algorithms[indices[j]];
measure(algorithm, elements.clone());
System.out.println();
}
}
System.out.println("\n----- Measuring -----");
for (int i = 1; i <= ITERATIONS; i++) {
System.out.printf("Iteration %d%n", i);
int[] elements = ArrayUtils.createRandomArray(size);
int[] indices = getShuffledIndices(numAlgorithms);
for (int j = 0; j < numAlgorithms; j++) {
SortAlgorithm algorithm = algorithms[indices[j]];
long time = measure(algorithm, elements.clone());
boolean newRecord = scorecardForAlgorithm(algorithm).add(time);
System.out.println(newRecord ? " <<< NEW RECORD :-)" : "");
}
if (i % PRINT_RESULTS_ALL_X_ITERATIONS == 0) {
printResults(algorithms, i);
System.out.println();
}
}
}
private static int[] getShuffledIndices(int numTestSets) {
int[] indices = ArrayUtils.createSortedArray(numTestSets);
ArrayUtils.shuffle(indices);
return indices;
}
private static long measure(SortAlgorithm sortAlgorithm, int[] elements) {
System.gc();
long time = System.nanoTime();
sortAlgorithm.sort(elements);
time = System.nanoTime() - time;
System.out.printf(Locale.US,
" %-" + longestNameLength + "s -> time = %,10.3f ms",
sortAlgorithm.getName(), (time / 1_000_000.0));
return time;
}
private static Scorecard scorecardForAlgorithm(SortAlgorithm algorithm) {
return scorecards.computeIfAbsent(algorithm.getName(), Scorecard::new);
}
private static void printResults(SortAlgorithm[] algorithms, int iterations) {
System.out.printf("%n----- Results after %d iterations-----%n", iterations);
// 1. Find the fastest median
int fastestAlgorithmIndex = 0;
long fastestAlgorithmMedian = Long.MAX_VALUE;
for (int i = 0; i < algorithms.length; i++) {
long median = scorecardForAlgorithm(algorithms[i]).getMedian();
if (median < fastestAlgorithmMedian) {
fastestAlgorithmIndex = i;
fastestAlgorithmMedian = median;
}
}
// 2. Print them, showing which one has the fastest median
for (int i = 0; i < algorithms.length; i++) {
scorecardForAlgorithm(algorithms[i]).printResult(longestNameLength,
i == fastestAlgorithmIndex ? "<<< FASTEST MEDIAN :-)" : null);
}
}
}
package eu.happycoders.sort.method;
/**
* Bubble Sort implementation for performance tests.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class BubbleSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
for (int max = elements.length - 1; max > 0; max--) {
boolean swapped = false;
for (int i = 0; i < max; i++) {
int left = elements[i];
int right = elements[i + 1];
if (left > right) {
elements[i + 1] = left;
elements[i] = right;
swapped = true;
}
}
if (!swapped) break;
}
}
@Override
public void sort(int[] elements, Counters counters) {
for (int max = elements.length - 1; max > 0; max--) {
counters.incIterations();
boolean swapped = false;
for (int i = 0; i < max; i++) {
counters.incIterations();
int left = elements[i];
int right = elements[i + 1];
counters.addReads(2);
counters.incComparisons();
if (left > right) {
elements[i + 1] = left;
elements[i] = right;
counters.addWrites(2);
swapped = true;
}
}
if (!swapped) break;
}
}
}
package eu.happycoders.sort.method;
import java.util.Locale;
/**
* Counter for sort operations.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class Counters {
private long iterations;
private long comparisons;
private long reads;
private long writes;
public void incIterations() {
iterations++;
}
public void addIterations(int x) {
iterations += x;
}
public void incComparisons() {
comparisons++;
}
public void addComparisons(int x) {
comparisons += x;
}
public void incReads() {
reads++;
}
public void addReads(int x) {
reads += x;
}
public void incWrites() {
writes++;
}
public void addWrites(int x) {
writes += x;
}
public void incReadsAndWrites() {
reads++;
writes++;
}
public void addReadsAndWrites(int x) {
reads += x;
writes += x;
}
@Override
public String toString() {
return String.format(Locale.US,
"iterations = %,11d, comparisons = %,11d, " +
"reads = %,11d, writes = %,11d",
iterations,
comparisons,
reads,
writes);
}
}
package eu.happycoders.sort.method;
/**
* Counting Sort implementation for performance tests.
*
* <p>
* For simplicity, this implementation allows only elements >= 0.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class CountingSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
int maxValue = findMax(elements);
int[] counts = new int[maxValue + 1];
// Count
for (int i = 0; i < elements.length; i++) {
counts[elements[i]]++;
}
// Write results back
int targetPos = 0;
for (int i = 0; i < counts.length; i++) {
for (int j = 0; j < counts[i]; j++) {
elements[targetPos++] = i;
}
}
}
private int findMax(int[] elements) {
int max = 0;
for (int i = 0; i < elements.length; i++) {
if (elements[i] > max) {
max = elements[i];
}
}
return max;
}
@Override
public void sort(int[] elements, Counters counters) {
int maxValue = findMax(elements);
int length = elements.length;
counters.addReads(length);
counters.addIterations(length);
counters.addComparisons(length);
int[] counts = new int[maxValue + 1];
// Count
counters.addIterations(length);
counters.addReads(length); // read elements[i]
counters.addReadsAndWrites(length); // inc counts[...]
for (int i = 0; i < length; i++) {
counts[elements[i]]++;
}
// Write results back
int targetPos = 0;
counters.addIterations(counts.length); // outer
counters.addIterations(length); // all inner combined
counters.addReads(counts.length); // read counts[i]
counters.addWrites(length); // write elements[targetPos++]
for (int i = 0; i < counts.length; i++) {
for (int j = 0; j < counts[i]; j++) {
elements[targetPos++] = i;
}
}
}
}
package eu.happycoders.sort.method;
import eu.happycoders.sort.utils.ArrayUtils;
/**
* Dual-pivot Quicksort implementation for performance tests.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class DualPivotQuicksort implements SortAlgorithm {
public enum PivotStrategy {LEFT_RIGHT, MIDDLES}
private final PivotStrategy pivotStrategy;
public DualPivotQuicksort(PivotStrategy pivotStrategy) {
this.pivotStrategy = pivotStrategy;
}
@Override
public String getName() {
return this.getClass().getSimpleName() + "(pivot: " + pivotStrategy + ")";
}
@Override
public boolean isSuitableForSortedInput() {
// Don't run tests for sorted input with pivot strategy LEFT_RIGHT.
// This will go (n - 2) x into recursion.
return pivotStrategy != PivotStrategy.LEFT_RIGHT;
}
@Override
public void sort(int[] elements) {
quicksort(elements, 0, elements.length - 1);
}
private void quicksort(int[] elements, int left, int right) {
// End of recursion reached?
if (left >= right) return;
int[] pivotPos = partition(elements, left, right);
int p0 = pivotPos[0];
int p1 = pivotPos[1];
quicksort(elements, left, p0 - 1);
quicksort(elements, p0 + 1, p1 - 1);
quicksort(elements, p1 + 1, right);
}
int[] partition(int[] elements, int left, int right) {
findPivotsAndMoveToLeftRight(elements, left, right);
int leftPivot = elements[left];
int rightPivot = elements[right];
int leftPartitionEnd = left + 1;
int leftIndex = left + 1;
int rightIndex = right - 1;
while (leftIndex <= rightIndex) {
// elements < left pivot element?
if (elements[leftIndex] < leftPivot) {
ArrayUtils.swap(elements, leftIndex, leftPartitionEnd);
leftPartitionEnd++;
}
// elements >= right pivot element?
else if (elements[leftIndex] >= rightPivot) {
while (elements[rightIndex] > rightPivot && leftIndex < rightIndex)
rightIndex--;
ArrayUtils.swap(elements, leftIndex, rightIndex);
rightIndex--;
if (elements[leftIndex] < leftPivot) {
ArrayUtils.swap(elements, leftIndex, leftPartitionEnd);
leftPartitionEnd++;
}
}
leftIndex++;
}
leftPartitionEnd--;
rightIndex++;
// bring pivots to their appropriate positions.
ArrayUtils.swap(elements, left, leftPartitionEnd);
ArrayUtils.swap(elements, right, rightIndex);
return new int[]{leftPartitionEnd, rightIndex};
}
@Override
public void sort(int[] elements, Counters counters) {
quicksort(elements, 0, elements.length - 1, counters);
}
private void quicksort(int[] elements, int left, int right,
Counters counters) {
// End of recursion reached?
if (left >= right) return;
int[] pivotPos = partition(elements, left, right, counters);
int p0 = pivotPos[0];
int p1 = pivotPos[1];
quicksort(elements, left, p0 - 1, counters);
quicksort(elements, p0 + 1, p1 - 1, counters);
quicksort(elements, p1 + 1, right, counters);
}
int[] partition(int[] elements, int left, int right, Counters counters) {
findPivotsAndMoveToLeftRight(elements, left, right);
int leftPivot = elements[left];
int rightPivot = elements[right];
counters.addReads(2);
int leftPartitionEnd = left + 1;
int leftIndex = left + 1;
int rightIndex = right - 1;
while (leftIndex <= rightIndex) {
counters.incIterations();
// elements <= left pivot element?
counters.incReads();
counters.incComparisons();
if (elements[leftIndex] < leftPivot) {
ArrayUtils.swap(elements, leftIndex, leftPartitionEnd);
counters.addReadsAndWrites(2);
leftPartitionEnd++;
} else {
counters.incReads();
counters.incComparisons();
// elements >= right pivot element?
if (elements[leftIndex] >= rightPivot) {
while (leftIndex < rightIndex) {
counters.incIterations();
counters.incReads();
counters.incComparisons();
if (!(elements[rightIndex] > rightPivot)) break;
rightIndex--;
}
ArrayUtils.swap(elements, leftIndex, rightIndex);
counters.addReadsAndWrites(2);
rightIndex--;
counters.incReads();
counters.incComparisons();
if (elements[leftIndex] < leftPivot) {
ArrayUtils.swap(elements, leftIndex, leftPartitionEnd);
counters.addReadsAndWrites(2);
leftPartitionEnd++;
}
}
}
leftIndex++;
}
leftPartitionEnd--;
rightIndex++;
// bring pivots to their appropriate positions.
ArrayUtils.swap(elements, left, leftPartitionEnd);
ArrayUtils.swap(elements, right, rightIndex);
counters.addReadsAndWrites(4);
return new int[]{leftPartitionEnd, rightIndex};
}
private void findPivotsAndMoveToLeftRight(int[] elements,
int left, int right) {
switch (pivotStrategy) {
case LEFT_RIGHT -> {
if (elements[left] > elements[right]) {
ArrayUtils.swap(elements, left, right);
}
}
case MIDDLES -> {
int len = right - left + 1;
int leftPos = left + (len - 1) / 3;
int rightPos = right - (len - 2) / 3;
int eLeft = elements[leftPos];
int eRight = elements[rightPos];
if (eLeft > eRight) {
if (rightPos == right) {
if (leftPos == left) {
ArrayUtils.swap(elements, left, right);
} else {
// 3-way swap
elements[right] = eLeft;
elements[leftPos] = elements[left];
elements[left] = eRight;
}
} else if (leftPos == left) {
// 3-way swap
elements[left] = eRight;
elements[rightPos] = elements[right];
elements[right] = eLeft;
} else {
ArrayUtils.swap(elements, leftPos, right);
ArrayUtils.swap(elements, rightPos, left);
}
} else {
if (rightPos != right)
ArrayUtils.swap(elements, rightPos, right);
if (leftPos != left)
ArrayUtils.swap(elements, leftPos, left);
}
}
default -> throw new IllegalStateException("Unexpected value: " + pivotStrategy);
}
}
}
package eu.happycoders.sort.method;
import eu.happycoders.sort.method.DualPivotQuicksort.PivotStrategy;
/**
* Dual-pivot Quicksort implementation for performance tests.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class DualPivotQuicksortImproved implements SortAlgorithm {
private final int threshold;
private final PivotStrategy pivotStrategy; // just for the getName() method
private final DualPivotQuicksort standardQuicksort;
private final InsertionSort insertionSort;
/**
* Constructs the Dual-pivot Quicksort instance.
*
* @param threshold when the array to be sorted is not longer than this
* threshold, the algorithm switches to Insertion Sort
* @param pivotStrategy the pivot strategy to use
*/
public DualPivotQuicksortImproved(int threshold,
PivotStrategy pivotStrategy) {
this.threshold = threshold;
this.pivotStrategy = pivotStrategy;
this.standardQuicksort = new DualPivotQuicksort(pivotStrategy);
this.insertionSort = new InsertionSort();
}
@Override
public String getName() {
return this.getClass().getSimpleName() + "(threshold: " + threshold +
", pivot: " + pivotStrategy + ")";
}
@Override
public void sort(int[] elements) {
quicksort(elements, 0, elements.length - 1);
}
private void quicksort(int[] elements, int left, int right) {
// End of recursion reached?
if (left >= right) return;
// Threshold for insertion sort reached?
if (right - left < threshold) {
insertionSort.sort(elements, left, right + 1);
return;
}
int[] pivotPos = standardQuicksort.partition(elements, left, right);
int p0 = pivotPos[0];
int p1 = pivotPos[1];
quicksort(elements, left, p0 - 1);
quicksort(elements, p0 + 1, p1 - 1);
quicksort(elements, p1 + 1, right);
}
@Override
public void sort(int[] elements, Counters counters) {
quicksort(elements, 0, elements.length - 1, counters);
}
private void quicksort(int[] elements, int left, int right,
Counters counters) {
// End of recursion reached?
if (left >= right) return;
// Threshold for insertion sort reached?
if (right - left < threshold) {
insertionSort.sort(elements, left, right + 1, counters);
return;
}
int[] pivotPos = standardQuicksort.partition(elements, left, right,
counters);
int p0 = pivotPos[0];
int p1 = pivotPos[1];
quicksort(elements, left, p0 - 1, counters);
quicksort(elements, p0 + 1, p1 - 1, counters);
quicksort(elements, p1 + 1, right, counters);
}
@Override
public boolean isSuitableForInputSize(int size) {
return standardQuicksort.isSuitableForInputSize(size);
}
@Override
public boolean isSuitableForSortedInput() {
return standardQuicksort.isSuitableForSortedInput();
}
}
package eu.happycoders.sort.method;
import eu.happycoders.sort.utils.ArrayUtils;
public class HeapSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
buildHeap(elements);
for (int swapToPos = elements.length - 1; swapToPos > 0; swapToPos--) {
// Move root to end
ArrayUtils.swap(elements, 0, swapToPos);
// Fix remaining heap
heapify(elements, swapToPos, 0);
}
}
/**
* Converts the given random array to a "max heap". Time complexity is O(n).
*
* @param elements the elements to be converted
*/
void buildHeap(int[] elements) {
// "Find" the last parent node
int lastParentNode = elements.length / 2 - 1;
// Now heapify it from here on backwards
for (int i = lastParentNode; i >= 0; i--) {
heapify(elements, elements.length, i);
}
}
/**
* "Fixes" a max heap starting at the given parent position. Time
* complexity is O(log n).
*
* @param heap the heap to be fixed
* @param length the number of elements in the array that belong to the
* heap
* @param parentPos the parent position
*/
void heapify(int[] heap, int length, int parentPos) {
int leftChildPos = parentPos * 2 + 1;
int rightChildPos = parentPos * 2 + 2;
// Find the largest element
int largestPos = parentPos;
if (leftChildPos < length && heap[leftChildPos] > heap[largestPos]) {
largestPos = leftChildPos;
}
if (rightChildPos < length && heap[rightChildPos] > heap[largestPos]) {
largestPos = rightChildPos;
}
// largestPos is now either parentPos, leftChildPos or rightChildPos.
// If it's not the parent, then switch!
if (largestPos != parentPos) {
ArrayUtils.swap(heap, parentPos, largestPos);
// ... and fix again starting at the child we moved the parent to
heapify(heap, length, largestPos);
}
}
@Override
public void sort(int[] elements, Counters counters) {
buildHeap(elements, counters);
for (int swapToPos = elements.length - 1; swapToPos > 0; swapToPos--) {
// Move root to end
ArrayUtils.swap(elements, 0, swapToPos);
counters.addReadsAndWrites(2);
// Fix remaining heap
heapify(elements, swapToPos, 0, counters);
}
}
void buildHeap(int[] elements, Counters counters) {
// "Find" the last parent node
int lastParentNode = elements.length / 2 - 1;
// Now fix it from here on backwards
for (int i = lastParentNode; i >= 0; i--) {
counters.incIterations();
heapify(elements, elements.length, i, counters);
}
}
void heapify(int[] heap, int length, int parentPos, Counters counters) {
int leftChildPos = parentPos * 2 + 1;
int rightChildPos = parentPos * 2 + 2;
// Find the largest element
int largestPos = parentPos;
if (leftChildPos < length) {
counters.incComparisons();
counters.addReads(2);
if (heap[leftChildPos] > heap[largestPos]) {
largestPos = leftChildPos;
}
}
if (rightChildPos < length) {
counters.incComparisons();
counters.addReads(2);
if (heap[rightChildPos] > heap[largestPos]) {
largestPos = rightChildPos;
}
}
// largestPos is now either parentPos, leftChildPos or rightChildPos.
// If it's not the parent, then switch!
if (largestPos != parentPos) {
ArrayUtils.swap(heap, parentPos, largestPos);
counters.addReadsAndWrites(2);
// ... and fix again starting at the child we moved the parent to
heapify(heap, length, largestPos, counters);
}
}
}
package eu.happycoders.sort.method;
/**
* Insertion Sort implementation for performance tests.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class InsertionSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
sort(elements, 0, elements.length);
}
void sort(int[] elements, int fromIndex, int toIndex) {
for (int i = fromIndex + 1; i < toIndex; i++) {
int elementToSort = elements[i];
// Move element to the left until it's at the right position
int j = i;
while (j > fromIndex && elementToSort < elements[j - 1]) {
elements[j] = elements[j - 1];
j--;
}
elements[j] = elementToSort;
}
}
@Override
public void sort(int[] elements, Counters counters) {
sort(elements, 0, elements.length, counters);
}
void sort(int[] elements, int fromIndex, int toIndex, Counters counters) {
for (int i = fromIndex + 1; i < toIndex; i++) {
counters.incIterations();
int number = elements[i];
counters.incReads();
// Move element to the left until it's at the right position
int j = i;
while (j > fromIndex) {
counters.incIterations();
counters.incComparisons();
if (!(number < elements[j - 1])) break;
elements[j] = elements[j - 1];
counters.incReads();
counters.incWrites();
j--;
}
elements[j] = number;
counters.incWrites();
}
}
}
package eu.happycoders.sort.method;
import java.util.Arrays;
/**
* Adapter for testing <code>Arrays.sort</code>.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class JavaArraysSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
Arrays.sort(elements);
}
@Override
public void sort(int[] elements, Counters counters) {
// Not implemented
}
@Override
public boolean supportsCounting() {
return false;
}
}
package eu.happycoders.sort.method;
/**
* Merge sort implementation for performance tests.
*
* <p>
* This implementation divides directly on the input array and creates new
* arrays only in the merge phase, passing the new arrays up the call chain
* and merging them; finally copying the resulting array back to the input
* array.
*
* <p>
* Additional space complexity is <strong>O(n)</strong>.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class MergeSort implements SortAlgorithm {
@Override
public void sort(int[] elements) {
int length = elements.length;
int[] sorted = mergeSort(elements, 0, length - 1);
System.arraycopy(sorted, 0, elements, 0, length);
}
private int[] mergeSort(int[] elements, int left, int right) {
// End of recursion reached?
if (left == right) return new int[]{elements[left]};
int middle = left + (right - left) / 2;
int[] leftArray = mergeSort(elements, left, middle);
int[] rightArray = mergeSort(elements, middle + 1, right);
return merge(leftArray, rightArray);
}
int[] merge(int[] leftArray, int[] rightArray) {
int leftLen = leftArray.length;
int rightLen = rightArray.length;
int[] target = new int[leftLen + rightLen];
int targetPos = 0;
int leftPos = 0;
int rightPos = 0;
// As long as both lists contain elements...
while (leftPos < leftLen && rightPos < rightLen) {
// Which one is smaller?
int leftValue = leftArray[leftPos];
int rightValue = rightArray[rightPos];
if (leftValue < rightValue) {
target[targetPos++] = leftValue;
leftPos++;
} else {
target[targetPos++] = rightValue;
rightPos++;
}
}
// Copying the rest
while (leftPos < leftLen) {
target[targetPos++] = leftArray[leftPos++];
}
while (rightPos < rightLen) {
target[targetPos++] = rightArray[rightPos++];
}
return target;
}
@Override
public void sort(int[] elements, Counters counters) {
int length = elements.length;
int[] sorted = mergeSort(elements, 0, length - 1, counters);
System.arraycopy(sorted, 0, elements, 0, length);
counters.addReadsAndWrites(length);
}
private int[] mergeSort(int[] elements, int left, int right,
Counters counters) {
// End of recursion reached?
if (left == right) return new int[]{elements[left]};
int middle = left + (right - left) / 2;
int[] leftArray = mergeSort(elements, left, middle, counters);
int[] rightArray = mergeSort(elements, middle + 1, right, counters);
return merge(leftArray, rightArray, counters);
}
int[] merge(int[] leftArray, int[] rightArray, Counters counters) {
int leftLen = leftArray.length;
int rightLen = rightArray.length;
int[] target = new int[leftLen + rightLen];
int targetPos = 0;
int leftPos = 0;
int rightPos = 0;
// As long as both lists contain elements...
while (leftPos < leftLen && rightPos < rightLen) {
counters.incIterations();
// Which one is smaller?
int leftValue = leftArray[leftPos];
int rightValue = rightArray[rightPos];
counters.addReads(2);
counters.incComparisons();
counters.incWrites();
if (leftValue < rightValue) {
target[targetPos++] = leftValue;
leftPos++;
} else {
target[targetPos++] = rightValue;
rightPos++;
}
}
// Copying the rest
while (leftPos < leftLen) {
counters.incIterations();
target[targetPos++] = leftArray[leftPos++];
counters.incReadsAndWrites();
}
while (rightPos < rightLen) {
counters.incIterations();
target[targetPos++] = rightArray[rightPos++];
counters.incReadsAndWrites();
}
return target;
}
// Otherwise we run out of heap
private static final int MAX_INPUT_SIZE = 1 << 28;
@Override
public boolean isSuitableForInputSize(int size) {
return size <= MAX_INPUT_SIZE;
}
}
package eu.happycoders.sort.method;
/**
* Merge sort implementation for performance tests.
*
* <p>
* This implementation divides directly on the input array and creates new
* arrays only in the merge phase, copying them immediately back into the
* input array after merging.
*
* <p>
* Additional space complexity is <strong>O(n)</strong>.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class MergeSort2 implements SortAlgorithm {
@Override
public void sort(int[] elements) {
mergeSort(elements, 0, elements.length - 1);
}
private void mergeSort(int[] elements, int left, int right) {
// End of recursion reached?
if (left == right) return;
int middle = left + (right - left) / 2;
mergeSort(elements, left, middle);
mergeSort(elements, middle + 1, right);
merge(elements, left, middle, right);
}
void merge(int[] elements, int leftStart, int leftEnd, int rightEnd) {
int leftPos = leftStart;
int rightPos = leftEnd + 1;
int length = rightEnd + 1 - leftPos;
int[] target = new int[length];
int targetPos = 0;
// As long as both lists contain elements...
while (leftPos <= leftEnd && rightPos <= rightEnd) {
// Which one is smaller?
int leftValue = elements[leftPos];
int rightValue = elements[rightPos];
if (leftValue < rightValue) {
target[targetPos++] = leftValue;
leftPos++;
} else {
target[targetPos++] = rightValue;
rightPos++;
}
}
// Copying the rest
while (leftPos <= leftEnd) {
target[targetPos++] = elements[leftPos++];
}
while (rightPos <= rightEnd) {
target[targetPos++] = elements[rightPos++];
}
// Write temporary array back into array to be sorted
System.arraycopy(target, 0, elements, leftStart, length);
}
@Override
public void sort(int[] elements, Counters counters) {
mergeSort(elements, 0, elements.length - 1, counters);
}
private void mergeSort(int[] elements, int left, int right,
Counters counters) {
// End of recursion reached?
if (left == right) return;
int middle = left + (right - left) / 2;
mergeSort(elements, left, middle, counters);
mergeSort(elements, middle + 1, right, counters);
merge(elements, left, middle, right, counters);
}
void merge(int[] elements, int leftStart, int leftEnd, int rightEnd,
Counters counters) {
int leftPos = leftStart;
int rightPos = leftEnd + 1;
int length = rightEnd + 1 - leftPos;
int[] target = new int[length];
int targetPos = 0;
// As long as both lists contain elements...
while (leftPos <= leftEnd && rightPos <= rightEnd) {
counters.incIterations();
// Which one is smaller?
int leftValue = elements[leftPos];
int rightValue = elements[rightPos];
counters.addReads(2);
counters.incComparisons();
counters.incWrites();
if (leftValue < rightValue) {
target[targetPos++] = leftValue;
leftPos++;
} else {
target[targetPos++] = rightValue;
rightPos++;
}
}
// Copying the rest
while (leftPos <= leftEnd) {
counters.incIterations();
target[targetPos++] = elements[leftPos++];
counters.incReadsAndWrites();
}
while (rightPos <= rightEnd) {
counters.incIterations();
target[targetPos++] = elements[rightPos++];
counters.incReadsAndWrites();
}
// Write temporary array back into array to be sorted
System.arraycopy(target, 0, elements, leftStart, length);