/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ package com.analog.lyric.collect; import java.util.ArrayList; import java.util.Iterator; import org.eclipse.jdt.annotation.Nullable; /** * Array-based binary heap implementation of {@link IHeap}. * <p> * Insertion, removal from head of the queue, and changing priority is O(log({@link #size})). * Bulk insertion and priority changes with deferred reordering is O(n + {@link #size}). * * @author Christopher Barber * @since 0.0.5 */ public class BinaryHeap<E> extends AbstractHeap<E> { /** * A heap entry. * * @see BinaryHeap#offer * @since 0.05 */ public static class Entry<E> extends AbstractEntry<E> { private int _offset; protected Entry(E element, double priority) { super(element, priority); _offset = -1; } protected Entry(Entry<E> that) { super(that); _offset = that._offset; } @Override public Entry<E> clone() { return new Entry<E>(this); } @Override public final boolean isOwned() { return _offset >= 0; } } private final ArrayList<Entry<E>> _heap; private int _orderedUpto; private boolean _deferOrdering; /*-------------- * Construction */ /** * Construct empty heap. */ public BinaryHeap() { this(16); } /** * Construct empty heap with initial space for {@code capacity} elements. */ public BinaryHeap(int capacity) { _heap = new ArrayList<Entry<E>>(capacity); _deferOrdering = true; _orderedUpto = 0; } /** * Construct a copy of the heap. */ public BinaryHeap(BinaryHeap<E> that) { @SuppressWarnings("unchecked") final ArrayList<Entry<E>> heap = (ArrayList<Entry<E>>) that._heap.clone(); _heap = heap; _deferOrdering = that._deferOrdering; _orderedUpto = that._orderedUpto; for (int i = 0, end = heap.size(); i < end; ++i) { heap.set(i, heap.get(i).clone()); } } public static <T> BinaryHeap<T> create() { return new BinaryHeap<T>(); } public static <T> BinaryHeap<T> create(int capacity) { return new BinaryHeap<T>(capacity); } @Override public BinaryHeap<E> clone() { return new BinaryHeap<E>(this); } /*-------------------- * Collection methods */ @Override public void clear() { _heap.clear(); _deferOrdering = true; _orderedUpto = 0; } @Override public final int size() { return _heap.size(); } /*------------------------------- * IDynamicPriorityQueue methods */ @Override public boolean changePriority(IEntry<E> oldEntry, double priority) { checkPriority(priority); if (!containsEntry(oldEntry)) { return false; } final Entry<E> entry = (Entry<E>)oldEntry; final double oldPriority = entry._priority; entry._priority = priority; if (_deferOrdering) { _orderedUpto = Math.min(entry._offset, _orderedUpto); } else { heapAdjust(entry, oldPriority); } return true; } @Override public boolean containsEntry(IEntry<E> entry) { return entry instanceof Entry && containsEntry((Entry<E>)entry); } /** * @see #containsEntry(IEntry) */ public boolean containsEntry(Entry<E> entry) { final int offset = entry._offset; return offset >= 0 && offset < _heap.size() && entry == _heap.get(offset); } @Override public final boolean deferOrdering() { return _deferOrdering; } @Override public boolean deferOrdering(boolean defer) { if (defer != _deferOrdering) { _deferOrdering = defer; if (!defer) { heapify(); } } return true; } @Override public boolean deferOrderingForBulkAdd(int n) { return _deferOrdering || n >= _heap.size() && deferOrdering(true); } @Override public boolean deferOrderingForBulkChange(int n) { return _deferOrdering || n >= _heap.size() / 2 && deferOrdering(true); } @Override public void ensureCapacity(int capacity) { _heap.ensureCapacity(capacity); } @Override public @Nullable Entry<E> entryForElement(@Nullable Object element) { for (int i = 0, end = _heap.size(); i < end; ++i) { Entry<E> entry = _heap.get(i); if (entry.getElement().equals(element)) { return entry; } } return null; } @Override public Iterator<? extends IEntry<E>> entryIterator() { return _heap.iterator(); } @Override public final boolean isOrdered() { return _orderedUpto >= _heap.size(); } @Override public boolean merge(IHeap<E> other) { if (!(other instanceof BinaryHeap)) { return super.merge(other); } BinaryHeap<E> that = (BinaryHeap<E>)other; final int prevSize = _heap.size(); _heap.addAll(that._heap); for (int i = prevSize, end = _heap.size(); i < end; ++i) { _heap.get(i)._offset = i; } that.clear(); heapify(); return true; } @Override public Entry<E> offer(E element, double priority) { checkPriority(priority); final Entry<E> entry = new Entry<E>(element, priority); entry._offset = _heap.size(); _heap.add(entry); if (!_deferOrdering) { heapRaise(entry); _orderedUpto = _heap.size(); } return entry; } @Override public @Nullable Entry<E> peekEntry() { Entry<E> entry = null; if (_heap.size() > 0) { heapify(); entry =_heap.get(0); } return entry; } @Override public @Nullable Entry<E> pollEntry() { Entry<E> entry = null; final ArrayList<Entry<E>> heap = _heap; final int size = heap.size(); switch (size) { case 0: break; case 1: entry = heap.remove(0); entry._offset = -1; _orderedUpto = 0; _deferOrdering = true; break; default: heapify(); entry = heap.get(0); removeEntryNoCheck(entry); break; } return entry; } @Override public boolean removeEntry(IEntry<E> entry) { return entry instanceof Entry && removeEntry((Entry<E>)entry); } /** * @see #removeEntry(IEntry) */ public boolean removeEntry(Entry<E> entry) { if (containsEntry(entry)) { removeEntryNoCheck(entry); return true; } return false; } /*--------- * Private */ private void checkPriority(double priority) { if (Double.isNaN(priority)) { throw new IllegalArgumentException("priority is not a number"); } } private void heapAdjust(Entry<E> entry, double oldPriority) { final double priority = entry._priority; if (priority < oldPriority) { heapRaise(entry); } else if (priority > oldPriority) { heapify(entry._offset); } } /** * Raise entry in heap until its priority is lower than everything below it. * Only appropriate to call if entry's children are known to have higher * priority. */ private void heapRaise(Entry<E> entry) { final double priority = entry._priority; for (int i = entry._offset; i > 0;) { final int parenti = (i - 1) / 2; final Entry<E> parent = _heap.get(parenti); if (parent._priority <= priority) { break; } heapSet(i, parent); heapSet(parenti, entry); i = parenti; } } /** * Reorder heap. */ private void heapify() { final int orderedUpto = _orderedUpto; final int size = _heap.size(); if (orderedUpto < size) { final int halfSize = size / 2; if (orderedUpto <= halfSize) { // Less than half the heap is already ordered, so do a full reorder. for (int i = halfSize; --i >= 0;) { heapify(i); } } else { // Most of the heap is already ordered and the unordered entries are // all leaf nodes, so we can just raise them in order. for (int i = orderedUpto; i < size; ++i) { heapRaise(_heap.get(i)); } } _orderedUpto = size; } _deferOrdering = false; } /** * Builds min heap rooted at index i given that i's children are already * min heaps */ private void heapify(int i) { final ArrayList<Entry<E>> heap = _heap; final int end = _heap.size(); while (true) { final Entry<E> entry = heap.get(i); final int l = i * 2 + 1; final int r = l + 1; double p = entry._priority; int min = i; if (l < end) { final double lp = heap.get(l)._priority; if (lp < p) { min = l; p = lp; } if (r < end) { if (heap.get(r)._priority < p) { min = r; } } } if (min == i) { break; } heapSet(i, heap.get(min)); heapSet(min, entry); i = min; } } /** * Set i'th slot of heap array to given entry and * update entry's {@link #_offset} accordingly. */ private void heapSet(int i, Entry<E> entry) { entry._offset = i; _heap.set(i, entry); } private void removeEntryNoCheck(Entry<E> entry) { final int size = _heap.size(); Entry<E> lastEntry = _heap.remove(size - 1); if (entry != lastEntry) { heapSet(entry._offset, lastEntry); if (_deferOrdering) { _orderedUpto = Math.min(entry._offset, _orderedUpto); } else { heapAdjust(lastEntry, entry._priority); _orderedUpto = size - 1; } } else { _orderedUpto = Math.min(entry._offset, _orderedUpto); } if (_heap.isEmpty()) { _deferOrdering = true; } entry._offset = -1; } }