...
 
Commits (6)
.classpath
.idea
.project
.settings
out
target
**/gitignore
*.iml
*.class
*.bin
src/eu/happycoders/cds/sandbox
# Ultimate Guide - Sorting in Java
# Sorting Algorithms - Ultimate Guide
Sorting algorithm source codes + ultimate test to compare the performance of all algorithms.
The code belongs to this series of articles:
* English:
* [Sorting Algorithms \[Ultimate Guide\]](https://www.happycoders.eu/algorithms/sorting-algorithms/)
* [Insertion Sort – Algorithm, Source Code, Time Complexity](https://www.happycoders.eu/algorithms/insertion-sort/)
* German:
* [Sortieralgorithmen \[Ultimate Guide\]](https://www.happycoders.eu/de/algorithmen/sortieralgorithmen/)
* [Insertion Sort – Insertion Sort – Algorithmus, Quellcode, Zeitkomplexität](https://www.happycoders.eu/de/algorithmen/insertion-sort/)
<?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>
This diff is collapsed.
--- Results for iteration 50 for: InsertionSort (order: random) ---
InsertionSort/random/1024 -> fastest: 0.088 ms, median: 0.095 ms
InsertionSort/random/2048 -> fastest: 0.339 ms, median: 0.355 ms
InsertionSort/random/4096 -> fastest: 1.323 ms, median: 1.378 ms
InsertionSort/random/8192 -> fastest: 5.292 ms, median: 5.494 ms
InsertionSort/random/16384 -> fastest: 21.349 ms, median: 21.890 ms
InsertionSort/random/32768 -> fastest: 85.546 ms, median: 87.859 ms
InsertionSort/random/65536 -> fastest: 343.144 ms, median: 350.428 ms
InsertionSort/random/131072 -> fastest: 1,378.011 ms, median: 1,398.919 ms
InsertionSort/random/262144 -> fastest: 5,636.412 ms, median: 5,706.817 ms
InsertionSort/random/524288 -> fastest: 22,749.327 ms, median: 23,009.682 ms
--- Results for iteration 50 for: InsertionSort (order: ascending) ---
InsertionSort/ascending/1024 -> fastest: 0.002 ms, median: 0.002 ms
InsertionSort/ascending/2048 -> fastest: 0.003 ms, median: 0.003 ms
InsertionSort/ascending/4096 -> fastest: 0.005 ms, median: 0.006 ms
InsertionSort/ascending/8192 -> fastest: 0.011 ms, median: 0.011 ms
InsertionSort/ascending/16384 -> fastest: 0.021 ms, median: 0.021 ms
InsertionSort/ascending/32768 -> fastest: 0.041 ms, median: 0.042 ms
InsertionSort/ascending/65536 -> fastest: 0.082 ms, median: 0.084 ms
InsertionSort/ascending/131072 -> fastest: 0.163 ms, median: 0.168 ms
InsertionSort/ascending/262144 -> fastest: 0.331 ms, median: 0.351 ms
InsertionSort/ascending/524288 -> fastest: 0.670 ms, median: 0.710 ms
InsertionSort/ascending/1048576 -> fastest: 1.334 ms, median: 1.419 ms
InsertionSort/ascending/2097152 -> fastest: 2.667 ms, median: 2.811 ms
InsertionSort/ascending/4194304 -> fastest: 5.343 ms, median: 5.621 ms
InsertionSort/ascending/8388608 -> fastest: 10.745 ms, median: 11.190 ms
InsertionSort/ascending/16777216 -> fastest: 21.748 ms, median: 22.290 ms
InsertionSort/ascending/33554432 -> fastest: 43.500 ms, median: 44.455 ms
InsertionSort/ascending/67108864 -> fastest: 86.143 ms, median: 87.079 ms
InsertionSort/ascending/134217728 -> fastest: 172.431 ms, median: 173.980 ms
InsertionSort/ascending/268435456 -> fastest: 343.419 ms, median: 346.236 ms
InsertionSort/ascending/536870912 -> fastest: 688.349 ms, median: 693.310 ms
--- Results for iteration 50 for: InsertionSort (order: descending) ---
InsertionSort/descending/1024 -> fastest: 0.177 ms, median: 0.178 ms
InsertionSort/descending/2048 -> fastest: 0.679 ms, median: 0.690 ms
InsertionSort/descending/4096 -> fastest: 2.676 ms, median: 2.704 ms
InsertionSort/descending/8192 -> fastest: 10.629 ms, median: 10.858 ms
InsertionSort/descending/16384 -> fastest: 42.709 ms, median: 43.609 ms
InsertionSort/descending/32768 -> fastest: 171.607 ms, median: 175.800 ms
InsertionSort/descending/65536 -> fastest: 684.408 ms, median: 697.590 ms
InsertionSort/descending/131072 -> fastest: 2,807.428 ms, median: 2,839.991 ms
InsertionSort/descending/262144 -> fastest: 11,379.433 ms, median: 11,517.355 ms
InsertionSort/descending/524288 -> fastest: 45,939.779 ms, median: 46,309.272 ms
--- Results for iteration 50 for: SelectionSort (order: random) ---
SelectionSort/random/1024 -> fastest: 0.155 ms, median: 0.162 ms
SelectionSort/random/2048 -> fastest: 0.512 ms, median: 0.530 ms
SelectionSort/random/4096 -> fastest: 1.825 ms, median: 1.882 ms
SelectionSort/random/8192 -> fastest: 6.847 ms, median: 7.114 ms
SelectionSort/random/16384 -> fastest: 27.239 ms, median: 27.888 ms
SelectionSort/random/32768 -> fastest: 106.386 ms, median: 107.985 ms
SelectionSort/random/65536 -> fastest: 425.302 ms, median: 434.026 ms
SelectionSort/random/131072 -> fastest: 1,705.741 ms, median: 1,729.812 ms
SelectionSort/random/262144 -> fastest: 6,843.655 ms, median: 6,913.384 ms
SelectionSort/random/524288 -> fastest: 27,414.730 ms, median: 27,649.758 ms
--- Results for iteration 50 for: SelectionSort (order: ascending) ---
SelectionSort/ascending/1024 -> fastest: 0.111 ms, median: 0.115 ms
SelectionSort/ascending/2048 -> fastest: 0.418 ms, median: 0.426 ms
SelectionSort/ascending/4096 -> fastest: 1.605 ms, median: 1.645 ms
SelectionSort/ascending/8192 -> fastest: 6.352 ms, median: 6.611 ms
SelectionSort/ascending/16384 -> fastest: 26.036 ms, median: 26.768 ms
SelectionSort/ascending/32768 -> fastest: 103.999 ms, median: 105.355 ms
SelectionSort/ascending/65536 -> fastest: 417.469 ms, median: 424.329 ms
SelectionSort/ascending/131072 -> fastest: 1,694.837 ms, median: 1,714.145 ms
SelectionSort/ascending/262144 -> fastest: 6,820.222 ms, median: 6,880.154 ms
SelectionSort/ascending/524288 -> fastest: 27,320.116 ms, median: 27,568.692 ms
--- Results for iteration 50 for: SelectionSort (order: descending) ---
SelectionSort/descending/1024 -> fastest: 0.265 ms, median: 0.279 ms
SelectionSort/descending/2048 -> fastest: 1.006 ms, median: 1.063 ms
SelectionSort/descending/4096 -> fastest: 3.976 ms, median: 4.184 ms
SelectionSort/descending/8192 -> fastest: 16.022 ms, median: 16.522 ms
SelectionSort/descending/16384 -> fastest: 65.071 ms, median: 65.647 ms
SelectionSort/descending/32768 -> fastest: 260.293 ms, median: 265.407 ms
SelectionSort/descending/65536 -> fastest: 1,043.764 ms, median: 1,052.185 ms
SelectionSort/descending/131072 -> fastest: 4,172.432 ms, median: 4,209.876 ms
SelectionSort/descending/262144 -> fastest: 16,787.269 ms, median: 16,863.748 ms
SelectionSort/descending/524288 -> fastest: 67,219.754 ms, median: 67,537.755 ms
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
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.utils.ArrayUtils;
import java.util.Locale;
/**
* Counts minPos/min assignments for finding the smallest element in an
* unsorted array.
*
* @author <a href="[email protected]">Sven Woltmann</a>
*/
public class FindMinimumTest {
private static final int NUM_SIZES = 29;
private static final int NUM_TESTS = 100;
private static final int[] counts = new int[NUM_SIZES];
private static final int[][] countsM = new int[NUM_SIZES][NUM_TESTS];
public static void main(String[] args) {
for (int i = 0; i < NUM_TESTS; i++) {
test(i);
printResults(i + 1);
}
}
private static void test(int iteration) {
for (int i = 0; i < NUM_SIZES; i++) {
int size = 2 << i;
int assignments = countAssignmentsForSize(size);
counts[i] += assignments;
countsM[i][iteration] = assignments;
}
}
private static int countAssignmentsForSize(int size) {
int[] array = ArrayUtils.createRandomArray(size);
int min = Integer.MAX_VALUE;
int assignments = 0;
for (int i = 0; i < size; i++) {
int element = array[i];
if (element < min) {
min = element;
assignments++;
}
}
return assignments;
}
private static void printResults(int iterations) {
System.out.printf(Locale.US, "Results after %d iterations:%n", iterations);
for (int i = 0; i < NUM_SIZES; i++) {
int size = 2 << i;
double avg = (double) counts[i] / iterations;
System.out.printf(Locale.US,
"- size: %,11d --> avg. no of assignments: %5.2f%n", size, avg);
}
System.out.println();
}
private static double median(int[] count, int iterations) {
long[] longs = new long[iterations];
for (int i = 0; i < iterations; i++) {
longs[i] = count[i] * 10;
}
long median = ArrayUtils.median(longs);
return median / 10.0;
}
}
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]eu">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;
private long localVariableAssignments;
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;
}
public void incLocalVariableAssignments() {
localVariableAssignments++;
}
@Override
public String toString() {
return String.format(Locale.US,
"iterations = %,11d, comparisons = %,11d, " +
"reads = %,11d, writes = %,11d, var.assignments = %,11d",
iterations,
comparisons,
reads,
writes,
localVariableAssignments);
}
}
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;