/* Copyright (c) 1999, 2000 Brown University, Providence, RI All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose other than its incorporation into a commercial product is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Brown University not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE. IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ package jdsl.core.ref; import java.io.Serializable; import jdsl.core.api.*; /** * An array implementation of a heap.<p> * * The number of elements that can be stored in the array or in the * heap is called capacity. The capacity of the heap is always the * capacity of the array minus one. The initial capacity of the array * is the public constant defaultInitialCapacity (or the capacity * specified in the constructor); the maximum capacity of the array is * 2^30. The capacity of the array is doubled when needed. The load * factor of the array is the ratio of the number of elements stored * in the array to the capacity of the array. If array-shrinking is * specified at construction time, the capacity of the array is halved * when the load factor of the array is less than or equal to 0.25.<p> * * A binary heap such as this one has O(log n) insert, remove, and * replaceKey, and O(1) min.<p> * * Internal ordering is maintained according to the order of the given * Comparator. Of the Comparator methods, only * <code>compare(.)</code> is used. * * @version JDSL 2.1.1 * * @author Mark Handy (mdh) * @author Benoit Hudson (bh) * @author Luca Vismara (lv) * * @see Comparator */ public class ArrayHeap implements PriorityQueue, Serializable { // class variable(s) public static final int defaultInitialCapacity = 8; private static final int maxGrowableCapacity = 1 << 29; // = 2^29 private static final int shrinkLoadFactor = 4; // instance variable(s) /** * @serial */ private AHLocator [] array_; /** * @serial */ private int size_; /** * The comparator used to prioritize the keys. * @serial */ private Comparator comp_; /** * @serial */ private boolean shrink_; /** * @serial */ private Locator [] locatorsArray_ = null; /** * @serial */ private Object [] keysArray_ = null; /** * @serial */ private Object [] elementsArray_ = null; // constructor(s) /** * Creates a new heap. The initial capacity of the array is of * defaultInitialCapacity elements. The capacity is doubled when * needed, and halved when the load factor of the array is less than * or equal to 0.25. * * @param comp the comparator to be used for comparing keys * * @exception IllegalArgumentException if comp is null */ public ArrayHeap (Comparator comp) throws IllegalArgumentException { init(comp,defaultInitialCapacity,true); } /** * Creates a new heap. The initial capacity of the array is of * defaultInitialCapacity elements. The capacity is doubled when * needed, and possibly halved when the load factor of the array is * less than or equal to 0.25. * * @param comp the comparator to be used for comparing keys * @param shrink indicates whether the array should be halved when * the load factor of the array is less than or equal to 0.25. * * @exception IllegalArgumentException if comp is null */ public ArrayHeap (Comparator comp, boolean shrink) throws IllegalArgumentException { init(comp,defaultInitialCapacity,shrink); } /** * Creates a new heap. The capacity is doubled when needed, and * possibly halved when the load factor of the array is less than or * equal to 0.25. * * @param comp the comparator to be used for comparing keys * @param initialCapacity the initial capacity of the array; must be * a power of 2 no greater than 2^30 * @param shrink indicates whether the array should be halved when * the load factor of the array is less than or equal to 0.25. * * @exception IllegalArgumentException if comp is null or * initialCapacity is greater than 2^30 */ public ArrayHeap (Comparator comp, int initialCapacity, boolean shrink) throws IllegalArgumentException { if (initialCapacity > 2*maxGrowableCapacity) throw new IllegalArgumentException("The initial capacity must be no" +" greater than 2^30."); else { // approximate initialCapacity with the closest power of 2 // greater than initialCapacity; this could be done more // efficiently with a binary search on an array storing all the // powers of 2 between 1 and 2^30 int ic = 1; while (ic < initialCapacity) ic <<= 1; init(comp,ic,shrink); } } /** * A service method for the constructors. */ private void init (Comparator comp, int initialCapacity, boolean shrink) throws IllegalArgumentException { if (comp == null) throw new IllegalArgumentException("The comparator is null."); else comp_ = comp; array_ = new AHLocator[initialCapacity]; size_ = 0; shrink_ = shrink; } // instance method(s) from PriorityQueue /** * time complexity = worst-case O(1) */ public Locator min () { checkEmpty(); return array_[1]; } /** * If array-shrinking is specified at construction time and the load * factor of the array is 0.25 after the removal, the capacity of * the array is halved by copying the elements into a new array. * * time complexity = worst-case O(log n) if array-shrinking is * specified at construction time, amortized O(log n) otherwise */ public Object removeMin () { checkEmpty(); // remove the locator AHLocator ahLoc = array_[1]; ahLoc.resetContainer(); size_--; if(!isEmpty()) { // fix the heap property: take the last element and put it // first, then downheap swap(1,size_+1); downheap(1); } array_[size_+1] = null; ensureLoadFactor(); locatorsArray_ = null; keysArray_ = null; elementsArray_ = null; return ahLoc.element(); } // instance method(s) from KeyBasedContainer /** * If the array is full, its capacity is doubled before the * insertion by copying the elements into a new array. * * time complexity = amortized O(log n) */ public Locator insert (Object key, Object elem) { checkKey(key); ensureCapacity(size_+1); size_++; AHLocator ahLoc = new AHLocator(key,elem,size_,this); array_[size_] = ahLoc; upheap(size_); locatorsArray_ = null; keysArray_ = null; elementsArray_ = null; return ahLoc; } /** * If array-shrinking is specified at construction time and the load * factor of the array is 0.25 after the removal, the capacity of * the array is halved by copying the elements into a new array. * * time complexity = worst-case O(log n) if array-shrinking is * specified at construction time, amortized O(log n) otherwise */ public void remove (Locator loc) { AHLocator ahLoc = checkContained(loc); int index = ahLoc.index(); int oldsize = size_; // remove the old locator from the heap size_--; ahLoc.resetContainer(); // now restore the heap properties if(index != oldsize) { // move the last-inserted key up, and put it in its right spot // (either upheap or downheap will do nothing) swap(index,oldsize); upheap(index); downheap(index); } array_[oldsize] = null; ensureLoadFactor(); locatorsArray_ = null; keysArray_ = null; elementsArray_ = null; } /** * time complexity = worst-case O(log n) */ public Object replaceKey (Locator loc, Object key) { AHLocator ahLoc = checkContained(loc); checkKey(key); int index = ahLoc.index(); // replace the key Object returnKey = ahLoc.key(); ahLoc.setKey(key); // restore the heap property (either upheap or downheap will do nothing) upheap(index); downheap(index); keysArray_ = null; return returnKey; } // instance method(s) from InspectableKeyBasedContainer /** * Cached for constant factor efficiency. If the container has not * been structurally modified and no key has been modified since the * last time this method was invoked, there is no need to copy the * elements in a new array. * * time complexity = worst-case O(n) */ public ObjectIterator keys () { if (keysArray_ == null) { keysArray_ = new Object[size_]; for (int i = 1; i <= size_; i++) keysArray_[i-1] = array_[i].key(); } return new ArrayObjectIterator(keysArray_); } /** * Cached for constant factor efficiency. If the container has not * been structurally modified since the last time this method was * invoked, there is no need to copy the locators in a new array. * * time complexity = worst-case O(n) */ public LocatorIterator locators () { if (locatorsArray_ == null) { locatorsArray_ = new Locator[size_]; System.arraycopy(array_,1,locatorsArray_,0,size_); } return new ArrayLocatorIterator(locatorsArray_); } // instance method(s) from Container /** * time complexity = worst-case O(1) */ public Container newContainer () { return new ArrayHeap(comp_,shrink_); } /** * time complexity = worst-case O(1) */ public Object replaceElement (Accessor a, Object elem) { AHLocator ahLoc = checkContained(a); // replace the element Object returnElem = ahLoc.element(); ahLoc.setElement(elem); elementsArray_ = null; return returnElem; } // instance method(s) from InspectableContainer /** * time complexity = worst-case O(1) */ public boolean contains (Accessor a) { if (a == null) throw new InvalidAccessorException("The accessor is null."); else if (!(a instanceof AHLocator)) return false; else return ((AHLocator)a).container() == this; } /** * Cached for constant factor efficiency. If the container has not * been structurally modified and no element has been modified since * the last time this method was invoked, there is no need to copy * the elements in a new array. * * time complexity = worst-case O(n) */ public ObjectIterator elements () { if (elementsArray_ == null) { elementsArray_ = new Object[size_]; for (int i = 1; i <= size_; i++) elementsArray_[i-1] = array_[i].element(); } return new ArrayObjectIterator(elementsArray_); } /** * time complexity = worst-case O(1) */ public boolean isEmpty () { return size_ == 0; } /** * time complexity = worst-case O(1) */ public int size () { return size_; } // instance method(s) from java.lang.Object /** * time complexity = worst-case O(n) */ public String toString () { return ToString.stringfor(this); } // non-interface instance method(s) /** * Creates an InspectableBinaryTree snapshot view of the heap. * * time complexity = worst-case O(n) * * @return the inspectable binary tree snapshot view * @exception EmptyContainerException if the prority queue is empty */ public InspectableBinaryTree inspectableBinaryTree () { Sequence s = new NodeSequence(); BinaryTree bt = new NodeBinaryTree(); s.insertLast(bt.root()); for (int i = 1; i < size_+1; i++) { Position p = (Position)s.removeFirst(); bt.replaceElement(p,array_[i]); bt.expandExternal(p); s.insertLast(bt.leftChild(p)); s.insertLast(bt.rightChild(p)); } // while (!s.isEmpty()) { // Position p = (Position)s.removeFirst(); // bt.replaceElement(p,Container.NULL); // } return bt; } /** * Compares the key at index i1 with that at index i2. */ private int compare (int i1, int i2) { return comp_.compare(array_[i1].key(),array_[i2].key()); } /** * Swaps the locators at index i1 with the one at index i2. */ private void swap (int i1, int i2) { AHLocator temp = array_[i1]; array_[i1] = array_[i2]; array_[i1].setIndex(i1); array_[i2] = temp; array_[i2].setIndex(i2); } /** * Perform the upheap operation. Swaps a (key,element) pair up * until the heap property has been restored. */ private void upheap (int index) { array_[0] = array_[index]; // to avoid testing index > 1 // while we are not at the root, and we are smaller than our // parent (at floor(index/2)), then swap us up and continue at the // higher level while (compare(index,index/2) < 0) { swap(index,index/2); index /= 2; } array_[0] = null; } /** * Performs the downheap operation. Swaps a (key,element) pair down * until the heap property has been restored. */ private void downheap (int index) { boolean even = (size_%2 == 0); boolean again = true; int candidate; if (even) { // to avoid the special case of the last leaf being a left child array_[size_+1] = array_[size_]; size_++; } // While we are not at a leaf, and we are larger than either of our // children, sink down. We may be at a leaf when going to the // right child would put us past the end of the array while (again && index <= (size_-1)/2) { int left = 2*index; // left child index int right = 2*index+1; // right child index if (compare(left,right) > 0) candidate = right; // right is smaller than left else candidate = left; // left is smaller than or equal to right if (compare(index,candidate) > 0) { // candidate is smaller than index; it should be higher swap(index,candidate); index = candidate; } else // candidate is greater than or equal to index, so we are done again = false; } if (even) { array_[size_] = null; size_--; } } /** * Makes sure the array is large enough; if not, double the capacity. */ private void ensureCapacity (int size) { if (size >= array_.length) { if (array_.length <= maxGrowableCapacity) { AHLocator [] newArray = new AHLocator[array_.length*2]; System.arraycopy(array_,1,newArray,1,array_.length-1); array_ = newArray; } else throw new FullContainerException("Maximum capacity of the priority" +" queue exceeded."); } } /** * Makes sure the load factor of the array is greater than * shrinkLoadFactor; if not, halve the capacity. */ private void ensureLoadFactor () { // System.out.println((size_+1)+" "+array_.length+" " // +((float)(size_+1)/(float)array_.length)); // System.out.flush(); if (shrink_ && size_+1 <= array_.length/shrinkLoadFactor) { // reduce the size AHLocator [] newArray = new AHLocator[array_.length/2]; // copy the old array System.arraycopy(array_,1,newArray,1,size_); // throw away the old array and replace it with the new array_ = newArray; } } /** * Throws an exception if the container is empty. */ private void checkEmpty () { if (isEmpty()) throw new EmptyContainerException("ArrayHeap empty."); } /** * Throws an exception if key cannot be used by this container. */ private void checkKey (Object key) { if (!comp_.isComparable(key)) throw new InvalidKeyException(key+" not comparable by "+comp_+"."); } /** * Throws an exception if key cannot be used by this container. */ private AHLocator checkContained (Accessor a) { if (a == null) throw new InvalidAccessorException("The accessor is null."); else if (a instanceof AHLocator) { AHLocator ahLoc = (AHLocator)a; if (ahLoc.container() == this) return ahLoc; else throw new InvalidAccessorException (a+" not contained in this ArrayHeap."); } else throw new InvalidAccessorException("The accessor must extend"+ " AHLocator."); } // nested class(es) /** * The locator class for use within this heap. */ private static class AHLocator implements Locator { // instance variable(s) private Object key_; private Object elem_; private int index_; private ArrayHeap container_; // constructor(s) private AHLocator (Object key, Object elem, int index, ArrayHeap container) { key_ = key; elem_ = elem; index_ = index; container_ = container; } // instance method(s) from Locator public Object key () { return key_; } // instance method(s) from Accessor public Object element () { return elem_; } // instance method(s) from java.lang.Object public String toString () { return ToString.stringfor(this); } // non-interface instance methods private int index () { return index_; } private ArrayHeap container () { return container_; } private void setKey (Object key) { key_ = key; } private void setElement (Object elem) { elem_ = elem; } private void setIndex (int index) { index_ = index; } private void resetContainer () { container_ = null; } } // end of class AHLocator } // end of class ArrayHeap