Enhance docs, add tests in MaxHeap (#5983)

This commit is contained in:
Hardik Pawar
2024-10-30 02:17:33 +05:30
committed by GitHub
parent b14db816bd
commit 03bb8ee66e
4 changed files with 321 additions and 58 deletions

View File

@ -143,10 +143,9 @@ public class HeapElement {
}
/**
* 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) {

View File

@ -4,128 +4,245 @@ import java.util.ArrayList;
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
}
}
/**
* 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 index
* @return heapElement
* @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 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();
}
}