From a8ef94149acceaaaf5143b234ef9c89c766fd30c Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 02:48:38 +0200 Subject: [PATCH 1/6] feat: implement Smooth Sort algorithm with detailed JavaDoc and test class --- .../com/thealgorithms/sorts/SmoothSort.java | 173 ++++++++++++++++++ .../thealgorithms/sorts/SmoothSortTest.java | 8 + 2 files changed, 181 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/SmoothSort.java create mode 100644 src/test/java/com/thealgorithms/sorts/SmoothSortTest.java diff --git a/src/main/java/com/thealgorithms/sorts/SmoothSort.java b/src/main/java/com/thealgorithms/sorts/SmoothSort.java new file mode 100644 index 000000000000..7e881e84ee62 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/SmoothSort.java @@ -0,0 +1,173 @@ +package com.thealgorithms.sorts; + +/** + * Smooth Sort is an in-place, comparison-based sorting algorithm proposed by Edsger W. Dijkstra (1981). + * + *

It can be viewed as a variant of heapsort that maintains a forest of heap-ordered Leonardo trees + * (trees whose sizes are Leonardo numbers). The algorithm is adaptive: when the input is already + * sorted or nearly sorted, the heap invariants are often satisfied and the expensive rebalancing + * operations do little work, yielding near-linear behavior. + * + *

Time Complexity: + *

+ * + *

Space Complexity: O(1) auxiliary space (in-place). + * + * @see Smoothsort + * @see Leonardo numbers + * @see SortAlgorithm + */ +public class SmoothSort implements SortAlgorithm { + + /** + * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that + * fits into a signed 32-bit integer. + */ + private static final int[] LEONARDO = { + 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, 1402817465 + }; + + /** + * Sorts the given array in ascending order using Smooth Sort. + * + * @param array the array to sort + * @param the element type + * @return the sorted array + */ + @Override + public > T[] sort(final T[] array) { + if (array.length < 2) { + return array; + } + + final int last = array.length - 1; + + // The forest shape is encoded as (p, pshift): p is a bit-vector of present tree orders, + // shifted right by pshift. pshift is the order of the rightmost (current) Leonardo tree. + long p = 1L; + int pshift = 1; + + int head = 0; + while (head < last) { + if ((p & 3L) == 3L) { + sift(array, pshift, head); + p >>>= 2; + pshift += 2; + } else { + // Add a new singleton tree; if it will not be merged anymore, we must fully trinkle. + if (LEONARDO[pshift - 1] >= last - head) { + trinkle(array, p, pshift, head, false); + } else { + // This tree will be merged later, so it is enough to restore its internal heap property. + sift(array, pshift, head); + } + + if (pshift == 1) { + // If L(1) is used, the new singleton is L(0). + p <<= 1; + pshift = 0; + } else { + // Otherwise, shift to order 1 and append a singleton of order 1. + p <<= (pshift - 1); + pshift = 1; + } + } + + p |= 1L; + head++; + } + + trinkle(array, p, pshift, head, false); + + // Repeatedly remove the maximum (always at head) by shrinking the heap region. + while (pshift != 1 || p != 1L) { + if (pshift <= 1) { + // Rightmost tree is a singleton (order 0 or 1). Move to the previous tree root. + final long mask = p & ~1L; + final int shift = Long.numberOfTrailingZeros(mask); + p >>>= shift; + pshift += shift; + } else { + // Split a tree of order (pshift) into two children trees of orders (pshift-1) and (pshift-2). + p <<= 2; + p ^= 7L; + pshift -= 2; + + trinkle(array, p >>> 1, pshift + 1, head - LEONARDO[pshift] - 1, true); + trinkle(array, p, pshift, head - 1, true); + } + + head--; + } + + return array; + } + + private static > void sift(final T[] array, int order, int root) { + final T value = array[root]; + + while (order > 1) { + final int right = root - 1; + final int left = root - 1 - LEONARDO[order - 2]; + + if (!SortUtils.less(value, array[left]) && !SortUtils.less(value, array[right])) { + break; + } + + if (!SortUtils.less(array[left], array[right])) { + array[root] = array[left]; + root = left; + order -= 1; + } else { + array[root] = array[right]; + root = right; + order -= 2; + } + } + + array[root] = value; + } + + private static > void trinkle(final T[] array, long p, int order, int root, boolean trusty) { + final T value = array[root]; + + while (p != 1L) { + final int stepson = root - LEONARDO[order]; + + if (!SortUtils.less(value, array[stepson])) { + break; + } + + if (!trusty && order > 1) { + final int right = root - 1; + final int left = root - 1 - LEONARDO[order - 2]; + + if (!SortUtils.less(array[right], array[stepson]) || !SortUtils.less(array[left], array[stepson])) { + break; + } + } + + array[root] = array[stepson]; + root = stepson; + + final long mask = p & ~1L; + final int shift = Long.numberOfTrailingZeros(mask); + p >>>= shift; + order += shift; + trusty = false; + } + + if (!trusty) { + array[root] = value; + sift(array, order, root); + } + } +} diff --git a/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java new file mode 100644 index 000000000000..8df0502e80e7 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java @@ -0,0 +1,8 @@ +package com.thealgorithms.sorts; + +public class SmoothSortTest extends SortingAlgorithmTest { + @Override + SortAlgorithm getSortAlgorithm() { + return new SmoothSort(); + } +} From 8bf1a0ea22f42dcb5ee88d004d7e1a27fceec26d Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 03:46:48 +0200 Subject: [PATCH 2/6] style: format LEONARDO array for improved readability with clang-format --- src/main/java/com/thealgorithms/sorts/SmoothSort.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/thealgorithms/sorts/SmoothSort.java b/src/main/java/com/thealgorithms/sorts/SmoothSort.java index 7e881e84ee62..c45d6f1f02b2 100644 --- a/src/main/java/com/thealgorithms/sorts/SmoothSort.java +++ b/src/main/java/com/thealgorithms/sorts/SmoothSort.java @@ -27,13 +27,8 @@ public class SmoothSort implements SortAlgorithm { * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that * fits into a signed 32-bit integer. */ - private static final int[] LEONARDO = { - 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, 1402817465 - }; + private static final int[] LEONARDO = {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, 1402817465}; /** * Sorts the given array in ascending order using Smooth Sort. From 2c340c82ad8afb5f85dd442b6cd228b838f74ba7 Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 04:41:37 +0200 Subject: [PATCH 3/6] feat(sorts): add TournamentSort (winner-tree) --- .../thealgorithms/sorts/TournamentSort.java | 84 +++++++++++++++++++ .../sorts/TournamentSortTest.java | 9 ++ 2 files changed, 93 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/TournamentSort.java create mode 100644 src/test/java/com/thealgorithms/sorts/TournamentSortTest.java diff --git a/src/main/java/com/thealgorithms/sorts/TournamentSort.java b/src/main/java/com/thealgorithms/sorts/TournamentSort.java new file mode 100644 index 000000000000..ec51a1e2c0a9 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/TournamentSort.java @@ -0,0 +1,84 @@ +package com.thealgorithms.sorts; + +import java.util.Arrays; + +/** + * Tournament Sort algorithm implementation. + * + * Tournament sort builds a winner tree (a complete binary tree storing the index + * of the smallest element in each subtree). It then repeatedly extracts the + * winner (minimum) and updates the path from the removed leaf to the root. + * + * Time Complexity: + * - Best case: O(n log n) + * - Average case: O(n log n) + * - Worst case: O(n log n) + * + * Space Complexity: O(n) – additional winner-tree storage + * + * @see Tournament Sort Algorithm + * @see SortAlgorithm + */ +public class TournamentSort implements SortAlgorithm { + + @Override + public > T[] sort(T[] array) { + if (array == null || array.length < 2) { + return array; + } + + final int n = array.length; + final int leafCount = nextPowerOfTwo(n); + + // Winner tree represented as an array: + // - Leaves live at [leafCount .. 2*leafCount) + // - Internal nodes live at [1 .. leafCount) + // Each node stores an index into the original array or -1 for "empty". + final int[] tree = new int[2 * leafCount]; + Arrays.fill(tree, -1); + + for (int i = 0; i < n; i++) { + tree[leafCount + i] = i; + } + + for (int node = leafCount - 1; node >= 1; node--) { + tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]); + } + + final T[] result = array.clone(); + for (int out = 0; out < n; out++) { + final int winner = tree[1]; + result[out] = array[winner]; + + int node = leafCount + winner; + tree[node] = -1; + + for (node /= 2; node >= 1; node /= 2) { + tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]); + } + } + + System.arraycopy(result, 0, array, 0, n); + return array; + } + + private static int nextPowerOfTwo(int n) { + int power = 1; + while (power < n) { + power <<= 1; + } + return power; + } + + private static > int winnerIndex(T[] array, int leftIndex, int rightIndex) { + if (leftIndex == -1) { + return rightIndex; + } + if (rightIndex == -1) { + return leftIndex; + } + + // If equal, prefer the left element to keep ordering deterministic. + return SortUtils.less(array[rightIndex], array[leftIndex]) ? rightIndex : leftIndex; + } +} diff --git a/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java new file mode 100644 index 000000000000..cf3d6b5f04e2 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java @@ -0,0 +1,9 @@ +package com.thealgorithms.sorts; + +public class TournamentSortTest extends SortingAlgorithmTest { + + @Override + SortAlgorithm getSortAlgorithm() { + return new TournamentSort(); + } +} From d658f1bae57924fddf0cacaea9d080901f26fccb Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:13:20 +0200 Subject: [PATCH 4/6] test: add unit test for null array handling in TournamentSort --- .../com/thealgorithms/sorts/TournamentSortTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java index cf3d6b5f04e2..91da746447a8 100644 --- a/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java @@ -1,7 +1,17 @@ package com.thealgorithms.sorts; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + public class TournamentSortTest extends SortingAlgorithmTest { + @Test + void shouldAcceptWhenNullArrayIsPassed() { + Integer[] array = null; + assertNull(getSortAlgorithm().sort(array)); + } + @Override SortAlgorithm getSortAlgorithm() { return new TournamentSort(); From 591ef3e1e447be307c3511938583fc6d0c80026d Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:46:15 +0200 Subject: [PATCH 5/6] feat(search): add rotated binary search --- .../searches/RotatedBinarySearch.java | 60 +++++++++++++++++++ .../searches/RotatedBinarySearchTest.java | 46 ++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java create mode 100644 src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java diff --git a/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java new file mode 100644 index 000000000000..86099b2fa2fa --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java @@ -0,0 +1,60 @@ +package com.thealgorithms.searches; + +import com.thealgorithms.devutils.searches.SearchAlgorithm; + +/** + * Searches for a key in a sorted array that has been rotated at an unknown pivot. + * + *

+ * Example: + * {@code [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]} + * + *

+ * This is a modified binary search. When the array contains no duplicates, the + * time complexity is {@code O(log n)}. With duplicates, the algorithm still + * works but may degrade to {@code O(n)} in the worst case. + * + * @see Search in rotated sorted array + * @see SearchAlgorithm + */ +public final class RotatedBinarySearch implements SearchAlgorithm { + + @Override + public > int find(T[] array, T key) { + int left = 0; + int right = array.length - 1; + + while (left <= right) { + int middle = (left + right) >>> 1; + int cmp = key.compareTo(array[middle]); + if (cmp == 0) { + return middle; + } + + // Handle duplicates: if we cannot determine which side is sorted. + if (array[left].compareTo(array[middle]) == 0 && array[middle].compareTo(array[right]) == 0) { + left++; + right--; + continue; + } + + // Left half is sorted. + if (array[left].compareTo(array[middle]) <= 0) { + if (array[left].compareTo(key) <= 0 && key.compareTo(array[middle]) < 0) { + right = middle - 1; + } else { + left = middle + 1; + } + } else { + // Right half is sorted. + if (array[middle].compareTo(key) < 0 && key.compareTo(array[right]) <= 0) { + left = middle + 1; + } else { + right = middle - 1; + } + } + } + + return -1; + } +} diff --git a/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java new file mode 100644 index 000000000000..43ba39e6380f --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class RotatedBinarySearchTest { + + @Test + void shouldFindElementInRotatedArrayLeftSide() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7}; + assertEquals(2, search.find(array, 10)); + } + + @Test + void shouldFindElementInRotatedArrayRightSide() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7}; + assertEquals(6, search.find(array, 2)); + } + + @Test + void shouldFindElementInNotRotatedArray() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {1, 2, 3, 4, 5, 6, 7}; + assertEquals(4, search.find(array, 5)); + } + + @Test + void shouldReturnMinusOneWhenNotFound() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {4, 5, 6, 7, 0, 1, 2}; + assertEquals(-1, search.find(array, 3)); + } + + @Test + void shouldHandleDuplicates() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {2, 2, 2, 3, 4, 2}; + int index = search.find(array, 3); + assertTrue(index >= 0 && index < array.length); + assertEquals(3, array[index]); + } +} From f02864126811b2ae6687b8c6d0df38b003426e30 Mon Sep 17 00:00:00 2001 From: Ahmed Allam <60698204+AllamF5J@users.noreply.github.com> Date: Fri, 9 Jan 2026 06:13:30 +0200 Subject: [PATCH 6/6] test: add unit test for handling middle element in right sorted half of rotated array --- .../thealgorithms/searches/RotatedBinarySearchTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java index 43ba39e6380f..1e6ab4c37fcc 100644 --- a/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java +++ b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java @@ -35,6 +35,13 @@ void shouldReturnMinusOneWhenNotFound() { assertEquals(-1, search.find(array, 3)); } + @Test + void shouldHandleWhenMiddleIsGreaterThanKeyInRightSortedHalf() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {6, 7, 0, 1, 2, 3, 4, 5}; + assertEquals(2, search.find(array, 0)); + } + @Test void shouldHandleDuplicates() { RotatedBinarySearch search = new RotatedBinarySearch();