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,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}
0 commit comments