267 lines
8.3 KiB
Kotlin
267 lines
8.3 KiB
Kotlin
/*
|
|
* Copyright 2023 dorkbox, llc
|
|
*
|
|
* 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.
|
|
*/
|
|
/*******************************************************************************
|
|
* Copyright 2011 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 dorkbox.collections
|
|
|
|
import dorkbox.collections.BinaryHeap.Node
|
|
import java.util.*
|
|
|
|
/** A binary heap that stores nodes which each have a float value and are sorted either lowest first or highest first. The
|
|
* [Node] class can be extended to store additional information.
|
|
* @author Nathan Sweet
|
|
*/
|
|
class BinaryHeap<T : BinaryHeap.Node?> @JvmOverloads constructor(capacity: Int = 16, private val isMaxHeap: Boolean = false) {
|
|
companion object {
|
|
const val version = Collections.version
|
|
}
|
|
|
|
var size = 0
|
|
private var nodes: Array<Node?>
|
|
|
|
init {
|
|
nodes = arrayOfNulls(capacity)
|
|
}
|
|
|
|
/**
|
|
* Adds the node to the heap using its current value. The node should not already be in the heap.
|
|
* @return The specified node.
|
|
*/
|
|
fun add(node: T): T {
|
|
// Expand if necessary.
|
|
if (size == nodes.size) {
|
|
val newNodes = arrayOfNulls<Node>(size shl 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 node
|
|
}
|
|
|
|
/** Sets the node's value and adds it to the heap. The node should not already be in the heap.
|
|
* @return The specified node.
|
|
*/
|
|
fun add(node: T, value: Float): T {
|
|
node!!.value = value
|
|
return add(node)
|
|
}
|
|
|
|
/** Returns true if the heap contains the specified node.
|
|
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
|
*/
|
|
fun contains(node: T?, identity: Boolean): Boolean {
|
|
requireNotNull(node) { "node cannot be null." }
|
|
if (identity) {
|
|
for (n in nodes) if (n === node) return true
|
|
}
|
|
else {
|
|
for (other in nodes) if (other == 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). */
|
|
fun peek(): T? {
|
|
check(size != 0) { "The heap is empty." }
|
|
@Suppress("UNCHECKED_CAST")
|
|
return nodes[0] as T?
|
|
}
|
|
|
|
/** Removes the first item in the heap and returns it. This is the item with the lowest value (or highest value if this heap is
|
|
* configured as a max heap). */
|
|
fun pop(): T? {
|
|
val removed = nodes[0]
|
|
if (--size > 0) {
|
|
nodes[0] = nodes[size]
|
|
nodes[size] = null
|
|
down(0)
|
|
}
|
|
else nodes[0] = null
|
|
@Suppress("UNCHECKED_CAST")
|
|
return removed as T?
|
|
}
|
|
|
|
/** @return The specified node.
|
|
*/
|
|
fun remove(node: T): T {
|
|
if (--size > 0) {
|
|
val moved = nodes[size]
|
|
nodes[size] = null
|
|
nodes[node!!.index] = moved
|
|
if ((moved!!.value < node.value) xor isMaxHeap) up(node.index) else down(node.index)
|
|
}
|
|
else nodes[0] = null
|
|
return node
|
|
}
|
|
|
|
/** Returns true if the heap has one or more items. */
|
|
fun notEmpty(): Boolean {
|
|
return size > 0
|
|
}
|
|
|
|
val isEmpty: Boolean
|
|
/** Returns true if the heap is empty. */
|
|
get() = size == 0
|
|
|
|
fun clear() {
|
|
Arrays.fill(nodes, 0, size, null)
|
|
size = 0
|
|
}
|
|
|
|
/** Changes the value of the node, which should already be in the heap. */
|
|
fun setValue(node: T, value: Float) {
|
|
val oldValue = node!!.value
|
|
node.value = value
|
|
if ((value < oldValue) xor isMaxHeap) up(node.index) else down(node.index)
|
|
}
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
private fun up(index: Int) {
|
|
var index = index
|
|
val nodes = nodes
|
|
val node = nodes[index]
|
|
val value = node!!.value
|
|
while (index > 0) {
|
|
val parentIndex = index - 1 shr 1
|
|
val parent = nodes[parentIndex]
|
|
if ((value < parent!!.value) xor isMaxHeap) {
|
|
nodes[index] = parent
|
|
parent.index = index
|
|
index = parentIndex
|
|
}
|
|
else break
|
|
}
|
|
nodes[index] = node
|
|
node.index = index
|
|
}
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
private fun down(index: Int) {
|
|
var index = index
|
|
val nodes = nodes
|
|
val size = size
|
|
val node = nodes[index]
|
|
val value = node!!.value
|
|
while (true) {
|
|
val leftIndex = 1 + (index shl 1)
|
|
if (leftIndex >= size) break
|
|
val rightIndex = leftIndex + 1
|
|
|
|
// Always has a left child.
|
|
val leftNode = nodes[leftIndex]
|
|
val leftValue = leftNode!!.value
|
|
|
|
// May have a right child.
|
|
var rightNode: Node?
|
|
var rightValue: Float
|
|
if (rightIndex >= size) {
|
|
rightNode = null
|
|
rightValue = if (isMaxHeap) -Float.MAX_VALUE else Float.MAX_VALUE
|
|
}
|
|
else {
|
|
rightNode = nodes[rightIndex]
|
|
rightValue = rightNode!!.value
|
|
}
|
|
|
|
// The smallest of the three values is the parent.
|
|
if ((leftValue < rightValue) xor isMaxHeap) {
|
|
if (leftValue == value || (leftValue > value) xor isMaxHeap) break
|
|
nodes[index] = leftNode
|
|
leftNode.index = index
|
|
index = leftIndex
|
|
}
|
|
else {
|
|
if (rightValue == value || (rightValue > value) xor isMaxHeap) break
|
|
nodes[index] = rightNode
|
|
if (rightNode != null) rightNode.index = index
|
|
index = rightIndex
|
|
}
|
|
}
|
|
nodes[index] = node
|
|
node.index = index
|
|
}
|
|
|
|
override fun equals(other: Any?): Boolean {
|
|
if (other !is BinaryHeap<*>) return false
|
|
if (other.size != size) return false
|
|
val nodes1 = nodes
|
|
val nodes2 = other.nodes
|
|
var i = 0
|
|
val n = size
|
|
while (i < n) {
|
|
if (nodes1[i]!!.value != nodes2[i]!!.value) return false
|
|
i++
|
|
}
|
|
return true
|
|
}
|
|
|
|
override fun hashCode(): Int {
|
|
var h = 1
|
|
val nodes = nodes
|
|
var i = 0
|
|
val n = size
|
|
while (i < n) {
|
|
h = h * 31 + java.lang.Float.floatToIntBits(nodes[i]!!.value)
|
|
i++
|
|
}
|
|
return h
|
|
}
|
|
|
|
override fun toString(): String {
|
|
if (size == 0) return "[]"
|
|
val nodes = nodes
|
|
val buffer = StringBuilder(32)
|
|
buffer.append('[')
|
|
buffer.append(nodes[0]!!.value)
|
|
for (i in 1 until size) {
|
|
buffer.append(", ")
|
|
buffer.append(nodes[i]!!.value)
|
|
}
|
|
buffer.append(']')
|
|
return buffer.toString()
|
|
}
|
|
|
|
/** A binary heap node.
|
|
* @author Nathan Sweet
|
|
*/
|
|
open class Node
|
|
/** @param value The initial value for the node. To change the value, use [BinaryHeap.add] if the node is
|
|
* not in the heap, or [BinaryHeap.setValue] if the node is in the heap.
|
|
*/(var value: Float) {
|
|
var index = 0
|
|
override fun toString(): String {
|
|
return value.toString()
|
|
}
|
|
}
|
|
}
|