Here's my array-based binary heap java implementation, looking for comments / suggestions on my approach. I used a base heap class to contain most of the logic, since the behavior of min/max heaps is quite similar. I found the array-based implementation quite simple to build compared to a tree-based structure, only mechanic I can really think to add at this point is a dynamically-resizing underlying array, instead of using a fixed-size only.
Base Heap abstract class:
public abstract class Heap<T extends Comparable<T>> {
protected T[] heap;
protected final int maxSize;
protected int size;
public Heap(Class<T> clazz, int maxSize) {
this.maxSize = maxSize;
this.heap = (T[]) Array.newInstance(clazz, this.maxSize);
this.size = 0;
}
/**
* Inserts an element into the heap.
* @param data item to insert.
* @throws HeapException
*/
public void insert(T data) throws HeapException {
if(this.size >= this.maxSize) {
throw new HeapException();
}
this.heap[this.size] = data;
upHeap();
this.size++;
}
/**
* Returns the current extreme value within the heap.
* @return object representing current extreme value.
* @throws HeapException
*/
public T getExtreme() throws HeapException {
if(isEmpty()) {
throw new HeapException();
}
return this.heap[0];
}
/**
* Returns and removes the current extreme value from within the heap, replacing the old extreme with the next candidate.
* @return object representing extreme value.
* @throws HeapException
*/
public T removeExtreme() throws HeapException {
if(isEmpty()) {
throw new HeapException();
}
T extreme = this.heap[0];
this.heap[0] = this.heap[this.size - 1];
this.heap[this.size - 1] = null;
this.size--;
downHeap();
return extreme;
}
/**
* 'Bubbles-up' an item from the bottom of the heap (tail of the array) into it's appropriate spot, following the rules of a Min Heap.
*/
protected void upHeap() {
int currentIndex = this.size;
while(currentIndex > 0) {
int parentIndex = (currentIndex % 2 == 0) ? (currentIndex / 2) - 1 : currentIndex / 2;
if(upHeapComparator(currentIndex, parentIndex)) {
break;
}
swap(currentIndex, parentIndex);
currentIndex = parentIndex;
}
}
/**
* Percolates-down an item from the top of the heap (head of the array) into it's appropriate spot, following the rules of the underlying heap class.
*/
protected void downHeap() {
int currentIndex = 0;
while(true) {
int leftChildIndex = (2 * currentIndex) + 1;
int rightChildIndex = (2 * currentIndex) + 2;
if(leftChildIndex < this.size && rightChildIndex < this.size) {
int extremeIndex = findExtremeIndex(leftChildIndex, rightChildIndex);
if(downHeapComparator(currentIndex, extremeIndex)){
swap(currentIndex, extremeIndex);
currentIndex = extremeIndex;
} else {
break;
}
}
else if(leftChildIndex < this.size) {
if(downHeapComparator(currentIndex, leftChildIndex)) {
swap(currentIndex, leftChildIndex);
currentIndex = leftChildIndex;
} else {
break;
}
}
else {
break;
}
}
}
/**
* Comparison method used with up-heap operations, to be overridden within inheriting class.
* @param xIndex first index to use within comparison.
* @param yIndex second index to use within comparison.
* @return true or false based on the inheriting class' implementation.
*/
protected abstract boolean upHeapComparator(int xIndex, int yIndex);
/**
* Comparison method used with down-heap operations, to be overridden within inheriting class.
* @param xIndex first index to use within comparison.
* @param yIndex second index to use within comparison.
* @return true or false based on the inheriting class' implementation.
*/
protected abstract boolean downHeapComparator(int xIndex, int yIndex);
/**
* Comparison method used when finding an extreme value, to be overridden within inheriting class.
* @param xIndex first index to use within comparison.
* @param yIndex second index to use within comparison.
* @return true or false based on the inheriting class' implementation.
*/
protected abstract boolean extremeComparator(int xIndex, int yIndex);
/**
* Compares two values within the underlying heap array and returns the index of the maximum.
* @param xIndex index of first item to use in comparison.
* @param yIndex index of second item to use in comparison.
* @return integer representing index of the maximum value from the comparison.
* @throws IndexOutOfBoundsException
*/
protected int findExtremeIndex(int xIndex, int yIndex) throws IndexOutOfBoundsException {
if(xIndex >= this.size || yIndex >= this.size) {
throw new IndexOutOfBoundsException();
}
return (extremeComparator(xIndex, yIndex)) ? xIndex : yIndex;
}
/**
* Quick method used to swap two items within the underlying heap array.
* @param xIndex index of first item to swap.
* @param yIndex index of second item to swap.
* @throws IndexOutOfBoundsException
*/
protected void swap(int xIndex, int yIndex) throws IndexOutOfBoundsException {
if(xIndex > this.size || yIndex > this.size) {
throw new IndexOutOfBoundsException();
}
T temp = this.heap[xIndex];
this.heap[xIndex] = this.heap[yIndex];
this.heap[yIndex] = temp;
}
/**
* Compares two values.
* @param x first value to use in comparison.
* @param y second value to use in comparison.
* @return
*/
protected int compare(T x, T y) {
return x.compareTo(y);
}
/**
* Returns the heap in array form.
* @return array of generic objects representing the heap.
*/
public T[] getHeap() {
return this.heap;
}
/**
* Returns the allotted maximum size of the underlying heap array.
* @return an integer representing maximum size of the heap.
*/
public int getMaxSize() {
return this.maxSize;
}
/**
* Returns the current number of elements present within the underlying heap array.
* @return an integer representing the current number of elements within the heap.
*/
public int getSize() {
return this.size;
}
/**
* Determines whether or not the heap contains any elements.
* @return true if the heap is empty, false if otherwise.
*/
public boolean isEmpty() {
return this.size <= 0;
}
}
Min-Heap implementation, extending base Heap class:
public class MinHeap<T extends Comparable<T>> extends Heap<T> {
public MinHeap(Class<T> clazz, int maxSize) {
super(clazz, maxSize);
}
@Override
protected boolean upHeapComparator(int xIndex, int yIndex) {
return this.heap[xIndex].compareTo(this.heap[yIndex]) >= 0;
}
@Override
protected boolean downHeapComparator(int xIndex, int yIndex) {
return compare(this.heap[xIndex], this.heap[yIndex]) > 0;
}
@Override
protected boolean extremeComparator(int xIndex, int yIndex) {
return this.heap[xIndex].compareTo(this.heap[yIndex]) <= 0;
}
}
Max-Heap implementation, extending base Heap class:
public class MaxHeap<T extends Comparable<T>> extends Heap<T> {
public MaxHeap(Class<T> clazz, int maxSize) {
super(clazz, maxSize);
}
@Override
protected boolean upHeapComparator(int xIndex, int yIndex) {
return this.heap[xIndex].compareTo(this.heap[yIndex]) < 0;
}
@Override
protected boolean downHeapComparator(int xIndex, int yIndex) {
return compare(this.heap[xIndex], this.heap[yIndex]) <= 0;
}
@Override
protected boolean extremeComparator(int xIndex, int yIndex) {
return this.heap[xIndex].compareTo(this.heap[yIndex]) > 0;
}
}
Supplementary HeapException class:
public class HeapException extends Exception {
public HeapException() {
super();
}
public HeapException(String message) {
super(message);
}
}
push
andpop
instead ofinsert
andremoveExtreme
. I like that thepop
method returns the popped item, it's quite convenient. Among C++ programmers it's become the norm not to, because the standard library'spriority_queue::pop
method only deletes the item. But there's a good reason there. IfT
is expensive to copy, it's cheaper to use it by reference while it's still in the heap, then pop it when you're done with it. Something to take into account? \$\endgroup\$priority_queue::top
function returns the top element by reference, meaning it doesn't make a copy. So you can use that element however you need, never making a copy of it, it's still sitting in the heap. YourremoveExtreme
method copies the top element before removing it, so it can return it at the end. This is fine ifT
is simple, but if it contains large data, it might be expensive. That said, I'm not all that familiar with Java, maybe data is never actually copied there? \$\endgroup\$T
will always be pointers toT
unlessT
is simple. \$\endgroup\$