From f9ce5c0f449bd3f528e2f25aac6b921920d4dc56 Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Tue, 16 Jun 2026 11:11:35 +0530 Subject: [PATCH 1/2] feat: add LibrarySort implementation --- .../com/thealgorithms/sorts/LibrarySort.java | 81 +++++++++++++++++++ .../thealgorithms/sorts/LibrarySortTest.java | 45 +++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/main/java/com/thealgorithms/sorts/LibrarySort.java create mode 100644 src/test/java/com/thealgorithms/sorts/LibrarySortTest.java diff --git a/src/main/java/com/thealgorithms/sorts/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java new file mode 100644 index 000000000000..d978cd762d71 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java @@ -0,0 +1,81 @@ +package com.thealgorithms.sorts; + +/** + * Library Sort (also known as Gapped Insertion Sort) is traditionally implemented + * using periodic gaps between elements for faster insertion. This implementation + * uses binary search to find the insertion position combined with array shifting, + * which is a simplified variant without gap-based optimization. + * Time Complexity: O(n^2) worst case due to element shifting + * Space Complexity: O(n) + * + * @see + * Wikipedia: Library Sort + * @author Vraj Prajapati (@Rosander0) + */ +public final class LibrarySort { + + private LibrarySort() { + // Utility class + } + + /** + * Sorts an array using the Library Sort algorithm. + * + * @param array the array to sort (must not be null) + * @return the sorted array + * @throws IllegalArgumentException if {@code array} is {@code null} + */ + public static int[] sort(final int[] array) { + if (array == null) { + throw new IllegalArgumentException("Input array must not be null."); + } + if (array.length <= 1) { + return array; + } + + int n = array.length; + Integer[] spaced = new Integer[2 * n]; + + spaced[0] = array[0]; + int inserted = 1; + + for (int i = 1; i < n; i++) { + int pos = binarySearch(spaced, inserted, array[i]); + for (int j = inserted; j > pos; j--) { + spaced[j] = spaced[j - 1]; + } + spaced[pos] = array[i]; + inserted++; + } + + int idx = 0; + for (int i = 0; i < 2 * n; i++) { + if (spaced[i] != null) { + array[idx++] = spaced[i]; + } + } + return array; + } + + /** + * Binary search to find insertion position among inserted elements. + * + * @param spaced the spaced array + * @param inserted number of elements inserted so far + * @param target the value to find position for + * @return the correct insertion index + */ + private static int binarySearch(final Integer[] spaced, final int inserted, final int target) { + int lo = 0; + int hi = inserted; + while (lo < hi) { + int mid = lo + (hi - lo) / 2; + if (spaced[mid] <= target) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + } +} diff --git a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java new file mode 100644 index 000000000000..87df9ef09b5f --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java @@ -0,0 +1,45 @@ +package com.thealgorithms.sorts; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class LibrarySortTest { + + @Test + public void testBasicSort() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 3, 1, 4, 2})); + } + + @Test + public void testAlreadySorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {1, 2, 3, 4, 5})); + } + + @Test + public void testReverseSorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 4, 3, 2, 1})); + } + + @Test + public void testDuplicates() { + assertArrayEquals(new int[] {1, 2, 2, 3, 3}, LibrarySort.sort(new int[] {3, 2, 1, 3, 2})); + } + + @Test + public void testSingleElement() { + assertArrayEquals(new int[] {1}, LibrarySort.sort(new int[] {1})); + } + + @Test + public void testEmptyArray() { + assertArrayEquals(new int[] {}, LibrarySort.sort(new int[] {})); + } + + @Test + public void testNullArray() { + assertThrows(IllegalArgumentException.class, () -> LibrarySort.sort(null)); + } +} From 0af0297129256083b5420878361d52a4c023ff5c Mon Sep 17 00:00:00 2001 From: Rosander0 Date: Sun, 5 Jul 2026 11:06:46 +0530 Subject: [PATCH 2/2] major: adding the missing algorithm --- .../com/thealgorithms/sorts/LibrarySort.java | 174 +++++++++++++++--- .../thealgorithms/sorts/LibrarySortTest.java | 1 - 2 files changed, 146 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/thealgorithms/sorts/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java index d978cd762d71..85762dc3e46b 100644 --- a/src/main/java/com/thealgorithms/sorts/LibrarySort.java +++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java @@ -1,11 +1,13 @@ package com.thealgorithms.sorts; /** - * Library Sort (also known as Gapped Insertion Sort) is traditionally implemented - * using periodic gaps between elements for faster insertion. This implementation - * uses binary search to find the insertion position combined with array shifting, - * which is a simplified variant without gap-based optimization. - * Time Complexity: O(n^2) worst case due to element shifting + * Library Sort (also known as Gapped Insertion Sort) maintains a sparse + * working array with gaps distributed between elements, so that most + * insertions land directly in an empty gap without shifting anything. + * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...); + * after each round the array is rebalanced so gaps are spread out evenly + * again for the next round. + * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse * Space Complexity: O(n) * * @see @@ -14,6 +16,8 @@ */ public final class LibrarySort { + private static final int GAP_FACTOR = 2; + private LibrarySort() { // Utility class } @@ -33,44 +37,128 @@ public static int[] sort(final int[] array) { return array; } - int n = array.length; - Integer[] spaced = new Integer[2 * n]; + final int n = array.length; + final int capacity = GAP_FACTOR * n; + final int[] data = new int[capacity]; + final boolean[] occupied = new boolean[capacity]; - spaced[0] = array[0]; - int inserted = 1; + final int mid = capacity / 2; + data[mid] = array[0]; + occupied[mid] = true; - for (int i = 1; i < n; i++) { - int pos = binarySearch(spaced, inserted, array[i]); - for (int j = inserted; j > pos; j--) { - spaced[j] = spaced[j - 1]; + int filled = 1; + int nextToInsert = 1; + int round = 0; + while (nextToInsert < n) { + final int roundSize = Math.min(1 << round, n - nextToInsert); + for (int i = 0; i < roundSize; i++) { + insert(data, occupied, array[nextToInsert + i]); + filled++; + } + nextToInsert += roundSize; + round++; + if (nextToInsert < n) { + rebalance(data, occupied, filled); } - spaced[pos] = array[i]; - inserted++; } int idx = 0; - for (int i = 0; i < 2 * n; i++) { - if (spaced[i] != null) { - array[idx++] = spaced[i]; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + array[idx++] = data[i]; } } return array; } /** - * Binary search to find insertion position among inserted elements. - * - * @param spaced the spaced array - * @param inserted number of elements inserted so far - * @param target the value to find position for - * @return the correct insertion index + * Inserts {@code value} into the gapped array, placing it directly in an + * empty gap when possible, otherwise shifting toward the nearest gap. + */ + private static void insert(final int[] data, final boolean[] occupied, final int value) { + final int pos = findInsertionIndex(data, occupied, value); + if (pos >= data.length) { + insertAtEnd(data, occupied, value); + return; + } + + if (!occupied[pos]) { + data[pos] = value; + occupied[pos] = true; + return; + } + + int right = pos; + while (right < data.length && occupied[right]) { + right++; + } + int left = pos - 1; + while (left >= 0 && occupied[left]) { + left--; + } + + final boolean canGoRight = right < data.length; + final boolean canGoLeft = left >= 0; + + if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) { + for (int j = right; j > pos; j--) { + data[j] = data[j - 1]; + occupied[j] = true; + } + data[pos] = value; + occupied[pos] = true; + } else if (canGoLeft) { + for (int j = left; j < pos - 1; j++) { + data[j] = data[j + 1]; + } + occupied[left] = true; + data[pos - 1] = value; + occupied[pos - 1] = true; + } else { + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + } + + /** + * Handles insertion of a new global maximum, which must land after every + * currently occupied slot. Since there is no room to its right, this + * shifts occupied slots left into the nearest gap instead. + */ + private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) { + final int last = data.length - 1; + if (!occupied[last]) { + data[last] = value; + occupied[last] = true; + return; + } + int left = last - 1; + while (left >= 0 && occupied[left]) { + left--; + } + if (left < 0) { + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + for (int j = left; j < last; j++) { + data[j] = data[j + 1]; + } + occupied[left] = true; + data[last] = value; + occupied[last] = true; + } + + /** + * Finds the leftmost index at which {@code value} can be inserted so + * that occupied slots remain sorted. Empty slots are compared using the + * value of the nearest occupied slot at or after them, which is a + * monotonic function of index and therefore safe to binary search over. */ - private static int binarySearch(final Integer[] spaced, final int inserted, final int target) { + private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) { int lo = 0; - int hi = inserted; + int hi = data.length; while (lo < hi) { - int mid = lo + (hi - lo) / 2; - if (spaced[mid] <= target) { + final int mid = lo + (hi - lo) / 2; + final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid); + if (probe != Integer.MAX_VALUE && probe <= value) { lo = mid + 1; } else { hi = mid; @@ -78,4 +166,34 @@ private static int binarySearch(final Integer[] spaced, final int inserted, fina } return lo; } + + private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) { + for (int i = index; i < data.length; i++) { + if (occupied[i]) { + return data[i]; + } + } + return Integer.MAX_VALUE; + } + + /** + * Redistributes the {@code filled} occupied elements evenly across the + * full capacity of {@code data}, restoring uniform gaps between them. + */ + private static void rebalance(final int[] data, final boolean[] occupied, final int filled) { + final int capacity = data.length; + final int[] temp = new int[filled]; + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + temp[idx++] = data[i]; + occupied[i] = false; + } + } + for (int k = 0; k < filled; k++) { + final int pos = (int) ((long) k * capacity / filled); + data[pos] = temp[k]; + occupied[pos] = true; + } + } } diff --git a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java index 87df9ef09b5f..bb14db36fe95 100644 --- a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java +++ b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java @@ -1,5 +1,4 @@ package com.thealgorithms.sorts; -// author: Vraj Prajapati @Rosander0 import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows;