Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c2cec07

Browse files
authored
Merge branch 'master' into add-sudoku-solver
2 parents 8931014 + cff5d36 commit c2cec07

File tree

2 files changed

+677
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)