Skip to content

Commit bb157c1

Browse files
committed
major: adding the missing algorithm
1 parent f9ce5c0 commit bb157c1

2 files changed

Lines changed: 171 additions & 29 deletions

File tree

Lines changed: 145 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.thealgorithms.sorts;
22

3+
import java.util.Arrays;
4+
35
/**
4-
* Library Sort (also known as Gapped Insertion Sort) is traditionally implemented
5-
* using periodic gaps between elements for faster insertion. This implementation
6-
* uses binary search to find the insertion position combined with array shifting,
7-
* which is a simplified variant without gap-based optimization.
8-
* Time Complexity: O(n^2) worst case due to element shifting
6+
* Library Sort (also known as Gapped Insertion Sort) maintains a sparse
7+
* working array with gaps distributed between elements, so that most
8+
* insertions land directly in an empty gap without shifting anything.
9+
* Elements are inserted in rounds that double in size (1, 2, 4, 8, ...);
10+
* after each round the array is rebalanced so gaps are spread out evenly
11+
* again for the next round.
12+
* Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse
913
* Space Complexity: O(n)
1014
*
1115
* @see <a href="https://en.wikipedia.org/wiki/Library_sort">
@@ -14,6 +18,8 @@
1418
*/
1519
public final class LibrarySort {
1620

21+
private static final int GAP_FACTOR = 2;
22+
1723
private LibrarySort() {
1824
// Utility class
1925
}
@@ -33,49 +39,160 @@ public static int[] sort(final int[] array) {
3339
return array;
3440
}
3541

36-
int n = array.length;
37-
Integer[] spaced = new Integer[2 * n];
42+
final int n = array.length;
43+
final int capacity = GAP_FACTOR * n;
44+
final int[] data = new int[capacity];
45+
final boolean[] occupied = new boolean[capacity];
3846

39-
spaced[0] = array[0];
40-
int inserted = 1;
47+
final int mid = capacity / 2;
48+
data[mid] = array[0];
49+
occupied[mid] = true;
4150

42-
for (int i = 1; i < n; i++) {
43-
int pos = binarySearch(spaced, inserted, array[i]);
44-
for (int j = inserted; j > pos; j--) {
45-
spaced[j] = spaced[j - 1];
51+
int filled = 1;
52+
int nextToInsert = 1;
53+
int round = 0;
54+
while (nextToInsert < n) {
55+
final int roundSize = Math.min(1 << round, n - nextToInsert);
56+
for (int i = 0; i < roundSize; i++) {
57+
insert(data, occupied, array[nextToInsert + i]);
58+
filled++;
59+
}
60+
nextToInsert += roundSize;
61+
round++;
62+
if (nextToInsert < n) {
63+
rebalance(data, occupied, filled);
4664
}
47-
spaced[pos] = array[i];
48-
inserted++;
4965
}
5066

5167
int idx = 0;
52-
for (int i = 0; i < 2 * n; i++) {
53-
if (spaced[i] != null) {
54-
array[idx++] = spaced[i];
68+
for (int i = 0; i < capacity; i++) {
69+
if (occupied[i]) {
70+
array[idx++] = data[i];
5571
}
5672
}
5773
return array;
5874
}
5975

6076
/**
61-
* Binary search to find insertion position among inserted elements.
62-
*
63-
* @param spaced the spaced array
64-
* @param inserted number of elements inserted so far
65-
* @param target the value to find position for
66-
* @return the correct insertion index
77+
* Inserts {@code value} into the gapped array, placing it directly in an
78+
* empty gap when possible, otherwise shifting toward the nearest gap.
79+
*/
80+
private static void insert(final int[] data, final boolean[] occupied, final int value) {
81+
final int pos = findInsertionIndex(data, occupied, value);
82+
if (pos >= data.length) {
83+
insertAtEnd(data, occupied, value);
84+
return;
85+
}
86+
87+
if (!occupied[pos]) {
88+
data[pos] = value;
89+
occupied[pos] = true;
90+
return;
91+
}
92+
93+
int right = pos;
94+
while (right < data.length && occupied[right]) {
95+
right++;
96+
}
97+
int left = pos - 1;
98+
while (left >= 0 && occupied[left]) {
99+
left--;
100+
}
101+
102+
final boolean canGoRight = right < data.length;
103+
final boolean canGoLeft = left >= 0;
104+
105+
if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) {
106+
// Shift data[pos, right) one slot to the right, opening a gap at pos.
107+
// occupied[pos] is untouched by the copy and was already true.
108+
System.arraycopy(data, pos, data, pos + 1, right - pos);
109+
occupied[right] = true;
110+
data[pos] = value;
111+
} else if (canGoLeft) {
112+
// Shift data[left + 1, pos) one slot to the left, opening a gap at pos - 1.
113+
// occupied[pos - 1] is untouched by the copy and was already true.
114+
System.arraycopy(data, left + 1, data, left, pos - 1 - left);
115+
occupied[left] = true;
116+
data[pos - 1] = value;
117+
} else {
118+
throw new IllegalStateException("No gap available for insertion; rebalance too infrequent.");
119+
}
120+
}
121+
122+
/**
123+
* Handles insertion of a new global maximum, which must land after every
124+
* currently occupied slot. Since there is no room to its right, this
125+
* shifts occupied slots left into the nearest gap instead.
126+
*/
127+
private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) {
128+
final int last = data.length - 1;
129+
if (!occupied[last]) {
130+
data[last] = value;
131+
occupied[last] = true;
132+
return;
133+
}
134+
int left = last - 1;
135+
while (left >= 0 && occupied[left]) {
136+
left--;
137+
}
138+
if (left < 0) {
139+
throw new IllegalStateException("No gap available for insertion; rebalance too infrequent.");
140+
}
141+
// Shift data[left + 1, last] one slot to the left, opening a gap at last.
142+
// occupied[last] is untouched by the copy and was already true.
143+
System.arraycopy(data, left + 1, data, left, last - left);
144+
occupied[left] = true;
145+
data[last] = value;
146+
}
147+
148+
/**
149+
* Finds the leftmost index at which {@code value} can be inserted so
150+
* that occupied slots remain sorted. Empty slots are compared using the
151+
* value of the nearest occupied slot at or after them, which is a
152+
* monotonic function of index and therefore safe to binary search over.
67153
*/
68-
private static int binarySearch(final Integer[] spaced, final int inserted, final int target) {
154+
private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) {
69155
int lo = 0;
70-
int hi = inserted;
156+
int hi = data.length;
71157
while (lo < hi) {
72-
int mid = lo + (hi - lo) / 2;
73-
if (spaced[mid] <= target) {
158+
final int mid = lo + (hi - lo) / 2;
159+
final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid);
160+
if (probe != Integer.MAX_VALUE && probe <= value) {
74161
lo = mid + 1;
75162
} else {
76163
hi = mid;
77164
}
78165
}
79166
return lo;
80167
}
168+
169+
private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) {
170+
for (int i = index; i < data.length; i++) {
171+
if (occupied[i]) {
172+
return data[i];
173+
}
174+
}
175+
return Integer.MAX_VALUE;
176+
}
177+
178+
/**
179+
* Redistributes the {@code filled} occupied elements evenly across the
180+
* full capacity of {@code data}, restoring uniform gaps between them.
181+
*/
182+
private static void rebalance(final int[] data, final boolean[] occupied, final int filled) {
183+
final int capacity = data.length;
184+
final int[] temp = new int[filled];
185+
int idx = 0;
186+
for (int i = 0; i < capacity; i++) {
187+
if (occupied[i]) {
188+
temp[idx++] = data[i];
189+
}
190+
}
191+
Arrays.fill(occupied, false);
192+
for (int k = 0; k < filled; k++) {
193+
final int pos = (int) ((long) k * capacity / filled);
194+
data[pos] = temp[k];
195+
occupied[pos] = true;
196+
}
197+
}
81198
}

