Skip to content

refactor: Enhance docs, add tests in MaxHeap #5983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 29, 2024
Merged
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
225 changes: 171 additions & 54 deletions src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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:
* <pre>
* List<HeapElement> 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
* </pre>
*
* @author Nicolas Renard
*/
public class MaxHeap implements Heap {

/** The internal list that stores heap elements */
private final List<HeapElement> 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<HeapElement> 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();
}
}
Loading