package de.lmu.ifi.dbs.elki.utilities.datastructures.heap; /* This file is part of ELKI: Environment for Developing KDD-Applications Supported by Index-Structures Copyright (C) 2012 Ludwig-Maximilians-Universität München Lehr- und Forschungseinheit für Datenbanksysteme ELKI Development Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import java.io.Serializable; import java.util.AbstractQueue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; import de.lmu.ifi.dbs.elki.math.MathUtil; /** * Basic in-memory heap structure. Closely related to a * {@link java.util.PriorityQueue}, but here we can override methods to obtain * e.g. a {@link TopBoundedHeap} * * Additionally, this heap is built lazily: if you first add many elements, then * poll the heap, it will be bulk-loaded in O(n) instead of iteratively built in * O(n log n). This is implemented via a simple validTo counter. * * @author Erich Schubert * * @param Element type. Should be {@link java.lang.Comparable} or a * {@link java.util.Comparator} needs to be given. */ public class Heap extends AbstractQueue implements Serializable { /** * Serial version */ private static final long serialVersionUID = 1L; /** * Heap storage */ protected transient Object[] queue; /** * Current number of objects */ protected int size = 0; /** * Indicate up to where the heap is valid */ protected int validSize = 0; /** * The comparator or {@code null} */ protected final Comparator comparator; /** * (Structural) modification counter. Used to invalidate iterators. */ public transient int modCount = 0; /** * Default initial capacity */ private static final int DEFAULT_INITIAL_CAPACITY = 11; /** * Default constructor: default capacity, natural ordering. */ public Heap() { this(DEFAULT_INITIAL_CAPACITY, null); } /** * Constructor with initial capacity, natural ordering. * * @param size initial size */ public Heap(int size) { this(size, null); } /** * Constructor with {@link Comparator}. * * @param comparator Comparator */ public Heap(Comparator comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); } /** * Constructor with initial capacity and {@link Comparator}. * * @param size initial capacity * @param comparator Comparator */ @SuppressWarnings("unchecked") public Heap(int size, Comparator comparator) { super(); this.size = 0; this.queue = new Object[size]; this.comparator = (Comparator) comparator; } @Override public boolean add(E e) { // Never full - overriding probably slightly faster return offer(e); } @Override public boolean offer(E e) { // resize when needed if(size + 1 > queue.length) { resize(size + 1); } // final int pos = size; this.queue[size] = e; this.size += 1; heapifyUp(size - 1, e); validSize += 1; // We have changed - return true according to {@link Collection#put} modCount++; return true; } @Override public E peek() { if(size == 0) { return null; } ensureValid(); return castQueueElement(0); } @Override public E poll() { ensureValid(); return removeAt(0); } /** * Repair the heap */ protected void ensureValid() { if(validSize != size) { if(size > 1) { // Bottom up heap update. if(comparator != null) { // Parent of first invalid int nextmin = validSize > 0 ? ((validSize - 1) >>> 1) : 0; int curmin = MathUtil.nextAllOnesInt(nextmin); // Next line int nextmax = curmin - 1; // End of valid line int pos = (size - 2) >>> 1; // Parent of last element // System.err.println(validSize+"<="+size+" iter:"+pos+"->"+curmin+", "+nextmin); while(pos >= nextmin) { // System.err.println(validSize+"<="+size+" iter:"+pos+"->"+curmin); while(pos >= curmin) { if(!heapifyDownComparator(pos, queue[pos])) { final int parent = (pos - 1) >>> 1; if(parent < curmin) { nextmin = Math.min(nextmin, parent); nextmax = Math.max(nextmax, parent); } } pos--; } curmin = nextmin; pos = Math.min(pos, nextmax); nextmax = -1; } } else { // Parent of first invalid int nextmin = validSize > 0 ? ((validSize - 1) >>> 1) : 0; int curmin = MathUtil.nextAllOnesInt(nextmin); // Next line int nextmax = curmin - 1; // End of valid line int pos = (size - 2) >>> 1; // Parent of last element // System.err.println(validSize+"<="+size+" iter:"+pos+"->"+curmin+", "+nextmin); while(pos >= nextmin) { // System.err.println(validSize+"<="+size+" iter:"+pos+"->"+curmin); while(pos >= curmin) { if(!heapifyDownComparable(pos, queue[pos])) { final int parent = (pos - 1) >>> 1; if(parent < curmin) { nextmin = Math.min(nextmin, parent); nextmax = Math.max(nextmax, parent); } } pos--; } curmin = nextmin; pos = Math.min(pos, nextmax); nextmax = -1; } } } validSize = size; } } /** * Remove the element at the given position. * * @param pos Element position. */ protected E removeAt(int pos) { if(pos < 0 || pos >= size) { return null; } final E ret = castQueueElement(pos); // Replacement object: final Object reinsert = queue[size - 1]; queue[size - 1] = null; // Keep heap in sync if(validSize == size) { size -= 1; validSize -= 1; heapifyDown(pos, reinsert); } else { size -= 1; validSize = Math.min(pos >>> 1, validSize); queue[pos] = reinsert; } modCount++; return ret; } /** * Execute a "Heapify Upwards" aka "SiftUp". Used in insertions. * * @param pos insertion position * @param elem Element to insert */ protected void heapifyUp(int pos, E elem) { assert (pos < size && pos >= 0); if(comparator != null) { heapifyUpComparator(pos, elem); } else { heapifyUpComparable(pos, elem); } } /** * Execute a "Heapify Upwards" aka "SiftUp". Used in insertions. * * @param pos insertion position * @param elem Element to insert */ @SuppressWarnings("unchecked") protected void heapifyUpComparable(int pos, Object elem) { final Comparable cur = (Comparable) elem; // queue[pos]; while(pos > 0) { final int parent = (pos - 1) >>> 1; Object par = queue[parent]; if(cur.compareTo(par) >= 0) { break; } queue[pos] = par; pos = parent; } queue[pos] = cur; } /** * Execute a "Heapify Upwards" aka "SiftUp". Used in insertions. * * @param pos insertion position * @param cur Element to insert */ protected void heapifyUpComparator(int pos, Object cur) { while(pos > 0) { final int parent = (pos - 1) >>> 1; Object par = queue[parent]; if(comparator.compare(cur, par) >= 0) { break; } queue[pos] = par; pos = parent; } queue[pos] = cur; } /** * Execute a "Heapify Downwards" aka "SiftDown". Used in deletions. * * @param pos re-insertion position * @param reinsert Object to reinsert * @return true when the order was changed */ protected boolean heapifyDown(int pos, Object reinsert) { assert (pos >= 0); if(comparator != null) { return heapifyDownComparator(pos, reinsert); } else { return heapifyDownComparable(pos, reinsert); } } /** * Execute a "Heapify Downwards" aka "SiftDown". Used in deletions. * * @param ipos re-insertion position * @return true when the order was changed */ @SuppressWarnings("unchecked") protected boolean heapifyDownComparable(final int ipos, Object reinsert) { Comparable cur = (Comparable) reinsert; int pos = ipos; final int half = size >>> 1; while(pos < half) { // Get left child (must exist!) int cpos = (pos << 1) + 1; Object child = queue[cpos]; // Test right child, if present final int rchild = cpos + 1; if(rchild < size) { Object right = queue[rchild]; if(((Comparable) child).compareTo(right) > 0) { cpos = rchild; child = right; } } if(cur.compareTo(child) <= 0) { break; } queue[pos] = child; pos = cpos; } queue[pos] = cur; return (pos == ipos); } /** * Execute a "Heapify Downwards" aka "SiftDown". Used in deletions. * * @param ipos re-insertion position * @return true when the order was changed */ protected boolean heapifyDownComparator(final int ipos, Object cur) { int pos = ipos; final int half = size >>> 1; while(pos < half) { int min = pos; Object best = cur; final int lchild = (pos << 1) + 1; Object left = queue[lchild]; if(comparator.compare(best, left) > 0) { min = lchild; best = left; } final int rchild = lchild + 1; if(rchild < size) { Object right = queue[rchild]; if(comparator.compare(best, right) > 0) { min = rchild; best = right; } } if(min == pos) { break; } queue[pos] = best; pos = min; } queue[pos] = cur; return (pos == ipos); } @SuppressWarnings("unchecked") protected final E castQueueElement(int n) { return (E) queue[n]; } @Override public int size() { return this.size; } /** * Test whether we need to resize to have the requested capacity. * * @param requiredSize required capacity */ protected final void resize(int requiredSize) { // Double until 64, then increase by 50% each time. int newCapacity = ((queue.length < 64) ? ((queue.length + 1) * 2) : ((queue.length / 2) * 3)); // overflow? if(newCapacity < 0) { throw new OutOfMemoryError(); } if(requiredSize > newCapacity) { newCapacity = requiredSize; } queue = Arrays.copyOf(queue, newCapacity); } @Override public void clear() { // clean up references in the array for memory management for(int i = 0; i < size; i++) { queue[i] = null; } this.size = 0; this.validSize = -1; modCount++; } @Override public boolean contains(Object o) { if(o != null) { // TODO: use order to reduce search space? for(int i = 0; i < size; i++) { if(o.equals(queue[i])) { return true; } } } return false; } @Override public Iterator iterator() { return new Itr(); } @Override public boolean addAll(Collection c) { final int addsize = c.size(); if(addsize <= 0) { return false; } if(size + addsize > queue.length) { resize(size + addsize); } for(E elem : c) { add(elem); } return true; } /** * Iterator over queue elements. No particular order (i.e. heap order!) * * @author Erich Schubert * * @apiviz.exclude */ protected final class Itr implements Iterator { /** * Cursor position */ private int cursor = 0; /** * Modification counter this iterator is valid for. */ private int expectedModCount = modCount; @Override public boolean hasNext() { return cursor < size; } @Override public E next() { if(expectedModCount != modCount) { throw new ConcurrentModificationException(); } if(cursor < size) { return castQueueElement(cursor++); } throw new NoSuchElementException(); } @Override public void remove() { if(expectedModCount != modCount) { throw new ConcurrentModificationException(); } if(cursor > 0) { cursor--; } else { throw new IllegalStateException(); } expectedModCount = modCount; } } /** * Return the heap as a sorted array list, by repeated polling. This will * empty the heap! * * @return new array list */ public ArrayList toSortedArrayList() { ArrayList ret = new ArrayList(size()); while(!isEmpty()) { ret.add(poll()); } return ret; } /** * Test whether the heap is still valid. * * Debug method. * * @return {@code null} when the heap is correct */ protected String checkHeap() { ensureValid(); if(comparator == null) { for(int i = 1; i < size; i++) { final int parent = (i - 1) >>> 1; @SuppressWarnings("unchecked") Comparable po = (Comparable) queue[parent]; if(po.compareTo(queue[i]) > 0) { return "@" + parent + ": " + queue[parent] + " < @" + i + ": " + queue[i]; } } } else { for(int i = 1; i < size; i++) { final int parent = (i - 1) >>> 1; if(comparator.compare(queue[parent], queue[i]) > 0) { return "@" + parent + ": " + queue[parent] + " < @" + i + ": " + queue[i]; } } } return null; } }