diff --git a/DIRECTORY.md b/DIRECTORY.md
index 0f7184cdb7e5..01e031b58581 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -488,6 +488,7 @@
* [PalindromePrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromePrime.java)
* [PalindromeSinglyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java)
* [RangeInSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java)
+ * [ShuffleArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ShuffleArray.java)
* [Sparsity](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/Sparsity.java)
* [ThreeSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java)
* [TwoSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/TwoSumProblem.java)
@@ -876,6 +877,7 @@
* [HeapElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java)
* [KthElementFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java)
* [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java)
+ * [MaxHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java)
* [MedianFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java)
* [MergeKSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java)
* [MinHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java)
@@ -1136,6 +1138,7 @@
* [PalindromePrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java)
* [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java)
* [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java)
+ * [ShuffleArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java)
* [SparsityTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/SparsityTest.java)
* [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java)
* [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java)
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
index 57cc9e37122d..a9cfac7036eb 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java
@@ -143,10 +143,9 @@ public String toString() {
}
/**
- * Compares this heap element to another object for equality.
- *
- * @param o the object to compare with
- * @return true if the keys and additional information are identical, false otherwise
+ * @param o : an object to compare with the current element
+ * @return true if the keys on both elements are identical and the
+ * additional info objects are identical.
*/
@Override
public boolean equals(Object o) {
diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
index 9010aae4cae5..5b4b29cf1c2d 100644
--- a/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
+++ b/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
@@ -4,128 +4,245 @@
import java.util.List;
/**
- * Heap tree where a node's key is higher than or equal to its parent's and
- * lower than or equal to its children's.
+ * A Max Heap implementation where each node's key is higher than or equal to its children's keys.
+ * This data structure provides O(log n) time complexity for insertion and deletion operations,
+ * and O(1) for retrieving the maximum element.
+ *
+ * Properties:
+ * 1. Complete Binary Tree
+ * 2. Parent node's key ≥ Children nodes' keys
+ * 3. Root contains the maximum element
+ *
+ * Example usage:
+ *
+ * List elements = Arrays.asList(
+ * new HeapElement(5, "Five"),
+ * new HeapElement(2, "Two")
+ * );
+ * MaxHeap heap = new MaxHeap(elements);
+ * heap.insertElement(new HeapElement(7, "Seven"));
+ * HeapElement max = heap.getElement(); // Returns and removes the maximum element
+ *
*
* @author Nicolas Renard
*/
public class MaxHeap implements Heap {
+ /** The internal list that stores heap elements */
private final List maxHeap;
+ /**
+ * Constructs a new MaxHeap from a list of elements.
+ * Null elements in the input list are ignored.
+ *
+ * @param listElements List of HeapElement objects to initialize the heap
+ * @throws IllegalArgumentException if the input list is null
+ */
public MaxHeap(List listElements) {
+ if (listElements == null) {
+ throw new IllegalArgumentException("Input list cannot be null");
+ }
+
maxHeap = new ArrayList<>();
+
+ // Safe initialization: directly add non-null elements first
for (HeapElement heapElement : listElements) {
if (heapElement != null) {
- insertElement(heapElement);
- } else {
- System.out.println("Null element. Not added to heap");
+ maxHeap.add(heapElement);
}
}
- if (maxHeap.isEmpty()) {
- System.out.println("No element has been added, empty heap.");
+
+ // Heapify the array bottom-up
+ for (int i = maxHeap.size() / 2; i >= 0; i--) {
+ heapifyDown(i + 1); // +1 because heapifyDown expects 1-based index
+ }
+ }
+
+ /**
+ * Maintains heap properties by moving an element down the heap.
+ * Similar to toggleDown but used specifically during initialization.
+ *
+ * @param elementIndex 1-based index of the element to heapify
+ */
+ private void heapifyDown(int elementIndex) {
+ int largest = elementIndex - 1;
+ int leftChild = 2 * elementIndex - 1;
+ int rightChild = 2 * elementIndex;
+
+ if (leftChild < maxHeap.size() && maxHeap.get(leftChild).getKey() > maxHeap.get(largest).getKey()) {
+ largest = leftChild;
+ }
+
+ if (rightChild < maxHeap.size() && maxHeap.get(rightChild).getKey() > maxHeap.get(largest).getKey()) {
+ largest = rightChild;
+ }
+
+ if (largest != elementIndex - 1) {
+ HeapElement swap = maxHeap.get(elementIndex - 1);
+ maxHeap.set(elementIndex - 1, maxHeap.get(largest));
+ maxHeap.set(largest, swap);
+
+ heapifyDown(largest + 1);
}
}
/**
- * Get the element at a given index. The key for the list is equal to index
- * value - 1
+ * Retrieves the element at the specified index without removing it.
+ * Note: The index is 1-based for consistency with heap operations.
*
- * @param elementIndex index
- * @return heapElement
+ * @param elementIndex 1-based index of the element to retrieve
+ * @return HeapElement at the specified index
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
public HeapElement getElement(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
return maxHeap.get(elementIndex - 1);
}
- // Get the key of the element at a given index
+ /**
+ * Retrieves the key value of an element at the specified index.
+ *
+ * @param elementIndex 1-based index of the element
+ * @return double value representing the key
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
private double getElementKey(int elementIndex) {
if ((elementIndex <= 0) || (elementIndex > maxHeap.size())) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
-
return maxHeap.get(elementIndex - 1).getKey();
}
- // Swaps two elements in the heap
+ /**
+ * Swaps two elements in the heap.
+ *
+ * @param index1 1-based index of first element
+ * @param index2 1-based index of second element
+ */
private void swap(int index1, int index2) {
HeapElement temporaryElement = maxHeap.get(index1 - 1);
maxHeap.set(index1 - 1, maxHeap.get(index2 - 1));
maxHeap.set(index2 - 1, temporaryElement);
}
- // Toggle an element up to its right place as long as its key is lower than its parent's
+ /**
+ * Moves an element up the heap until heap properties are satisfied.
+ * This operation is called after insertion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move up
+ */
private void toggleUp(int elementIndex) {
double key = maxHeap.get(elementIndex - 1).getKey();
- while (getElementKey((int) Math.floor(elementIndex / 2.0)) < key) {
+ while (elementIndex > 1 && getElementKey((int) Math.floor(elementIndex / 2.0)) < key) {
swap(elementIndex, (int) Math.floor(elementIndex / 2.0));
elementIndex = (int) Math.floor(elementIndex / 2.0);
}
}
- // Toggle an element down to its right place as long as its key is higher
- // than any of its children's
+ /**
+ * Moves an element down the heap until heap properties are satisfied.
+ * This operation is called after deletion to maintain heap properties.
+ *
+ * @param elementIndex 1-based index of the element to move down
+ */
private void toggleDown(int elementIndex) {
double key = maxHeap.get(elementIndex - 1).getKey();
- boolean wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size())));
- while ((2 * elementIndex <= maxHeap.size()) && wrongOrder) {
- // Check whether it shall swap the element with its left child or its right one if any.
- if ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2))) {
- swap(elementIndex, 2 * elementIndex + 1);
- elementIndex = 2 * elementIndex + 1;
+ boolean wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1));
+
+ while (2 * elementIndex <= maxHeap.size() && wrongOrder) {
+ int largerChildIndex;
+ if (2 * elementIndex + 1 <= maxHeap.size() && getElementKey(elementIndex * 2 + 1) > getElementKey(elementIndex * 2)) {
+ largerChildIndex = 2 * elementIndex + 1;
} else {
- swap(elementIndex, 2 * elementIndex);
- elementIndex = 2 * elementIndex;
+ largerChildIndex = 2 * elementIndex;
}
- wrongOrder = (key < getElementKey(elementIndex * 2)) || (key < getElementKey(Math.min(elementIndex * 2, maxHeap.size())));
+
+ swap(elementIndex, largerChildIndex);
+ elementIndex = largerChildIndex;
+
+ wrongOrder = (2 * elementIndex <= maxHeap.size() && key < getElementKey(elementIndex * 2)) || (2 * elementIndex + 1 <= maxHeap.size() && key < getElementKey(elementIndex * 2 + 1));
}
}
- private HeapElement extractMax() {
- HeapElement result = maxHeap.get(0);
- deleteElement(0);
+ /**
+ * Extracts and returns the maximum element from the heap.
+ *
+ * @return HeapElement with the highest key
+ * @throws EmptyHeapException if the heap is empty
+ */
+ private HeapElement extractMax() throws EmptyHeapException {
+ if (maxHeap.isEmpty()) {
+ throw new EmptyHeapException("Cannot extract from an empty heap");
+ }
+ HeapElement result = maxHeap.getFirst();
+ deleteElement(1);
return result;
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public final void insertElement(HeapElement element) {
+ public void insertElement(HeapElement element) {
+ if (element == null) {
+ throw new IllegalArgumentException("Cannot insert null element");
+ }
maxHeap.add(element);
toggleUp(maxHeap.size());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
- public void deleteElement(int elementIndex) {
+ public void deleteElement(int elementIndex) throws EmptyHeapException {
if (maxHeap.isEmpty()) {
- try {
- throw new EmptyHeapException("Attempt to delete an element from an empty heap");
- } catch (EmptyHeapException e) {
- e.printStackTrace();
- }
+ throw new EmptyHeapException("Cannot delete from an empty heap");
}
if ((elementIndex > maxHeap.size()) || (elementIndex <= 0)) {
- throw new IndexOutOfBoundsException("Index out of heap range");
+ throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}
- // The last element in heap replaces the one to be deleted
- maxHeap.set(elementIndex - 1, getElement(maxHeap.size()));
- maxHeap.remove(maxHeap.size());
- // Shall the new element be moved up...
- if (getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) {
- toggleUp(elementIndex);
- } // ... or down ?
- else if (((2 * elementIndex <= maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2))) || ((2 * elementIndex < maxHeap.size()) && (getElementKey(elementIndex) < getElementKey(elementIndex * 2)))) {
- toggleDown(elementIndex);
+
+ // Replace with last element and remove last position
+ maxHeap.set(elementIndex - 1, maxHeap.getLast());
+ maxHeap.removeLast();
+
+ // No need to toggle if we just removed the last element
+ if (!maxHeap.isEmpty() && elementIndex <= maxHeap.size()) {
+ // Determine whether to toggle up or down
+ if (elementIndex > 1 && getElementKey(elementIndex) > getElementKey((int) Math.floor(elementIndex / 2.0))) {
+ toggleUp(elementIndex);
+ } else {
+ toggleDown(elementIndex);
+ }
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public HeapElement getElement() throws EmptyHeapException {
- try {
- return extractMax();
- } catch (Exception e) {
- throw new EmptyHeapException("Heap is empty. Error retrieving element", e);
- }
+ return extractMax();
+ }
+
+ /**
+ * Returns the current size of the heap.
+ *
+ * @return number of elements in the heap
+ */
+ public int size() {
+ return maxHeap.size();
+ }
+
+ /**
+ * Checks if the heap is empty.
+ *
+ * @return true if the heap contains no elements
+ */
+ public boolean isEmpty() {
+ return maxHeap.isEmpty();
}
}
diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java
new file mode 100644
index 000000000000..c1b7eb3fd4ae
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java
@@ -0,0 +1,144 @@
+package com.thealgorithms.datastructures.heaps;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for MaxHeap implementation
+ */
+class MaxHeapTest {
+
+ private MaxHeap heap;
+
+ @BeforeEach
+ void setUp() {
+ // Create a fresh heap for each test
+ List elements = Arrays.asList(new HeapElement(5.0, "Five"), new HeapElement(2.0, "Two"), new HeapElement(8.0, "Eight"), new HeapElement(1.0, "One"), new HeapElement(9.0, "Nine"));
+ heap = new MaxHeap(elements);
+ }
+
+ @Test
+ void testConstructorWithNullList() {
+ assertThrows(IllegalArgumentException.class, () -> new MaxHeap(null));
+ }
+
+ @Test
+ void testConstructorWithEmptyList() {
+ MaxHeap emptyHeap = new MaxHeap(new ArrayList<>());
+ assertTrue(emptyHeap.isEmpty());
+ }
+
+ @Test
+ void testConstructorWithNullElements() {
+ List elements = Arrays.asList(new HeapElement(1.0, "One"), null, new HeapElement(2.0, "Two"));
+ MaxHeap heap = new MaxHeap(elements);
+ assertEquals(2, heap.size());
+ }
+
+ @Test
+ void testInsertElement() {
+ heap.insertElement(new HeapElement(10.0, "Ten"));
+ assertEquals(10.0, heap.getElement(1).getKey());
+ assertEquals(6, heap.size());
+ }
+
+ @Test
+ void testInsertNullElement() {
+ assertThrows(IllegalArgumentException.class, () -> heap.insertElement(null));
+ }
+
+ @Test
+ void testGetElementAtIndex() {
+ HeapElement element = heap.getElement(1);
+ assertEquals(9.0, element.getKey());
+ assertEquals("Nine", element.getValue());
+ }
+
+ @Test
+ void testGetElementAtInvalidIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(0));
+ assertThrows(IndexOutOfBoundsException.class, () -> heap.getElement(10));
+ }
+
+ @Test
+ void testDeleteElement() throws EmptyHeapException {
+ heap.deleteElement(1);
+ assertEquals(8.0, heap.getElement(1).getKey());
+ assertEquals(4, heap.size());
+ }
+
+ @Test
+ void testDeleteElementAtInvalidIndex() {
+ assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(0));
+ assertThrows(IndexOutOfBoundsException.class, () -> heap.deleteElement(10));
+ }
+
+ @Test
+ void testDeleteFromEmptyHeap() {
+ MaxHeap emptyHeap = new MaxHeap(new ArrayList<>());
+ assertThrows(EmptyHeapException.class, () -> emptyHeap.deleteElement(1));
+ }
+
+ @Test
+ void testExtractMax() throws EmptyHeapException {
+ HeapElement max = heap.getElement();
+ assertEquals(9.0, max.getKey());
+ assertEquals("Nine", max.getValue());
+ assertEquals(4, heap.size());
+
+ max = heap.getElement();
+ assertEquals(8.0, max.getKey());
+ assertEquals(3, heap.size());
+ }
+
+ @Test
+ void testExtractMaxFromEmptyHeap() {
+ MaxHeap emptyHeap = new MaxHeap(new ArrayList<>());
+ assertThrows(EmptyHeapException.class, () -> emptyHeap.getElement());
+ }
+
+ @Test
+ void testHeapOrder() {
+ // Test that parent is always greater than or equal to children
+ for (int i = 1; i <= heap.size() / 2; i++) {
+ double parentKey = heap.getElement(i).getKey();
+
+ // Check left child
+ if (2 * i <= heap.size()) {
+ assertTrue(parentKey >= heap.getElement(2 * i).getKey());
+ }
+
+ // Check right child
+ if (2 * i + 1 <= heap.size()) {
+ assertTrue(parentKey >= heap.getElement(2 * i + 1).getKey());
+ }
+ }
+ }
+
+ @Test
+ void testSizeAndEmpty() {
+ assertEquals(5, heap.size());
+ assertFalse(heap.isEmpty());
+
+ // Remove all elements
+ while (!heap.isEmpty()) {
+ try {
+ heap.getElement();
+ } catch (EmptyHeapException e) {
+ Assertions.fail("Should not throw EmptyHeapException while heap is not empty");
+ }
+ }
+
+ assertEquals(0, heap.size());
+ assertTrue(heap.isEmpty());
+ }
+}