All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tommyettinger.ds.BinaryHeap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022-2025 See AUTHORS file.
 *
 * 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.github.tommyettinger.ds;

import com.github.tommyettinger.digital.BitConversion;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A binary heap that stores nodes which each have a float value and are sorted either lowest first or highest first.
 * This can expand if its capacity is exceeded. It defaults to acting as a min-heap, sorting lowest-first.
 * The {@link Node} class can be extended to store additional information.
 * 
* This isn't a direct copy from libGDX, but it's very close. It implements {@link java.util.Queue} and {@link Collection}. * * @author Nathan Sweet * @author Tommy Ettinger */ @SuppressWarnings("unchecked") public class BinaryHeap extends AbstractQueue implements EnhancedCollection { public int size; private Node[] nodes; private final boolean isMaxHeap; @Nullable protected transient HeapIterator iterator1 = null; @Nullable protected transient HeapIterator iterator2 = null; /** * Constructs a BinaryHeap with 16 starting capacity, sorting lowest-first (a min-heap). */ public BinaryHeap () { this(16, false); } /** * Constructs a BinaryHeap with the specified capacity and sorting order. * * @param capacity the initial capacity * @param isMaxHeap if true, this will sort highest-first; if false, it will sort lowest-first */ public BinaryHeap (int capacity, boolean isMaxHeap) { this.isMaxHeap = isMaxHeap; nodes = new Node[capacity]; } /** * Constructs a BinaryHeap with the contents from the given Collection of nodes, sorting lowest-first (a min-heap). * If a duplicate node is present in {@code coll}, all repeats are ignored. * * @param coll a Collection of T (which must extend {@link Node}) or objects that subclass T */ public BinaryHeap (Collection coll) { this(false, coll); } /** * Constructs a BinaryHeap with the specified sorting order and the contents from the given Collection of nodes. * If a duplicate node is present in {@code coll}, all repeats are ignored. * * @param isMaxHeap if true, this will sort highest-first; if false, it will sort lowest-first * @param coll a Collection of T (which must extend {@link Node}) or objects that subclass T */ public BinaryHeap (boolean isMaxHeap, Collection coll) { this.isMaxHeap = isMaxHeap; nodes = new Node[coll.size()]; addAll(coll); } /** * Constructs a BinaryHeap with the contents from the given array of nodes, sorting lowest-first (a min-heap). * If a duplicate node is present in {@code arr}, all repeats are ignored. * * @param arr an array of T (which must extend {@link Node}) or objects that subclass T */ public BinaryHeap (T[] arr) { this(false, arr); } /** * Constructs a BinaryHeap with the specified sorting order and the contents from the given array of nodes. * If a duplicate node is present in {@code arr}, all repeats are ignored. * * @param isMaxHeap if true, this will sort highest-first; if false, it will sort lowest-first * @param arr an array of T (which must extend {@link Node}) or objects that subclass T */ public BinaryHeap (boolean isMaxHeap, T[] arr) { this.isMaxHeap = isMaxHeap; nodes = new Node[arr.length]; addAll(arr); } /** * Returns true if this is a max-heap (that is, it sorts highest-first), or false if this is a min-heap * (it sorts lowest-first). If not specified, this is usually false; it can be set only in the constructor, * such as {@link #BinaryHeap(int, boolean)} or {@link #BinaryHeap(boolean, Collection)}. * * @return true if this sorts highest-first; false if it sorts lowest-first */ public boolean isMaxHeap () { return isMaxHeap; } @Override public boolean addAll (Collection c) { if (c == this) { throw new IllegalArgumentException("A BinaryHeap cannot be added to itself."); } boolean modified = false; for (T t : c) { modified |= offer(t); } return modified; } public boolean addAll (T[] c) { return addAll(c, 0, c.length); } public boolean addAll (T[] c, int offset, int length) { boolean modified = false; for (int i = offset, n = offset + length; i < n; i++) { modified |= offer(c[i]); } return modified; } /** * Adds the node to the heap using its current value. The node should not already be in the heap. */ @Override public boolean add (T node) { // Expand if necessary. if (size == nodes.length) { Node[] newNodes = new Node[size << 1]; System.arraycopy(nodes, 0, newNodes, 0, size); nodes = newNodes; } // Insert at end and bubble up. node.index = size; nodes[size] = node; up(size++); return true; } /** * Inserts the specified element into this queue if it is possible to do * so immediately without violating capacity restrictions. * You can also use {@link #add(Node)}, but if you try to add a duplicate element * with that, an {@link IllegalStateException} is thrown. Here, if you try to add * a duplicate element, no Exception is thrown and this returns {@code false}. * * @param node the element to add; must not be null * @return {@code true} if the element was added to this queue, else * {@code false} (typically when node is already present in this BinaryHeap) * @throws ClassCastException if the class of the specified element * prevents it from being added to this queue * @throws NullPointerException if the specified element is null */ @Override public boolean offer (T node) { if (size == nodes.length) { Node[] newNodes = new Node[size << 1]; System.arraycopy(nodes, 0, newNodes, 0, size); nodes = newNodes; } // Insert at end and bubble up. node.index = size; nodes[size] = node; try { up(size++); } catch (IllegalStateException ise) { return false; } return true; } /** * Retrieves and removes the head of this queue, or returns {@code null} if this BinaryHeap is empty. * The head is the item with the lowest value (or highest value if this heap is configured as a max heap). * * @return the head of this BinaryHeap, or {@code null} if this queue is empty * @throws ClassCastException if the class of the specified element * prevents it from being added to this queue */ @Nullable @Override public T poll () { if (size == 0) return null; Node removed = nodes[0]; if (--size > 0) { nodes[0] = nodes[size]; nodes[size] = null; down(0); } else {nodes[0] = null;} return (T)removed; } /** * Retrieves and removes the head of this queue. This method differs * from {@link #poll()} only in that it throws an exception if this * queue is empty, and won't return null. * * @return the head of this queue * @throws NoSuchElementException if this queue is empty * @throws ClassCastException if the class of the specified element * prevents it from being added to this queue */ @Override public T remove () { if (size == 0) throw new NoSuchElementException("A BinaryHeap cannot be empty when remove() is called."); Node removed = nodes[0]; if (--size > 0) { nodes[0] = nodes[size]; nodes[size] = null; down(0); } else {nodes[0] = null;} return (T)removed; } /** * Sets the node's value and adds it to the heap. The node should not already be in the heap. */ public boolean add (T node, float value) { node.value = value; return add(node); } /** * Returns true if the heap contains the specified node. Exactly the same as {@link #contains(Object, boolean)} * with {@code identity} set to false. * * @param node should be a {@code T}, which must extend {@link Node}; can be some other type, which gives false * @implSpec This implementation iterates over the elements in the collection, * checking each element in turn for equality with the specified element via {@link Object#equals(Object)}. */ @Override public boolean contains (Object node) { for (Node other : nodes) {if (other.equals(node)) {return true;}} return false; } /** * Returns true if the heap contains the specified node. * * @param node should be a {@code T}, which must extend {@link Node}; can be some other type, which gives false * @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */ public boolean contains (Object node, boolean identity) { if (identity) { for (Node n : nodes) {if (n == node) {return true;}} } else { for (Node other : nodes) {if (other.equals(node)) {return true;}} } return false; } /** * Returns the first item in the heap. This is the item with the lowest value (or highest value if this heap is configured as * a max heap). If the heap is empty, throws an {@link NoSuchElementException}. * * @return the first item in the heap * @throws NoSuchElementException if the heap is empty. * @throws ClassCastException if the class of the specified element * prevents it from being added to this queue */ @Override public T element () { if (size == 0) {throw new NoSuchElementException("The heap is empty.");} return (T)nodes[0]; } /** * Returns the first item in the heap. This is the item with the lowest value (or highest value if this heap is configured as * a max heap). If the heap is empty, returns null. * * @return the first item in the heap, or null if the heap is empty * @throws ClassCastException if the class of the specified element * prevents it from being added to this queue */ @Nullable @Override public T peek () { if (size == 0) {return null;} return (T)nodes[0]; } /** * Retrieves and removes the head of this queue, or returns {@code null} if this BinaryHeap is empty. * The head is the item with the lowest value (or highest value if this heap is configured as a max heap). *
* This method is identical to {@link #poll()} in this class, but because poll() is defined as part of the * Queue interface, whereas this method was defined ad-hoc by libGDX, poll() should be preferred in new code. * * @return the head of this BinaryHeap, or {@code null} if this queue is empty */ @Nullable public T pop () { if (size == 0) return null; Node removed = nodes[0]; if (--size > 0) { nodes[0] = nodes[size]; nodes[size] = null; down(0); } else {nodes[0] = null;} return (T)removed; } /** * Removes the given node and returns it. If the node is not present in this BinaryHeap or is invalid, this will * probably throw an Exception. * * @param node a {@link Node} that should be present in this already * @return {@code node} after its removal */ public T remove (T node) { if (--size > 0) { Node moved = nodes[size]; nodes[size] = null; nodes[node.index] = moved; if (moved.value < node.value ^ isMaxHeap) {up(node.index);} else {down(node.index);} } else {nodes[0] = null;} return node; } @Override public int size () { return size; } /** * Returns true if the heap has one or more items. */ public boolean notEmpty () { return size != 0; } /** * Returns true if the heap is empty. */ @Override public boolean isEmpty () { return size == 0; } /** * Removes all nodes from this BinaryHeap. */ @Override public void clear () { Utilities.clear(nodes, 0, size); size = 0; } /** * Changes the value of the node, which should already be in the heap. */ public void setValue (T node, float value) { float oldValue = node.value; node.value = value; if (value < oldValue ^ isMaxHeap) {up(node.index);} else {down(node.index);} } private void up (int index) { Node[] nodes = this.nodes; Node node = nodes[index]; float value = node.value; while (index > 0) { int parentIndex = (index - 1) >> 1; Node parent = nodes[parentIndex]; if (node == parent) throw new IllegalStateException("Duplicate nodes are not allowed in a BinaryHeap."); if (value < parent.value ^ isMaxHeap) { nodes[index] = parent; parent.index = index; index = parentIndex; } else {break;} } nodes[index] = node; node.index = index; } private void down (int index) { Node[] nodes = this.nodes; int size = this.size; Node node = nodes[index]; float value = node.value; while (true) { int leftIndex = 1 + (index << 1); if (leftIndex >= size) {break;} int rightIndex = leftIndex + 1; // Always has a left child. Node leftNode = nodes[leftIndex]; float leftValue = leftNode.value; // May have a right child. Node rightNode; float rightValue; if (rightIndex >= size) { rightNode = null; rightValue = isMaxHeap ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; } else { rightNode = nodes[rightIndex]; rightValue = rightNode.value; } // The smallest of the three values is the parent. if (leftValue < rightValue ^ isMaxHeap) { if (leftValue == value || (leftValue > value ^ isMaxHeap)) {break;} nodes[index] = leftNode; leftNode.index = index; index = leftIndex; } else { if (rightValue == value || (rightValue > value ^ isMaxHeap)) {break;} nodes[index] = rightNode; if (rightNode != null) {rightNode.index = index;} index = rightIndex; } } while (index > 0) { int parentIndex = (index - 1) >> 1; Node parent = nodes[parentIndex]; if (value < parent.value ^ isMaxHeap) { nodes[index] = parent; parent.index = index; index = parentIndex; } else {break;} } nodes[index] = node; node.index = index; } @Override public boolean remove (Object o) { if(o instanceof Node) { if (--size > 0) { Node moved = nodes[size]; nodes[size] = null; Node node = (Node)o; nodes[node.index] = moved; if (moved.value < node.value ^ isMaxHeap) {up(node.index);} else {down(node.index);} } else {nodes[0] = null;} return true; } return false; } @Override public boolean containsAll (Collection<@NonNull ?> c) { for (Object o : c) { if (!contains(o)) {return false;} } return true; } /** * Exactly like {@link #containsAll(Collection)}, but takes an array instead of a Collection. * @see #containsAll(Collection) * @param array array to be checked for containment in this set * @return {@code true} if this set contains all the elements * in the specified array */ public boolean containsAll (@NonNull Object[] array) { for (Object o : array) { if (!contains(o)) return false; } return true; } /** * Like {@link #containsAll(Object[])}, but only uses at most {@code length} items from {@code array}, starting at {@code offset}. * @see #containsAll(Object[]) * @param array array to be checked for containment in this set * @param offset the index of the first item in array to check * @param length how many items, at most, to check from array * @return {@code true} if this set contains all the elements * in the specified range of array */ public boolean containsAll (@NonNull Object[] array, int offset, int length) { for (int i = offset, n = 0; n < length && i < array.length; i++, n++) { if(!contains(array[i])) return false; } return true; } /** * Returns true if this set contains any of the specified values. * * @param values must not contain nulls, and must not be null itself * @return true if this set contains any of the items in {@code values}, false otherwise */ public boolean containsAny (Iterable<@NonNull ?> values) { for (Object v : values) { if (contains(v)) {return true;} } return false; } /** * Returns true if this set contains any of the specified values. * * @param values must not contain nulls, and must not be null itself * @return true if this set contains any of the items in {@code values}, false otherwise */ public boolean containsAny (@NonNull Object[] values) { for (Object v : values) { if (contains(v)) {return true;} } return false; } /** * Returns true if this set contains any items from the specified range of values. * * @param values must not contain nulls, and must not be null itself * @param offset the index to start checking in values * @param length how many items to check from values * @return true if this set contains any of the items in the given range of {@code values}, false otherwise */ public boolean containsAny (@NonNull Object[] values, int offset, int length) { for (int i = offset, n = 0; n < length && i < values.length; i++, n++) { if (contains(values[i])) {return true;} } return false; } /** * Removes each object in {@code other} from this heap, removing an item once if it appears once, twice if it appears twice, * and so on. In this respect, this acts like {@link #removeEach(Iterable)} rather than Collection's removeAll(). * @see #removeEach(Iterable) * @param other collection containing elements to be removed from this collection * @return true if any elements were removed, or false otherwise */ @Override public boolean removeAll (@NonNull Collection<@NonNull ?> other) { return removeEach(other); } /** * Exactly like {@link #removeAll(Collection)}, but takes an array instead of a Collection. * This delegates entirely to {@link #removeEach(Object[])}, and does not act like removeAll() does in other * collections if there are duplicate nodes present in the heap. * @see #removeAll(Collection) * @param other array containing elements to be removed from this list * @return {@code true} if this list changed as a result of the call */ public boolean removeAll (@NonNull Object[] other) { return removeEach(other); } /** * Like {@link #removeAll(Object[])}, but only uses at most {@code length} items from {@code array}, starting at {@code offset}. * This delegates entirely to {@link #removeEach(Object[], int, int)}, and does not act like removeAll() does in other * collections if there are duplicate nodes present in the heap. * @see #removeAll(Object[]) * @see #removeEach(Object[], int, int) * @param array the elements to be removed from this list * @param offset the index of the first item in array to remove * @param length how many items, at most, to get from array and remove from this * @return {@code true} if this list changed as a result of the call */ public boolean removeAll (@NonNull Object[] array, int offset, int length) { return removeEach(array, offset, length); } /** * Removes from this ObjectList element-wise occurrences of elements contained in the specified Iterable. * Note that if a value is present more than once in this ObjectList, only one of those occurrences * will be removed for each occurrence of that value in {@code other}. If {@code other} has the same * contents as this ObjectList or has additional items, then removing each of {@code other} will clear this. * * @param other an Iterable of T items to remove one-by-one, such as another ObjectList or an ObjectSet * @return true if this list was modified. */ public boolean removeEach (@NonNull Iterable<@NonNull ?> other) { boolean changed = false; for(Object item : other) { changed |= remove(item); } return changed; } /** * Exactly like {@link #removeEach(Iterable)}, but takes an array instead of a Collection. * @see #removeEach(Iterable) * @param array array containing elements to be removed from this list * @return {@code true} if this list changed as a result of the call */ public boolean removeEach (@NonNull Object @NonNull [] array) { return removeEach(array, 0, array.length); } /** * Like {@link #removeEach(Object[])}, but only uses at most {@code length} items from {@code array}, starting at {@code offset}. * @see #removeEach(Object[]) * @param array the elements to be removed from this list * @param offset the index of the first item in array to remove * @param length how many items, at most, to get from array and remove from this * @return {@code true} if this list changed as a result of the call */ public boolean removeEach (@NonNull Object @NonNull [] array, int offset, int length) { boolean changed = false; for (int i = offset, n = 0; n < length && i < array.length; i++, n++) { changed |= remove(array[i]); } return changed; } @Override public boolean equals (Object obj) { if (!(obj instanceof BinaryHeap)) {return false;} BinaryHeap other = (BinaryHeap)obj; if (other.size != size) {return false;} Node[] nodes1 = this.nodes, nodes2 = other.nodes; for (int i = 0, n = size; i < n; i++) {if (nodes1[i].value != nodes2[i].value) {return false;}} return true; } @Override public int hashCode () { int h = 1; Node[] nodes = this.nodes; for (int i = 0, n = size; i < n; i++) { h += BitConversion.floatToRawIntBits(nodes[i].value); h ^= h >>> 15; } return h; } @Override public String toString () { return toString(", ", true); } /** * Returns an iterator over the elements contained in this collection. * * @return an iterator over the elements contained in this collection */ @Override public @NonNull HeapIterator iterator () { if (iterator1 == null || iterator2 == null) { iterator1 = new HeapIterator<>(this); iterator2 = new HeapIterator<>(this); } if (!iterator1.valid) { iterator1.reset(); iterator1.valid = true; iterator2.valid = false; return iterator1; } iterator2.reset(); iterator2.valid = true; iterator1.valid = false; return iterator2; } public static class HeapIterator implements Iterator { private final BinaryHeap heap; private int index; private boolean valid = true; public HeapIterator (BinaryHeap binaryHeap) { heap = binaryHeap; index = 0; } /** * Returns {@code true} if the iteration has more elements. * (In other words, returns {@code true} if {@link #next} would * return an element rather than throwing an exception.) * * @return {@code true} if the iteration has more elements */ @Override public boolean hasNext () { if (!valid) {throw new RuntimeException("#iterator() cannot be used nested.");} return index < heap.size; } /** * Returns the next element in the iteration. * * @return the next element in the iteration */ @Override public T next () { if (!valid) {throw new RuntimeException("#iterator() cannot be used nested.");} if (index >= heap.size) {throw new NoSuchElementException();} return (T)heap.nodes[index++]; } public void reset () { index = 0; } } /** * A binary heap node. Has a float value that is used to compare this Node with others, * and an int index that is used inside BinaryHeap. This class is often extended so * requisite functionality can be supplied and sorted by BinaryHeap. * * @author Nathan Sweet */ public static class Node { /** * The value that is used to compare this Node with others. */ public float value; /** * Used internally by BinaryHeap; generally not modified by external code, but may need to be read. */ public int index; /** * @param value The initial value for the node. To change the value, use {@link BinaryHeap#add(Node, float)} if the node is * not in the heap, or {@link BinaryHeap#setValue(Node, float)} if the node is in the heap. */ public Node (float value) { this.value = value; } public float getValue () { return value; } @Override public String toString () { return Float.toString(value); } } /** * Builds a BinaryHeap with the min-heap property from the given array or varargs of items that extend {@link Node}. * This is equivalent to {@link #minHeapWith(Node[])}. * * @param array an array or varargs of items that extend {@link Node} * @param must extend {@link Node} * @return a new BinaryHeap of T with the min-heap property. */ @SafeVarargs public static BinaryHeap with (T... array) { return new BinaryHeap<>(false, array); } /** * Builds a BinaryHeap with the min-heap property from the given array or varargs of items that extend {@link Node}. * This is equivalent to {@link #with(Node[])}. * * @param array an array or varargs of items that extend {@link Node} * @param must extend {@link Node} * @return a new BinaryHeap of T with the min-heap property. */ @SafeVarargs public static BinaryHeap minHeapWith (T... array) { return new BinaryHeap<>(false, array); } /** * Builds a BinaryHeap with the max-heap property from the given array or varargs of items that extend {@link Node}. * * @param array an array or varargs of items that extend {@link Node} * @param must extend {@link Node} * @return a new BinaryHeap of T with the max-heap property. */ @SafeVarargs public static BinaryHeap maxHeapWith (T... array) { return new BinaryHeap<>(true, array); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy