11package 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">
1418 */
1519public final class LibrarySort {
1620
21+ private static final int GAP_FACTOR = 2 ;
22+
1723 private LibrarySort () {
1824 // Utility class
1925 }
@@ -33,49 +39,169 @@ 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+ // Unreachable in practice: canGoRight and canGoLeft can only both be false if
119+ // every slot in this capacity-2n array is occupied, but at most n elements are
120+ // ever present at once. Kept as a defensive guard against that invariant breaking.
121+ throw new IllegalStateException ("No gap available for insertion; rebalance too infrequent." );
122+ }
123+ }
124+
125+ /**
126+ * Handles insertion of a new global maximum, which must land after every
127+ * currently occupied slot. Since there is no room to its right, this
128+ * shifts occupied slots left into the nearest gap instead.
129+ */
130+ private static void insertAtEnd (final int [] data , final boolean [] occupied , final int value ) {
131+ final int last = data .length - 1 ;
132+ // occupied[last] is unreachable as false here: insertAtEnd() is only called when
133+ // findInsertionIndex() returns data.length, which requires data[last] to already be
134+ // occupied. Kept as a defensive guard in case that invariant is ever broken.
135+ if (!occupied [last ]) {
136+ data [last ] = value ;
137+ occupied [last ] = true ;
138+ return ;
139+ }
140+ int left = last - 1 ;
141+ while (left >= 0 && occupied [left ]) {
142+ left --;
143+ }
144+ // left < 0 is unreachable in practice: at most n elements ever occupy this
145+ // capacity-2n array, so fewer than half the slots left of `last` can be filled,
146+ // guaranteeing a gap exists before the scan reaches index -1.
147+ if (left < 0 ) {
148+ throw new IllegalStateException ("No gap available for insertion; rebalance too infrequent." );
149+ }
150+ // Shift data[left + 1, last] one slot to the left, opening a gap at last.
151+ // occupied[last] is untouched by the copy and was already true.
152+ System .arraycopy (data , left + 1 , data , left , last - left );
153+ occupied [left ] = true ;
154+ data [last ] = value ;
155+ }
156+
157+ /**
158+ * Finds the leftmost index at which {@code value} can be inserted so
159+ * that occupied slots remain sorted. Empty slots are compared using the
160+ * value of the nearest occupied slot at or after them, which is a
161+ * monotonic function of index and therefore safe to binary search over.
67162 */
68- private static int binarySearch (final Integer [] spaced , final int inserted , final int target ) {
163+ private static int findInsertionIndex (final int [] data , final boolean [] occupied , final int value ) {
69164 int lo = 0 ;
70- int hi = inserted ;
165+ int hi = data . length ;
71166 while (lo < hi ) {
72- int mid = lo + (hi - lo ) / 2 ;
73- if (spaced [mid ] <= target ) {
167+ final int mid = lo + (hi - lo ) / 2 ;
168+ final int probe = nearestOccupiedValueAtOrAfter (data , occupied , mid );
169+ if (probe != Integer .MAX_VALUE && probe <= value ) {
74170 lo = mid + 1 ;
75171 } else {
76172 hi = mid ;
77173 }
78174 }
79175 return lo ;
80176 }
177+
178+ private static int nearestOccupiedValueAtOrAfter (final int [] data , final boolean [] occupied , final int index ) {
179+ for (int i = index ; i < data .length ; i ++) {
180+ if (occupied [i ]) {
181+ return data [i ];
182+ }
183+ }
184+ return Integer .MAX_VALUE ;
185+ }
186+
187+ /**
188+ * Redistributes the {@code filled} occupied elements evenly across the
189+ * full capacity of {@code data}, restoring uniform gaps between them.
190+ */
191+ private static void rebalance (final int [] data , final boolean [] occupied , final int filled ) {
192+ final int capacity = data .length ;
193+ final int [] temp = new int [filled ];
194+ int idx = 0 ;
195+ for (int i = 0 ; i < capacity ; i ++) {
196+ if (occupied [i ]) {
197+ temp [idx ++] = data [i ];
198+ }
199+ }
200+ Arrays .fill (occupied , false );
201+ for (int k = 0 ; k < filled ; k ++) {
202+ final int pos = (int ) ((long ) k * capacity / filled );
203+ data [pos ] = temp [k ];
204+ occupied [pos ] = true ;
205+ }
206+ }
81207}
0 commit comments