From 98f2540b361b1a0cc58ef06287dcc417f01e4b90 Mon Sep 17 00:00:00 2001
From: SeungHyeok Yun <162292720+SeungHyeokYoon@users.noreply.github.com>
Date: Tue, 11 Nov 2025 23:39:36 +0900
Subject: [PATCH 1/4] feat: add IndexedPriorityQueue implementation and tests
---
.../heaps/IndexedPriorityQueue.java | 293 ++++++++++++++++
.../heaps/IndexedPriorityQueueTest.java | 317 ++++++++++++++++++
2 files changed, 610 insertions(+)
create mode 100644 src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
create mode 100644 src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
new file mode 100644
index 000000000000..f44f7298f458
--- /dev/null
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
@@ -0,0 +1,293 @@
+package com.thealgorithms.datastructures.heaps;
+
+import java.util.Comparator;
+import java.util.IdentityHashMap;
+import java.util.Objects;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * An addressable (indexed) min-priority queue with O(log n) updates.
+ *
+ *
Key features:
+ *
+ * - 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}).
+ * - 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}).
+ * - 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.
+ *
+ *
+ * IMPORTANT contracts
+ *
+ * - Do not mutate comparator-relevant fields of an element directly while it is
+ * inside the queue. Always use {@code changeKey}/{@code decreaseKey}/{@code increaseKey}
+ * so the heap can be restored accordingly.
+ * - 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.
+ * - {@code peek()} returns {@code null} when empty (matching {@link java.util.PriorityQueue}).
+ * - Not thread-safe.
+ *
+ *
+ * Complexities:
+ * {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
+ * {@code peek, isEmpty, size, contains} are O(1).
+ */
+public class IndexedPriorityQueue {
+
+ /** 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.
+ * We use IdentityHashMap by default to:
+ *
+ * - allow duplicate-equals elements;
+ * - avoid corruption when equals/hashCode are mutable or not ID-based.
+ *
+ * If you prefer value-based semantics, replace with HashMap and
+ * respect the warnings in the class Javadoc.
+ */
+ private final IdentityHashMap 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.
+ *
+ * IMPORTANT: 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 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 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 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.
+ * 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);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
new file mode 100644
index 000000000000..74150f1c8601
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
@@ -0,0 +1,317 @@
+package com.thealgorithms.datastructures.heaps;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Comparator;
+
+/**
+ * Tests for {@link IndexedPriorityQueue}.
+ *
+ * Notes:
+ * - We mainly use a Node class with a mutable "prio" field to test changeKey/decreaseKey/increaseKey.
+ * - The queue is a min-heap, so smaller "prio" means higher priority.
+ * - By default the implementation uses IdentityHashMap so duplicate-equals objects are allowed.
+ */
+public class IndexedPriorityQueueTest {
+
+ // ------------------------
+ // Helpers
+ // ------------------------
+
+ /** Simple payload with mutable priority. */
+ static class Node {
+ final String id;
+ int prio; // lower is better (min-heap)
+
+ Node(String id, int prio) {
+ this.id = id;
+ this.prio = prio;
+ }
+
+ @Override
+ public String toString() {
+ return id + "(" + prio + ")";
+ }
+ }
+
+ /** Same as Node but overrides equals/hashCode to simulate "duplicate-equals" scenario. */
+ static class NodeWithEquals {
+ final String id;
+ int prio;
+
+ NodeWithEquals(String id, int prio) {
+ this.id = id;
+ this.prio = prio;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof NodeWithEquals)) return false;
+ NodeWithEquals other = (NodeWithEquals) o;
+ // Intentionally naive equality: equal if priority is equal
+ return this.prio == other.prio;
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(prio);
+ }
+
+ @Override
+ public String toString() {
+ return id + "(" + prio + ")";
+ }
+ }
+
+ private static IndexedPriorityQueue newNodePQ() {
+ return new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+ }
+
+ // ------------------------
+ // Basic operations
+ // ------------------------
+
+ @Test
+ void testOfferPollWithIntegers_ComparableMode() {
+ // cmp == null -> elements must be Comparable
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
+ Assertions.assertTrue(pq.isEmpty());
+
+ pq.offer(5);
+ pq.offer(1);
+ pq.offer(3);
+
+ Assertions.assertEquals(3, pq.size());
+ Assertions.assertEquals(1, pq.peek());
+ Assertions.assertEquals(1, pq.poll());
+ Assertions.assertEquals(3, pq.poll());
+ Assertions.assertEquals(5, pq.poll());
+ Assertions.assertNull(pq.poll()); // empty -> null
+ Assertions.assertTrue(pq.isEmpty());
+ }
+
+ @Test
+ void testPeekAndIsEmpty() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.peek());
+
+ pq.offer(new Node("A", 10));
+ pq.offer(new Node("B", 5));
+ pq.offer(new Node("C", 7));
+
+ Assertions.assertFalse(pq.isEmpty());
+ Assertions.assertEquals("B(5)", pq.peek().toString());
+ }
+
+ @Test
+ void testRemoveSpecificElement() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 5);
+ Node c = new Node("C", 7);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // remove by reference (O(log n))
+ Assertions.assertTrue(pq.remove(b));
+ Assertions.assertEquals(2, pq.size());
+ // now min should be C(7)
+ Assertions.assertEquals("C(7)", pq.peek().toString());
+ // removing an element not present -> false
+ Assertions.assertFalse(pq.remove(b));
+ }
+
+ @Test
+ void testContainsAndClear() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 2);
+ Node b = new Node("B", 3);
+
+ pq.offer(a);
+ pq.offer(b);
+
+ Assertions.assertTrue(pq.contains(a));
+ Assertions.assertTrue(pq.contains(b));
+
+ pq.clear();
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertFalse(pq.contains(a));
+ Assertions.assertNull(pq.peek());
+ }
+
+ // ------------------------
+ // Key updates
+ // ------------------------
+
+ @Test
+ void testDecreaseKeyMovesUp() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 5);
+ Node c = new Node("C", 7);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // current min is B(5)
+ Assertions.assertEquals("B(5)", pq.peek().toString());
+
+ // Make A more important: 10 -> 1 (smaller is better)
+ pq.decreaseKey(a, n -> n.prio = 1);
+
+ // Now A should be at the top
+ Assertions.assertEquals("A(1)", pq.peek().toString());
+ }
+
+ @Test
+ void testIncreaseKeyMovesDown() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 1);
+ Node b = new Node("B", 2);
+ Node c = new Node("C", 3);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // min is A(1)
+ Assertions.assertEquals("A(1)", pq.peek().toString());
+
+ // Make A worse: 1 -> 100
+ pq.increaseKey(a, n -> n.prio = 100);
+
+ // Now min should be B(2)
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+ }
+
+ @Test
+ void testChangeKeyChoosesDirectionAutomatically() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+ Node b = new Node("B", 20);
+ Node c = new Node("C", 30);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+
+ // Decrease B to 0 -> should move up
+ pq.changeKey(b, n -> n.prio = 0);
+ Assertions.assertEquals("B(0)", pq.peek().toString());
+
+ // Increase B to 100 -> should move down
+ pq.changeKey(b, n -> n.prio = 100);
+ Assertions.assertEquals("A(10)", pq.peek().toString());
+ }
+
+ @Test
+ void testDirectMutationWithoutChangeKeyDoesNotReheap_ByDesign() {
+ // Demonstrates the contract: do NOT mutate comparator fields directly.
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 5);
+ Node b = new Node("B", 10);
+
+ pq.offer(a);
+ pq.offer(b);
+
+ // Illegally mutate priority directly
+ a.prio = 100; // worse than b now, but heap wasn't notified
+
+ // The heap structure is unchanged; peek still returns A(100) (was A(5) before)
+ // This test documents the behavior/contract rather than relying on it.
+ Assertions.assertEquals("A(100)", pq.peek().toString());
+
+ // Now fix properly via changeKey (no change in value, but triggers reheap)
+ pq.changeKey(a, n -> n.prio = n.prio);
+ Assertions.assertEquals("B(10)", pq.peek().toString());
+ }
+
+ // ------------------------
+ // Identity semantics & duplicates
+ // ------------------------
+
+ @Test
+ void testDuplicateEqualsElementsAreSupported_IdentityMap() {
+ IndexedPriorityQueue pq =
+ new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+
+ NodeWithEquals x1 = new NodeWithEquals("X1", 7);
+ NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance
+
+ // With IdentityHashMap internally, both can coexist
+ pq.offer(x1);
+ pq.offer(x2);
+
+ Assertions.assertEquals(2, pq.size());
+ // Poll twice; both 7s should be returned (order between x1/x2 is unspecified)
+ Assertions.assertEquals(7, pq.poll().prio);
+ Assertions.assertEquals(7, pq.poll().prio);
+ Assertions.assertTrue(pq.isEmpty());
+ }
+
+ // ------------------------
+ // Capacity growth
+ // ------------------------
+
+ @Test
+ void testGrowByManyInserts() {
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
+ int N = 100; // beyond default capacity (11)
+
+ for (int i = N; i >= 1; i--) {
+ pq.offer(i);
+ }
+
+ Assertions.assertEquals(N, pq.size());
+ // Ensure min-to-max order when polling
+ for (int expected = 1; expected <= N; expected++) {
+ Integer v = pq.poll();
+ Assertions.assertEquals(expected, v);
+ }
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.poll());
+ }
+
+ // ------------------------
+ // remove/contains edge cases
+ // ------------------------
+
+ @Test
+ void testRemoveHeadAndMiddleAndTail() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 1);
+ Node b = new Node("B", 2);
+ Node c = new Node("C", 3);
+ Node d = new Node("D", 4);
+
+ pq.offer(a);
+ pq.offer(b);
+ pq.offer(c);
+ pq.offer(d);
+
+ // remove head
+ Assertions.assertTrue(pq.remove(a));
+ Assertions.assertFalse(pq.contains(a));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove middle
+ Assertions.assertTrue(pq.remove(c));
+ Assertions.assertFalse(pq.contains(c));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove tail (last)
+ Assertions.assertTrue(pq.remove(d));
+ Assertions.assertFalse(pq.contains(d));
+ Assertions.assertEquals("B(2)", pq.peek().toString());
+
+ // remove last remaining
+ Assertions.assertTrue(pq.remove(b));
+ Assertions.assertTrue(pq.isEmpty());
+ Assertions.assertNull(pq.peek());
+ }
+}
From c9a9dee58ce27c621211bb927e5714586f5b1fd8 Mon Sep 17 00:00:00 2001
From: SeungHyeok Yun <162292720+SeungHyeokYoon@users.noreply.github.com>
Date: Sun, 16 Nov 2025 23:39:22 +0900
Subject: [PATCH 2/4] mod : clang-format
---
.../heaps/IndexedPriorityQueue.java | 78 +++++++++++++------
.../heaps/IndexedPriorityQueueTest.java | 9 ++-
2 files changed, 61 insertions(+), 26 deletions(-)
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
index f44f7298f458..ad7229760fd0 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java
@@ -1,9 +1,9 @@
package com.thealgorithms.datastructures.heaps;
+import java.util.Arrays;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Objects;
-import java.util.Arrays;
import java.util.function.Consumer;
/**
@@ -73,24 +73,32 @@ public IndexedPriorityQueue(Comparator super E> cmp) {
}
public IndexedPriorityQueue(int initialCapacity, Comparator super E> cmp) {
- if (initialCapacity < 1) throw new IllegalArgumentException("initialCapacity < 1");
+ 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; }
+ public int size() {
+ return size;
+ }
/** Returns {@code true} if empty. */
- public boolean isEmpty() { return size == 0; }
+ 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]; }
+ public E peek() {
+ return size == 0 ? null : (E) heap[0];
+ }
/**
* Inserts the specified element (O(log n)).
@@ -100,7 +108,9 @@ public IndexedPriorityQueue(int initialCapacity, Comparator super E> cmp) {
*/
public boolean offer(E e) {
Objects.requireNonNull(e, "element is null");
- if (size >= heap.length) grow(size + 1);
+ 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++;
@@ -112,7 +122,9 @@ public boolean offer(E e) {
*/
@SuppressWarnings("unchecked")
public E poll() {
- if (size == 0) return null;
+ if (size == 0) {
+ return null;
+ }
E min = (E) heap[0];
removeAt(0); // updates map and heap structure
return min;
@@ -124,7 +136,9 @@ public E poll() {
*/
public boolean remove(Object o) {
Integer i = index.get(o);
- if (i == null) return false;
+ if (i == null) {
+ return false;
+ }
removeAt(i);
return true;
}
@@ -156,11 +170,15 @@ public void clear() {
*/
public void changeKey(E e, Consumer mutator) {
Integer i = index.get(e);
- if (i == null) throw new IllegalArgumentException("Element not in queue");
+ 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);
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
}
/**
@@ -169,7 +187,9 @@ public void changeKey(E e, Consumer mutator) {
*/
public void decreaseKey(E e, Consumer mutator) {
Integer i = index.get(e);
- if (i == null) throw new IllegalArgumentException("Element not in queue");
+ if (i == null) {
+ throw new IllegalArgumentException("Element not in queue");
+ }
mutator.accept(e);
siftUp(i);
}
@@ -180,7 +200,9 @@ public void decreaseKey(E e, Consumer mutator) {
*/
public void increaseKey(E e, Consumer mutator) {
Integer i = index.get(e);
- if (i == null) throw new IllegalArgumentException("Element not in queue");
+ if (i == null) {
+ throw new IllegalArgumentException("Element not in queue");
+ }
mutator.accept(e);
siftDown(i);
}
@@ -199,7 +221,9 @@ private void grow(int minCapacity) {
@SuppressWarnings("unchecked")
private int compare(E a, E b) {
- if (cmp != null) return cmp.compare(a, b);
+ if (cmp != null) {
+ return cmp.compare(a, b);
+ }
return ((Comparable super E>) a).compareTo(b);
}
@@ -212,7 +236,9 @@ 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;
+ if (compare(x, e) >= 0) {
+ break;
+ }
heap[k] = e;
index.put(e, k);
k = p;
@@ -232,7 +258,9 @@ private boolean siftUp(int k) {
while (k > 0) {
int p = (k - 1) >>> 1;
E e = (E) heap[p];
- if (compare(x, e) >= 0) break;
+ if (compare(x, e) >= 0) {
+ break;
+ }
heap[k] = e;
index.put(e, k);
k = p;
@@ -252,14 +280,16 @@ private void siftDown(int k) {
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
+ 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;
+ if (compare(x, c) <= 0) {
+ break;
+ }
heap[k] = c;
index.put(c, k);
k = child;
@@ -276,18 +306,22 @@ private void siftDown(int k) {
*/
@SuppressWarnings("unchecked")
private void removeAt(int i) {
- int n = --size; // last index after removal
+ 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
+ heap[n] = null; // help GC
+ index.remove(removed); // drop mapping for removed element
- if (i == n) return; // removed last element; done
+ 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);
+ if (!siftUp(i)) {
+ siftDown(i);
+ }
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
index 74150f1c8601..05a5aaf0910d 100644
--- a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
@@ -1,10 +1,9 @@
package com.thealgorithms.datastructures.heaps;
+import java.util.Comparator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import java.util.Comparator;
-
/**
* Tests for {@link IndexedPriorityQueue}.
*
@@ -47,7 +46,9 @@ static class NodeWithEquals {
@Override
public boolean equals(Object o) {
- if (!(o instanceof NodeWithEquals)) return false;
+ if (!(o instanceof NodeWithEquals)) {
+ return false;
+ }
NodeWithEquals other = (NodeWithEquals) o;
// Intentionally naive equality: equal if priority is equal
return this.prio == other.prio;
@@ -238,7 +239,7 @@ void testDirectMutationWithoutChangeKeyDoesNotReheap_ByDesign() {
@Test
void testDuplicateEqualsElementsAreSupported_IdentityMap() {
IndexedPriorityQueue pq =
- new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+ new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
NodeWithEquals x1 = new NodeWithEquals("X1", 7);
NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance
From 115f69660bc85ca6b8ca078651b5d4c612e5887f Mon Sep 17 00:00:00 2001
From: SeungHyeok Yun <162292720+SeungHyeokYoon@users.noreply.github.com>
Date: Mon, 17 Nov 2025 00:08:11 +0900
Subject: [PATCH 3/4] Fix Checkstyle naming for IndexedPriorityQueue tests
---
.../heaps/IndexedPriorityQueueTest.java | 56 ++++++++++++++++---
1 file changed, 49 insertions(+), 7 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
index 05a5aaf0910d..f2205aa6482d 100644
--- a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
@@ -74,7 +74,7 @@ private static IndexedPriorityQueue newNodePQ() {
// ------------------------
@Test
- void testOfferPollWithIntegers_ComparableMode() {
+ void testOfferPollWithIntegersComparableMode() {
// cmp == null -> elements must be Comparable
IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
Assertions.assertTrue(pq.isEmpty());
@@ -211,7 +211,7 @@ void testChangeKeyChoosesDirectionAutomatically() {
}
@Test
- void testDirectMutationWithoutChangeKeyDoesNotReheap_ByDesign() {
+ void testDirectMutationWithoutChangeKeyDoesNotReheapByDesign() {
// Demonstrates the contract: do NOT mutate comparator fields directly.
IndexedPriorityQueue pq = newNodePQ();
Node a = new Node("A", 5);
@@ -237,7 +237,7 @@ void testDirectMutationWithoutChangeKeyDoesNotReheap_ByDesign() {
// ------------------------
@Test
- void testDuplicateEqualsElementsAreSupported_IdentityMap() {
+ void testDuplicateEqualsElementsAreSupportedIdentityMap() {
IndexedPriorityQueue pq =
new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
@@ -262,15 +262,15 @@ void testDuplicateEqualsElementsAreSupported_IdentityMap() {
@Test
void testGrowByManyInserts() {
IndexedPriorityQueue pq = new IndexedPriorityQueue<>();
- int N = 100; // beyond default capacity (11)
+ int n = 100; // beyond default capacity (11)
- for (int i = N; i >= 1; i--) {
+ for (int i = n; i >= 1; i--) {
pq.offer(i);
}
- Assertions.assertEquals(N, pq.size());
+ Assertions.assertEquals(n, pq.size());
// Ensure min-to-max order when polling
- for (int expected = 1; expected <= N; expected++) {
+ for (int expected = 1; expected <= n; expected++) {
Integer v = pq.poll();
Assertions.assertEquals(expected, v);
}
@@ -315,4 +315,46 @@ void testRemoveHeadAndMiddleAndTail() {
Assertions.assertTrue(pq.isEmpty());
Assertions.assertNull(pq.peek());
}
+
+ // ------------------------
+ // Error / edge cases for coverage
+ // ------------------------
+
+ @Test
+ void testInvalidInitialCapacityThrows() {
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> new IndexedPriorityQueue(0, Comparator.naturalOrder()));
+ }
+
+ @Test
+ void testChangeKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> pq.changeKey(a, n -> n.prio = 5));
+ }
+
+ @Test
+ void testDecreaseKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> pq.decreaseKey(a, n -> n.prio = 5));
+ }
+
+ @Test
+ void testIncreaseKeyOnMissingElementThrows() {
+ IndexedPriorityQueue pq = newNodePQ();
+ Node a = new Node("A", 10);
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> pq.increaseKey(a, n -> n.prio = 15));
+ }
+
}
From d312dad437780af30688fd624eb2f9e4375142ec Mon Sep 17 00:00:00 2001
From: SeungHyeok Yun <162292720+SeungHyeokYoon@users.noreply.github.com>
Date: Mon, 17 Nov 2025 00:20:15 +0900
Subject: [PATCH 4/4] Align IndexedPriorityQueue tests with Checkstyle and
clang-format
---
.../heaps/IndexedPriorityQueueTest.java | 20 +++++--------------
1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
index f2205aa6482d..8d8c4e1db6bd 100644
--- a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java
@@ -238,8 +238,7 @@ void testDirectMutationWithoutChangeKeyDoesNotReheapByDesign() {
@Test
void testDuplicateEqualsElementsAreSupportedIdentityMap() {
- IndexedPriorityQueue pq =
- new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
+ IndexedPriorityQueue pq = new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
NodeWithEquals x1 = new NodeWithEquals("X1", 7);
NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance
@@ -322,9 +321,7 @@ void testRemoveHeadAndMiddleAndTail() {
@Test
void testInvalidInitialCapacityThrows() {
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> new IndexedPriorityQueue(0, Comparator.naturalOrder()));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new IndexedPriorityQueue(0, Comparator.naturalOrder()));
}
@Test
@@ -332,9 +329,7 @@ void testChangeKeyOnMissingElementThrows() {
IndexedPriorityQueue pq = newNodePQ();
Node a = new Node("A", 10);
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> pq.changeKey(a, n -> n.prio = 5));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.changeKey(a, n -> n.prio = 5));
}
@Test
@@ -342,9 +337,7 @@ void testDecreaseKeyOnMissingElementThrows() {
IndexedPriorityQueue pq = newNodePQ();
Node a = new Node("A", 10);
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> pq.decreaseKey(a, n -> n.prio = 5));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.decreaseKey(a, n -> n.prio = 5));
}
@Test
@@ -352,9 +345,6 @@ void testIncreaseKeyOnMissingElementThrows() {
IndexedPriorityQueue pq = newNodePQ();
Node a = new Node("A", 10);
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> pq.increaseKey(a, n -> n.prio = 15));
+ Assertions.assertThrows(IllegalArgumentException.class, () -> pq.increaseKey(a, n -> n.prio = 15));
}
-
}