MinMaxHeap.java Source code

Java tutorial

Introduction

Here is the source code for MinMaxHeap.java

Source

//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);
        }
    };

}