A MinMaxHeap provides a heap-like data structure that provides fast access to both the minimum and maximum elements of the heap.
//package com.aliasi.util;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
/**
* A <code>MinMaxHeap</code> provides a heap-like data structure that
* provides fast access to both the minimum and maximum elements of
* the heap. Each min-max heap is of a fixed maximum size. A min-max
* heap holds elements implementing the {@link Scored} interface, with
* scores being used for sorting.
*
* <p>A min-max heap data structure is useful to implement priority
* queues with fixed numbers of elements, which requires access to
* both the best and worst elements of the queue.
*
* <p>This implementation is based on the paper:
* <ul>
* <li>Atkinson, M.D., J.-R. Sack, N. Santoro and T. Strothotte.
* 1986.
* <a href="http://www.cs.otago.ac.nz/staffpriv/mike/Papers/MinMaxHeaps/MinMaxHeaps.pdf">Min-max heaps and generalized priority queues</a>.
* <i>Communications of the ACM</i> <b>29</b>(10):996-1000.
* </li>
* </ul>
*
* @author Bob Carpenter
* @version 3.8
* @since LingPipe2.4.0
* @param <E> the type of objects stored in the heap
*/
public class MinMaxHeap<E extends Scored> extends AbstractCollection<E> {
// index starts at 1 to follow article
// min at root and at even levels; max at root dtrs
private final E[] mHeap;
private final int mHeapLength;
// true for min levels
private final boolean[] mLevelTypes;
private int mNextFreeIndex = 1;
/**
* Construct a min-max heap holding up to the specified
* number of elements. Min-max heaps do not grow
* dynamically and attempts to push an element onto a full
* heap will result in exceptions.
*
* @param maxSize Maximum number of elements in the heap.
*/
public MinMaxHeap(int maxSize) {
if (maxSize < 1) {
String msg = "Heaps must be at least one element."
+ " Found maxSize=" + maxSize;
throw new IllegalArgumentException(msg);
}
// required for array
@SuppressWarnings("unchecked")
E[] tempHeap = (E[]) new Scored[maxSize+1];
mHeap = tempHeap;
mHeapLength = mHeap.length;
mLevelTypes = new boolean[maxSize+1];
fillLevelTypes(mLevelTypes);
}
/**
* Returns the current size of this heap.
*
* @return The size of the heap.
*/
@Override
public int size() {
return mNextFreeIndex - 1;
}
/**
* Returns an iterator over this heap. The elements are returned
* in decreasing order of score.
*
* @return Iterator over this heap in decreasing order of score.
*/
@Override
public Iterator<E> iterator() {
ArrayList<E> list = new ArrayList<E>();
for (int i = 0; i < mNextFreeIndex; ++i)
list.add(mHeap[i]);
Collections.<E>sort(list,ScoredObject.reverseComparator());
return list.iterator();
}
/**
* Add the element to the heap.
*
* @param s Element to add to heap.
* @return <code>true</code> if the element is added.
*/
@Override
public boolean add(E s) {
// space still left
if (mNextFreeIndex < mHeapLength) {
mHeap[mNextFreeIndex++] = s;
bubbleUp(mNextFreeIndex-1);
return true;
} else if (s.score() <= peekMin().score()) {
return false;
} else {
popMin();
mHeap[mNextFreeIndex++] = s;
bubbleUp(mNextFreeIndex-1);
return true;
}
}
/**
* Returns the maximum element in the heap, or <code>null</code>
* if it is empty.
*
* @return The largest element in the heap.
*/
public E peekMax() {
return
( mNextFreeIndex == 1
? null
: (mNextFreeIndex == 2
? mHeap[1]
: (mNextFreeIndex == 3
? mHeap[2]
: ( mHeap[2].score() > mHeap[3].score()
? mHeap[2]
: mHeap[3] ) ) ) );
}
/**
* Returns the minimum element in the heap, or <code>null</code>
* if it is empty.
*
* @return The smallest element in the heap.
*/
public E peekMin() {
return (mNextFreeIndex == 1)
? null
: mHeap[1];
}
/**
* Returns the maximum element in the heap after removing it, or
* returns <code>null</code> if the heap is empty.
*
* @return The largest element in the heap.
*/
public E popMax() {
if (mNextFreeIndex == 1) return null;
// only one element; return it
if (mNextFreeIndex == 2) {
--mNextFreeIndex;
return mHeap[1];
}
// two elements, so max is only on second level
if (mNextFreeIndex == 3) {
--mNextFreeIndex;
return mHeap[2];
}
// at least three elements, so check level 1 dtrs
if (mHeap[2].score() > mHeap[3].score()) {
E max = mHeap[2];
mHeap[2] = mHeap[--mNextFreeIndex];
trickleDownMax(2);
return max;
} else {
E max = mHeap[3];
mHeap[3] = mHeap[--mNextFreeIndex];
trickleDownMax(3);
return max;
}
}
/**
* Returns the minimum element in the heap after removing it, or
* returns <code>null</code> if the heap is empty.
*
* @return The smallest element in the heap.
*/
public E popMin() {
if (mNextFreeIndex == 1) return null;
if (mNextFreeIndex == 2) {
mNextFreeIndex = 1;
return mHeap[1];
}
E min = mHeap[1];
mHeap[1] = mHeap[--mNextFreeIndex];
trickleDownMin(1);
return min;
}
/**
* Returns a string-based representation of this heap.
*
* @return String representation of this heap.
*/
@Override
public String toString() {
if (mNextFreeIndex == 1) return "EMPTY HEAP";
StringBuilder sb = new StringBuilder();
for (int i = 1; i < mNextFreeIndex; ++i) {
if (i > 1) sb.append("\n");
sb.append(i + "=" + mHeap[i]);
}
return sb.toString();
}
void bubbleUp(int nodeIndex) {
if (!hasParent(nodeIndex)) return;
int parentIndex = parentIndex(nodeIndex);
if (onMinLevel(nodeIndex)) {
if (mHeap[nodeIndex].score() > mHeap[parentIndex].score()) {
swap(nodeIndex,parentIndex);
bubbleUpMax(parentIndex);
} else {
bubbleUpMin(nodeIndex);
}
} else { // on max level
if (mHeap[nodeIndex].score() < mHeap[parentIndex].score()) {
swap(nodeIndex,parentIndex);
bubbleUpMin(parentIndex);
} else {
bubbleUpMax(nodeIndex);
}
}
}
void bubbleUpMin(int nodeIndex) {
while (true) {
if (!hasParent(nodeIndex)) return;
int parentIndex = parentIndex(nodeIndex);
if (!hasParent(parentIndex)) return;
int grandparentIndex = parentIndex(parentIndex);
if (mHeap[nodeIndex].score()
>= mHeap[grandparentIndex].score()) return;
swap(nodeIndex,grandparentIndex);
nodeIndex = grandparentIndex;
}
}
void bubbleUpMax(int nodeIndex) {
while (true) {
if (!hasParent(nodeIndex)) return;
int parentIndex = parentIndex(nodeIndex);
if (!hasParent(parentIndex)) return;
int grandparentIndex = parentIndex(parentIndex);
if (mHeap[nodeIndex].score()
<= mHeap[grandparentIndex].score()) return;
swap(nodeIndex,grandparentIndex);
nodeIndex = grandparentIndex;
}
}
boolean onMinLevel(int nodeIndex) {
return mLevelTypes[nodeIndex];
}
void trickleDown(int nodeIndex) {
if (noChildren(nodeIndex)) return;
if (onMinLevel(nodeIndex))
trickleDownMin(nodeIndex);
else
trickleDownMax(nodeIndex);
}
void trickleDownMin(int nodeIndex) {
while (leftDaughterIndex(nodeIndex) < mNextFreeIndex) { // has dtrs
int minDescIndex = minDtrOrGrandDtrIndex(nodeIndex);
if (isDaughter(nodeIndex,minDescIndex)) {
if (mHeap[minDescIndex].score() < mHeap[nodeIndex].score())
swap(minDescIndex,nodeIndex);
return;
} else { // is grand child
if (mHeap[minDescIndex].score() >= mHeap[nodeIndex].score())
return;
swap(minDescIndex,nodeIndex);
int parentIndex = parentIndex(minDescIndex);
if (mHeap[minDescIndex].score() > mHeap[parentIndex].score())
swap(minDescIndex,parentIndex);
nodeIndex = minDescIndex; // recursive call in paper
}
}
}
void trickleDownMax(int nodeIndex) {
while (leftDaughterIndex(nodeIndex) < mNextFreeIndex) {
int maxDescIndex = maxDtrOrGrandDtrIndex(nodeIndex);
if (isDaughter(nodeIndex,maxDescIndex)) {
if (mHeap[maxDescIndex].score() > mHeap[nodeIndex].score())
swap(maxDescIndex,nodeIndex);
return;
} else { // is grand child
if (mHeap[maxDescIndex].score() <= mHeap[nodeIndex].score())
return;
swap(maxDescIndex,nodeIndex);
int parentIndex = parentIndex(maxDescIndex);
if (mHeap[maxDescIndex].score() < mHeap[parentIndex].score())
swap(maxDescIndex,parentIndex);
nodeIndex = maxDescIndex; // recursive call in paper
}
}
}
// requires nodeIndex to have a dtr
int minDtrOrGrandDtrIndex(int nodeIndex) {
// start with left dtr; must have a dtr coming in
int leftDtrIndex = leftDaughterIndex(nodeIndex);
int minIndex = leftDtrIndex;
double minScore = mHeap[leftDtrIndex].score();
int rightDtrIndex = rightDaughterIndex(nodeIndex);
if (rightDtrIndex >= mNextFreeIndex) return minIndex;
double rightDtrScore = mHeap[rightDtrIndex].score();
if (rightDtrScore < minScore) {
minIndex = rightDtrIndex;
minScore = rightDtrScore;
}
int grandDtr1Index = leftDaughterIndex(leftDtrIndex);
if (grandDtr1Index >= mNextFreeIndex) return minIndex;
double grandDtr1Score = mHeap[grandDtr1Index].score();
if (grandDtr1Score < minScore) {
minIndex = grandDtr1Index;
minScore = grandDtr1Score;
}
int grandDtr2Index = rightDaughterIndex(leftDtrIndex);
if (grandDtr2Index >= mNextFreeIndex) return minIndex;
double grandDtr2Score = mHeap[grandDtr2Index].score();
if (grandDtr2Score < minScore) {
minIndex = grandDtr2Index;
minScore = grandDtr2Score;
}
int grandDtr3Index = leftDaughterIndex(rightDtrIndex);
if (grandDtr3Index >= mNextFreeIndex) return minIndex;
double grandDtr3Score = mHeap[grandDtr3Index].score();
if (grandDtr3Score < minScore) {
minIndex = grandDtr3Index;
minScore = grandDtr3Score;
}
int grandDtr4Index = rightDaughterIndex(rightDtrIndex);
if (grandDtr4Index >= mNextFreeIndex) return minIndex;
double grandDtr4Score = mHeap[grandDtr4Index].score();
return grandDtr4Score < minScore
? grandDtr4Index
: minIndex;
}
// requires nodeIndex to have a dtr
int maxDtrOrGrandDtrIndex(int nodeIndex) {
// start with left dtr; must have a dtr coming in
int leftDtrIndex = leftDaughterIndex(nodeIndex);
int maxIndex = leftDtrIndex;
double maxScore = mHeap[leftDtrIndex].score();
int rightDtrIndex = rightDaughterIndex(nodeIndex); // opt to left+1
if (rightDtrIndex >= mNextFreeIndex) return maxIndex;
double rightDtrScore = mHeap[rightDtrIndex].score();
if (rightDtrScore > maxScore) {
maxIndex = rightDtrIndex;
maxScore = rightDtrScore;
}
int grandDtr1Index = leftDaughterIndex(leftDtrIndex);
if (grandDtr1Index >= mNextFreeIndex) return maxIndex;
double grandDtr1Score = mHeap[grandDtr1Index].score();
if (grandDtr1Score > maxScore) {
maxIndex = grandDtr1Index;
maxScore = grandDtr1Score;
}
int grandDtr2Index = rightDaughterIndex(leftDtrIndex);
if (grandDtr2Index >= mNextFreeIndex) return maxIndex;
double grandDtr2Score = mHeap[grandDtr2Index].score();
if (grandDtr2Score > maxScore) {
maxIndex = grandDtr2Index;
maxScore = grandDtr2Score;
}
int grandDtr3Index = leftDaughterIndex(rightDtrIndex);
if (grandDtr3Index >= mNextFreeIndex) return maxIndex;
double grandDtr3Score = mHeap[grandDtr3Index].score();
if (grandDtr3Score > maxScore) {
maxIndex = grandDtr3Index;
maxScore = grandDtr3Score;
}
int grandDtr4Index = rightDaughterIndex(rightDtrIndex);
if (grandDtr4Index >= mNextFreeIndex) return maxIndex;
double grandDtr4Score = mHeap[grandDtr4Index].score();
return grandDtr4Score > maxScore
? grandDtr4Index
: maxIndex;
}
boolean hasParent(int nodeIndex) {
return nodeIndex > 1;
}
boolean noChildren(int nodeIndex) {
return leftDaughterIndex(nodeIndex) >= mHeapLength;
}
boolean isDaughter(int nodeIndexParent, int nodeIndexDescendant) {
return nodeIndexDescendant <= rightDaughterIndex(nodeIndexParent);
}
void swap(int index1, int index2) {
E temp = mHeap[index1];
mHeap[index1] = mHeap[index2];
mHeap[index2] = temp;
}
static int parentIndex(int nodeIndex) {
return nodeIndex/2; // Java's int arith rounds down
}
static int leftDaughterIndex(int nodeIndex) {
return 2 * nodeIndex;
}
static int rightDaughterIndex(int nodeIndex) {
return 2 * nodeIndex + 1;
}
static void fillLevelTypes(boolean[] levelTypes) {
boolean type = MAX_LEVEL;
int index = 1;
for (int numEltsOfType = 1; ; numEltsOfType *= 2) { // 2**n per level
type = !type; // reverse types at each level
for (int j = 0; j < numEltsOfType; ++j) {
if (index >= levelTypes.length) return;
levelTypes[index++] = type;
}
}
}
static final boolean MIN_LEVEL = true;
static final boolean MAX_LEVEL = false;
}
interface Scored {
/**
* Returns the score for this object.
*
* @return The score for this object.
*/
public double score();
}
/**
* A <code>ScoredObject</code> provides an implementation of the
* <code>Scored</code> interface with an attached object. Scored
* objects are immutable and identity is reference. The object
* returned by the getter {@link #getObject()} is the actual object
* stored, so changes to it will affect the scored object of which it
* is a part.
*
* @author Bob Carpenter
* @version 3.8.3
* @since LingPipe2.0
* @param <E> the type of object that is scored
*/
class ScoredObject<E> implements Scored {
private final E mObj;
private final double mScore;
/**
* Construct a scored object from the specified object
* and score.
*
* @param obj Object for the constructed scored object.
* @param score Score for the constructed scored object.
*/
public ScoredObject(E obj, double score) {
mObj = obj;
mScore = score;
}
/**
* Returns the object attached to this scored object.
*
* @return The object attached to this scored object.
*/
public E getObject() {
return mObj;
}
/**
* Returns the score for this scored object.
*
* @return The score for this scored object.
*/
public double score() {
return mScore;
}
/**
* Returns a string-based representation of this object consisting
* of the score followed by a colon (<code>':'</code>), followed
* by the object converted to a string.
*
* @return The string-based representation of this object.
*/
@Override
public String toString() {
return mScore + ":" + getObject();
}
/**
* Returns <code>true</code> if the specified object is
* a scored object with an object equal to this object's
* and equal scores.
*
* @param that Object to compare to this scored object.
* @return <code>true</code> if the object is a scored object
* equal to this one.
*/
@Override
@SuppressWarnings("rawtypes") // required for instanceof
public boolean equals(Object that) {
if (!(that instanceof ScoredObject))
return false;
ScoredObject<?> thatSo = (ScoredObject<?>) that;
return mObj.equals(thatSo.mObj)
&& mScore == thatSo.mScore;
}
/**
* Returns a comparator that sorts in ascending order of score.
*
* <p>This comparator may not be consistent with equality on
* the objects being compared, as it only depends on the score.
* <p>The returned comparator may be used as the priority ordering
* for a priority queue of objects sorted by score. It may also
* be passed to {@link
* java.util.Arrays#sort(Object[],Comparator)}.
*
* <p>This implementation is a singleton -- the same comparator
* is used for all instances.
*
* @return The ascending score comparator.
* @param <E> the type of scored objects being compared
*/
public static <E extends Scored> Comparator<E> comparator() {
@SuppressWarnings({"unchecked","deprecation"})
Comparator<E> result = (Comparator<E>) SCORE_COMPARATOR;
return result;
}
/**
* Returns a comparator that sorts in descending order of score.
* This is just the inverse ordering of {@link #comparator()}; see
* that method's documentation for more details.
*
* @return The descending score comparator.
* @param <E> the type of scored objects being compared
*/
public static <E extends Scored> Comparator<E> reverseComparator() {
@SuppressWarnings({"unchecked", "deprecation"})
Comparator<E> result = (Comparator<E>) REVERSE_SCORE_COMPARATOR;
return result;
}
static final Comparator<Scored> REVERSE_SCORE_COMPARATOR
= new ScoredObject.ReverseScoredComparator();
static final Comparator<Scored> SCORE_COMPARATOR
= new ScoredObject.ScoredComparator();
// package privates can't go in interface, so park them here
static class ScoredComparator implements Comparator<Scored> {
public int compare(Scored obj1, Scored obj2) {
return (obj1.score() > obj2.score())
? 1
: ( (obj1.score() < obj2.score())
? -1
: 0 );
}
};
static class ReverseScoredComparator
implements Comparator<Scored> {
public int compare(Scored obj1, Scored obj2) {
return (obj1.score() > obj2.score())
? -1
: ( (obj1.score() < obj2.score())
? 1
: 0 );
}
};
}
Related examples in the same category