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 @@ -835,6 +835,7 @@
* [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java)
* [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.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)
* [MinPriorityQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java)
* lists
* [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ public interface Heap {
* @param elementIndex int containing the position in the heap of the
* element to be deleted.
*/
void deleteElement(int elementIndex);
void deleteElement(int elementIndex) throws EmptyHeapException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public String toString() {
}

/**
* @param otherHeapElement
* @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.
*/
Expand All @@ -134,4 +134,8 @@ public int hashCode() {
result = 31 * result + (additionalInfo != null ? additionalInfo.hashCode() : 0);
return result;
}

public String getValue() {
return additionalInfo.toString();
}
}
225 changes: 174 additions & 51 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,251 @@
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:
* ```java
* 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
* ```
*
* @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 with a warning message.
*
* @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 elements first
for (HeapElement heapElement : listElements) {
if (heapElement != null) {
insertElement(heapElement);
maxHeap.add(heapElement);
} else {
System.out.println("Null element. Not added to heap");
}
}

// Then heapify the array bottom-up
for (int i = maxHeap.size() / 2; i >= 0; i--) {
heapifyDown(i + 1); // +1 because heapifyDown expects 1-based index
}

if (maxHeap.isEmpty()) {
System.out.println("No element has been added, empty heap.");
}
}

/**
* Get the element at a given index. The key for the list is equal to index
* value - 1
* 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);
}
}

/**
* 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 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 empty heap");
}
if ((elementIndex > maxHeap.size()) || (elementIndex <= 0)) {
throw new IndexOutOfBoundsException("Index out of heap range");
}
// 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);
throw new IndexOutOfBoundsException("Index " + elementIndex + " is out of heap range [1, " + maxHeap.size() + "]");
}

// 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