Skip to content
148 changes: 148 additions & 0 deletions src/main/java/com/thealgorithms/sorts/SmoothSort.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.thealgorithms.sorts;

/**
* Implements the Smoothsort algorithm created by Edsger W. Dijkstra.
*
* <p>Smoothsort is an adaptive comparison sorting algorithm. It is a variation of
* heapsort that can efficiently sort arrays that are already substantially
* sorted, approaching an O(n) time complexity in the best case. Its worst-case
* time complexity is O(n log n).
*
* <p>The algorithm works by building a series of "Leonardo heaps", which are
* heaps that obey a specific size constraint based on Leonardo numbers.
* The list is first partitioned into this implicit sequence of heaps, and then
* the largest element is repeatedly extracted and placed in its final sorted
* position.
*
* @see <a href="https://en.wikipedia.org/wiki/Smoothsort">Wikipedia: Smoothsort</a>
*/
public final class SmoothSort {
// clang-format off

/** Private constructor to prevent instantiation of this utility class. */
private SmoothSort() {}

// Leonardo numbers: L(0)=1, L(1)=1, L(k)=L(k-1)+L(k-2)+1
private static final int[] LP = {
1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193,
5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835,
635621, 1028457, 1664079, 2692537, 4356617, 7049155, 11405773, 18454929,
29860703, 48315633, 78176337, 126491971, 204668309, 331160281, 535828591,
866988873,
};

/**
* Sorts an array in ascending order using the Smoothsort algorithm.
*
* @param <T> the type of elements in the array, which must be comparable.
* @param arr the array to be sorted.
*/
public static <T extends Comparable<T>> void sort(T[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int n = arr.length;

int q = 0; // Order of the rightmost heap
long p = 1; // Bitmask representing heap sizes
int head = 0; // Current index in array

while (head < n) {
if ((p & 3) == 3) {
p >>>= 2;
q += 2;
} else {
if ((p & 1) == 0) {
p = (p << 1) | 1;
q = 1;
} else {
p <<= 1;
q = 0;
}
}

// Prevent out-of-bounds heap building
while (head + LP[q] > n) {
p >>>= 1;
q--;
}

trinkle(arr, head, p, q);
head++;
}

// Sort the heaps
while (q != 0 || p != 1) {
if (q <= 1) {
// Step back through the heaps
while ((p & 1) == 0) {
p >>>= 1;
q++;
}
p >>>= 1;
} else {
p &= ~(1L << q);
q--;
int right = head - 1;
int left = head - 1 - LP[q];
sift(arr, left + LP[q], q);
sift(arr, right, q);
}
head--;
}
}

private static <T extends Comparable<T>> void trinkle(T[] arr, int head, long p, int q) {
while (q > 1) {
int parent = head - LP[q];
if (parent < 0) {
return; // prevent negative index
}

if (arr[head].compareTo(arr[parent]) <= 0) {
break;
}

swap(arr, head, parent);
head = parent;
q -= 2;
}
sift(arr, head, q);
}

private static <T extends Comparable<T>> void sift(T[] arr, int head, int order) {
while (order >= 2) {
int rightChild = head - 1;
int leftChild = head - 1 - LP[order - 2];

if (leftChild < 0 || rightChild < 0) {
return; // safety check
}

int largerChild =
arr[leftChild].compareTo(arr[rightChild]) >= 0 ? leftChild : rightChild;

if (arr[head].compareTo(arr[largerChild]) >= 0) {
break;
}

swap(arr, head, largerChild);
head = largerChild;

if (largerChild == leftChild) {
order -= 1;
} else {
order -= 2;
}
}
}

private static <T> void swap(T[] arr, int i, int j) {
if (i == j) return;
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}

// clang-format on
}
82 changes: 82 additions & 0 deletions src/test/java/com/thealgorithms/sorts/SmoothSortTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.thealgorithms.sorts;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
* Unit tests for the SmoothSort class.
*/
final class SmoothSortTest {

@Test
@DisplayName("Test with an empty array")
void testSortEmptyArray() {
Integer[] array = {};
SmoothSort.sort(array);
assertArrayEquals(new Integer[] {}, array);
}

@Test
@DisplayName("Test with a single-element array")
void testSortSingleElementArray() {
Integer[] array = {42};
SmoothSort.sort(array);
assertArrayEquals(new Integer[] {42}, array);
}

@Test
@DisplayName("Test with a simple unsorted array of integers")
void testSortSimpleIntegerArray() {
Integer[] array = {5, 8, 3, 1, 9, 4, 7, 2, 6};
Integer[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}

@Test
@DisplayName("Test with an already sorted array (best case)")
void testSortAlreadySortedArray() {
Integer[] array = {10, 20, 30, 40, 50};
Integer[] expected = {10, 20, 30, 40, 50};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}

@Test
@DisplayName("Test with a reverse sorted array (worst case)")
void testSortReverseSortedArray() {
Integer[] array = {55, 44, 33, 22, 11};
Integer[] expected = {11, 22, 33, 44, 55};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}

@Test
@DisplayName("Test with an array containing duplicate elements")
void testSortArrayWithDuplicates() {
Integer[] array = {7, 2, 9, 2, 5, 7, 9, 1, 5};
Integer[] expected = {1, 2, 2, 5, 5, 7, 7, 9, 9};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}

@Test
@DisplayName("Test with an array of strings")
void testSortStringArray() {
String[] array = {"banana", "apple", "cherry", "date", "fig"};
String[] expected = {"apple", "banana", "cherry", "date", "fig"};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}

@Test
@DisplayName("Test with an array containing negative numbers")
void testSortArrayWithNegativeNumbers() {
Integer[] array = {-5, 8, -3, 0, 9, -4, 7, 2, -6};
Integer[] expected = {-6, -5, -4, -3, 0, 2, 7, 8, 9};
SmoothSort.sort(array);
assertArrayEquals(expected, array);
}
}
Loading