11package com .thealgorithms .sorts ;
22
33/**
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
4+ * Library Sort (also known as Gapped Insertion Sort) maintains a sparse
5+ * working array with gaps distributed between elements, so that most
6+ * insertions land directly in an empty gap without shifting anything.
7+ * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...);
8+ * after each round the array is rebalanced so gaps are spread out evenly
9+ * again for the next round.
10+ * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse
911 * Space Complexity: O(n)
1012 *
1113 * @see <a href="https://en.wikipedia.org/wiki/Library_sort">
1416 */
1517public final class LibrarySort {
1618
19+ private static final int GAP_FACTOR = 2 ;
20+
1721 private LibrarySort () {
1822 // Utility class
1923 }
@@ -33,49 +37,163 @@ public static int[] sort(final int[] array) {
3337 return array ;
3438 }
3539
36- int n = array .length ;
37- Integer [] spaced = new Integer [2 * n ];
40+ final int n = array .length ;
41+ final int capacity = GAP_FACTOR * n ;
42+ final int [] data = new int [capacity ];
43+ final boolean [] occupied = new boolean [capacity ];
3844
39- spaced [0 ] = array [0 ];
40- int inserted = 1 ;
45+ final int mid = capacity / 2 ;
46+ data [mid ] = array [0 ];
47+ occupied [mid ] = true ;
4148
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 ];
49+ int filled = 1 ;
50+ int nextToInsert = 1 ;
51+ int round = 0 ;
52+ while (nextToInsert < n ) {
53+ final int roundSize = Math .min (1 << round , n - nextToInsert );
54+ for (int i = 0 ; i < roundSize ; i ++) {
55+ insert (data , occupied , array [nextToInsert + i ]);
56+ filled ++;
57+ }
58+ nextToInsert += roundSize ;
59+ round ++;
60+ if (nextToInsert < n ) {
61+ rebalance (data , occupied , filled );
4662 }
47- spaced [pos ] = array [i ];
48- inserted ++;
4963 }
5064
5165 int idx = 0 ;
52- for (int i = 0 ; i < 2 * n ; i ++) {
53- if (spaced [i ] != null ) {
54- array [idx ++] = spaced [i ];
66+ for (int i = 0 ; i < capacity ; i ++) {
67+ if (occupied [i ]) {
68+ array [idx ++] = data [i ];
5569 }
5670 }
5771 return array ;
5872 }
5973
6074 /**
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
75+ * Inserts {@code value} into the gapped array, placing it directly in an
76+ * empty gap when possible, otherwise shifting toward the nearest gap.
77+ */
78+ private static void insert (final int [] data , final boolean [] occupied , final int value ) {
79+ final int pos = findInsertionIndex (data , occupied , value );
80+ if (pos >= data .length ) {
81+ insertAtEnd (data , occupied , value );
82+ return ;
83+ }
84+
85+ if (!occupied [pos ]) {
86+ data [pos ] = value ;
87+ occupied [pos ] = true ;
88+ return ;
89+ }
90+
91+ int right = pos ;
92+ while (right < data .length && occupied [right ]) {
93+ right ++;
94+ }
95+ int left = pos - 1 ;
96+ while (left >= 0 && occupied [left ]) {
97+ left --;
98+ }
99+
100+ final boolean canGoRight = right < data .length ;
101+ final boolean canGoLeft = left >= 0 ;
102+
103+ if (canGoRight && (!canGoLeft || (right - pos ) <= (pos - left ))) {
104+ for (int j = right ; j > pos ; j --) {
105+ data [j ] = data [j - 1 ];
106+ occupied [j ] = true ;
107+ }
108+ data [pos ] = value ;
109+ occupied [pos ] = true ;
110+ } else if (canGoLeft ) {
111+ for (int j = left ; j < pos - 1 ; j ++) {
112+ data [j ] = data [j + 1 ];
113+ }
114+ occupied [left ] = true ;
115+ data [pos - 1 ] = value ;
116+ occupied [pos - 1 ] = true ;
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+ for (int j = left ; j < last ; j ++) {
142+ data [j ] = data [j + 1 ];
143+ }
144+ occupied [left ] = true ;
145+ data [last ] = value ;
146+ occupied [last ] = true ;
147+ }
148+
149+ /**
150+ * Finds the leftmost index at which {@code value} can be inserted so
151+ * that occupied slots remain sorted. Empty slots are compared using the
152+ * value of the nearest occupied slot at or after them, which is a
153+ * monotonic function of index and therefore safe to binary search over.
67154 */
68- private static int binarySearch (final Integer [] spaced , final int inserted , final int target ) {
155+ private static int findInsertionIndex (final int [] data , final boolean [] occupied , final int value ) {
69156 int lo = 0 ;
70- int hi = inserted ;
157+ int hi = data . length ;
71158 while (lo < hi ) {
72- int mid = lo + (hi - lo ) / 2 ;
73- if (spaced [mid ] <= target ) {
159+ final int mid = lo + (hi - lo ) / 2 ;
160+ final int probe = nearestOccupiedValueAtOrAfter (data , occupied , mid );
161+ if (probe != Integer .MAX_VALUE && probe <= value ) {
74162 lo = mid + 1 ;
75163 } else {
76164 hi = mid ;
77165 }
78166 }
79167 return lo ;
80168 }
169+
170+ private static int nearestOccupiedValueAtOrAfter (final int [] data , final boolean [] occupied , final int index ) {
171+ for (int i = index ; i < data .length ; i ++) {
172+ if (occupied [i ]) {
173+ return data [i ];
174+ }
175+ }
176+ return Integer .MAX_VALUE ;
177+ }
178+
179+ /**
180+ * Redistributes the {@code filled} occupied elements evenly across the
181+ * full capacity of {@code data}, restoring uniform gaps between them.
182+ */
183+ private static void rebalance (final int [] data , final boolean [] occupied , final int filled ) {
184+ final int capacity = data .length ;
185+ final int [] temp = new int [filled ];
186+ int idx = 0 ;
187+ for (int i = 0 ; i < capacity ; i ++) {
188+ if (occupied [i ]) {
189+ temp [idx ++] = data [i ];
190+ occupied [i ] = false ;
191+ }
192+ }
193+ for (int k = 0 ; k < filled ; k ++) {
194+ final int pos = (int ) ((long ) k * capacity / filled );
195+ data [pos ] = temp [k ];
196+ occupied [pos ] = true ;
197+ }
198+ }
81199}
0 commit comments