src/test/java/com/thealgorithms/sorts/LibrarySortTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
package com.thealgorithms.sorts;
2-
// author: Vraj Prajapati @Rosander0
32

43
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
54
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -42,4 +41,30 @@ public void testEmptyArray() {
4241
public void testNullArray() {
4342
assertThrows(IllegalArgumentException.class, () -> LibrarySort.sort(null));
4443
}
44+
45+
// --- Added to cover branches the tests above never reach ---
46+
47+
@Test
48+
public void testShiftLeftWhenRightSideIsFull() {
49+
// Right side of the target slot is completely occupied, forcing a left shift.
50+
assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6}, LibrarySort.sort(new int[] {0, 1, 2, 6, 4, 5, 3}));
51+
}
52+
53+
@Test
54+
public void testTieBreakPrefersRightWhenDistancesEqual() {
55+
// A gap exists on both sides at equal distance; algorithm should favor the right shift.
56+
assertArrayEquals(new int[] {0, 1, 2, 3}, LibrarySort.sort(new int[] {0, 1, 3, 2}));
57+
}
58+
59+
@Test
60+
public void testRightSearchRunsOffTheEnd() {
61+
// No gap anywhere to the right of the target slot, all the way to the array's end.
62+
assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 7, 6}));
63+
}
64+
65+
@Test
66+
public void testInsertAtEndWithNoTrailingGap() {
67+
// A new global maximum arrives with no trailing gap left, forcing insertAtEnd().
68+
assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 6, 7}));
69+
}
4570
}

0 commit comments

Comments
 (0)