forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathIndexedPriorityQueue.java
More file actions
327 lines (295 loc) · 10.6 KB
/
IndexedPriorityQueue.java
File metadata and controls
327 lines (295 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package com.thealgorithms.datastructures.heaps;
import java.util.Arrays;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Objects;
import java.util.function.Consumer;
/**
* An addressable (indexed) min-priority queue with O(log n) updates.
*
* <p>Key features:
* <ul>
* <li>Each element E is tracked by a handle (its current heap index) via a map,
* enabling O(log n) {@code remove(e)} and O(log n) key updates
* ({@code changeKey/decreaseKey/increaseKey}).</li>
* <li>The queue order is determined by the provided {@link Comparator}. If the
* comparator is {@code null}, elements must implement {@link Comparable}
* (same contract as {@link java.util.PriorityQueue}).</li>
* <li>By default this implementation uses {@link IdentityHashMap} for the index
* mapping to avoid issues with duplicate-equals elements or mutable equals/hashCode.
* If you need value-based equality, switch to {@code HashMap} and read the caveats
* in the class-level Javadoc carefully.</li>
* </ul>
*
* <h2>IMPORTANT contracts</h2>
* <ul>
* <li><b>Do not mutate comparator-relevant fields of an element directly</b> while it is
* inside the queue. Always use {@code changeKey}/{@code decreaseKey}/{@code increaseKey}
* so the heap can be restored accordingly.</li>
* <li>If you replace {@link IdentityHashMap} with {@link HashMap}, you must ensure:
* (a) no two distinct elements are {@code equals()}-equal at the same time in the queue, and
* (b) {@code equals/hashCode} of elements remain stable while enqueued.</li>
* <li>{@code peek()} returns {@code null} when empty (matching {@link java.util.PriorityQueue}).</li>
* <li>Not thread-safe.</li>
* </ul>
*
* <p>Complexities:
* {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
* {@code peek, isEmpty, size, contains} are O(1).
*/
public class IndexedPriorityQueue<E> {
/** Binary heap storage (min-heap). */
private Object[] heap;
/** Number of elements in the heap. */
private int size;
/** Comparator used for ordering; if null, elements must be Comparable. */
private final Comparator<? super E> cmp;
/**
* Index map: element -> current heap index.
* <p>We use IdentityHashMap by default to:
* <ul>
* <li>allow duplicate-equals elements;</li>
* <li>avoid corruption when equals/hashCode are mutable or not ID-based.</li>
* </ul>
* If you prefer value-based semantics, replace with HashMap<E,Integer> and
* respect the warnings in the class Javadoc.
*/
private final IdentityHashMap<E, Integer> index;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
public IndexedPriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public IndexedPriorityQueue(Comparator<? super E> cmp) {
this(DEFAULT_INITIAL_CAPACITY, cmp);
}
public IndexedPriorityQueue(int initialCapacity, Comparator<? super E> cmp) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity < 1");
}
this.heap = new Object[initialCapacity];
this.cmp = cmp;
this.index = new IdentityHashMap<>();
}
/** Returns current number of elements. */
public int size() {
return size;
}
/** Returns {@code true} if empty. */
public boolean isEmpty() {
return size == 0;
}
/**
* Returns the minimum element without removing it, or {@code null} if empty.
* Matches {@link java.util.PriorityQueue#peek()} behavior.
*/
@SuppressWarnings("unchecked")
public E peek() {
return size == 0 ? null : (E) heap[0];
}
/**
* Inserts the specified element (O(log n)).
* @throws NullPointerException if {@code e} is null
* @throws ClassCastException if {@code cmp == null} and {@code e} is not Comparable,
* or if incompatible with other elements
*/
public boolean offer(E e) {
Objects.requireNonNull(e, "element is null");
if (size >= heap.length) {
grow(size + 1);
}
// Insert at the end and bubble up. siftUp will maintain 'index' for all touched nodes.
siftUp(size, e);
size++;
return true;
}
/**
* Removes and returns the minimum element (O(log n)), or {@code null} if empty.
*/
@SuppressWarnings("unchecked")
public E poll() {
if (size == 0) {
return null;
}
E min = (E) heap[0];
removeAt(0); // updates map and heap structure
return min;
}
/**
* Removes one occurrence of the specified element e (O(log n)) if present.
* Uses the index map for O(1) lookup.
*/
public boolean remove(Object o) {
Integer i = index.get(o);
if (i == null) {
return false;
}
removeAt(i);
return true;
}
/** O(1): returns whether the queue currently contains the given element reference. */
public boolean contains(Object o) {
return index.containsKey(o);
}
/** Clears the heap and the index map. */
public void clear() {
Arrays.fill(heap, 0, size, null);
index.clear();
size = 0;
}
// ------------------------------------------------------------------------------------
// Key update API
// ------------------------------------------------------------------------------------
/**
* Changes comparator-relevant fields of {@code e} via the provided {@code mutator},
* then restores the heap in O(log n) by bubbling in the correct direction.
*
* <p><b>IMPORTANT:</b> The mutator must not change {@code equals/hashCode} of {@code e}
* if you migrate this implementation to value-based indexing (HashMap).
*
* @throws IllegalArgumentException if {@code e} is not in the queue
*/
public void changeKey(E e, Consumer<E> mutator) {
Integer i = index.get(e);
if (i == null) {
throw new IllegalArgumentException("Element not in queue");
}
// Mutate fields used by comparator (do NOT mutate equality/hash if using value-based map)
mutator.accept(e);
// Try bubbling up; if no movement occurred, bubble down.
if (!siftUp(i)) {
siftDown(i);
}
}
/**
* Faster variant if the new key is strictly smaller (higher priority).
* Performs a single sift-up (O(log n)).
*/
public void decreaseKey(E e, Consumer<E> mutator) {
Integer i = index.get(e);
if (i == null) {
throw new IllegalArgumentException("Element not in queue");
}
mutator.accept(e);
siftUp(i);
}
/**
* Faster variant if the new key is strictly larger (lower priority).
* Performs a single sift-down (O(log n)).
*/
public void increaseKey(E e, Consumer<E> mutator) {
Integer i = index.get(e);
if (i == null) {
throw new IllegalArgumentException("Element not in queue");
}
mutator.accept(e);
siftDown(i);
}
// ------------------------------------------------------------------------------------
// Internal utilities
// ------------------------------------------------------------------------------------
/** Grows the internal array to accommodate at least {@code minCapacity}. */
private void grow(int minCapacity) {
int old = heap.length;
int pref = (old < 64) ? old + 2 : old + (old >> 1); // +2 if small, else +50%
int newCap = Math.max(minCapacity, pref);
heap = Arrays.copyOf(heap, newCap);
}
@SuppressWarnings("unchecked")
private int compare(E a, E b) {
if (cmp != null) {
return cmp.compare(a, b);
}
return ((Comparable<? super E>) a).compareTo(b);
}
/**
* Inserts item {@code x} at position {@code k}, bubbling up while maintaining the heap.
* Also maintains the index map for all moved elements.
*/
@SuppressWarnings("unchecked")
private void siftUp(int k, E x) {
while (k > 0) {
int p = (k - 1) >>> 1;
E e = (E) heap[p];
if (compare(x, e) >= 0) {
break;
}
heap[k] = e;
index.put(e, k);
k = p;
}
heap[k] = x;
index.put(x, k);
}
/**
* Attempts to bubble up the element currently at {@code k}.
* @return true if it moved; false otherwise.
*/
@SuppressWarnings("unchecked")
private boolean siftUp(int k) {
int orig = k;
E x = (E) heap[k];
while (k > 0) {
int p = (k - 1) >>> 1;
E e = (E) heap[p];
if (compare(x, e) >= 0) {
break;
}
heap[k] = e;
index.put(e, k);
k = p;
}
if (k != orig) {
heap[k] = x;
index.put(x, k);
return true;
}
return false;
}
/** Bubbles down the element currently at {@code k}. */
@SuppressWarnings("unchecked")
private void siftDown(int k) {
int n = size;
E x = (E) heap[k];
int half = n >>> 1; // loop while k has at least one child
while (k < half) {
int child = (k << 1) + 1; // assume left is smaller
E c = (E) heap[child];
int r = child + 1;
if (r < n && compare(c, (E) heap[r]) > 0) {
child = r;
c = (E) heap[child];
}
if (compare(x, c) <= 0) {
break;
}
heap[k] = c;
index.put(c, k);
k = child;
}
heap[k] = x;
index.put(x, k);
}
/**
* Removes the element at heap index {@code i}, restoring the heap afterwards.
* <p>Returns nothing; the standard {@code PriorityQueue} returns a displaced
* element in a rare case to help its iterator. We don't need that here, so
* we keep the API simple.
*/
@SuppressWarnings("unchecked")
private void removeAt(int i) {
int n = --size; // last index after removal
E moved = (E) heap[n];
E removed = (E) heap[i];
heap[n] = null; // help GC
index.remove(removed); // drop mapping for removed element
if (i == n) {
return; // removed last element; done
}
heap[i] = moved;
index.put(moved, i);
// Try sift-up first (cheap if key decreased); if no movement, sift-down.
if (!siftUp(i)) {
siftDown(i);
}
}
}