I am at a loss about the role of change values/change_val(),
lack of in-code documentation contributing - there is KDoc.
It may be due to not using the common names sift-up&sift-down.
insert(item, priority) might check whether item already is in positions.
This may be a good place to design and specify exceptions.
In pop(), resize after decreasing size.
In change_priority(), you could compare positions[item]!! to heap_size - 1 for an early out.
One could compare old and new priority and let the item stay in place, sift-up or sift-down accordingly.
change_val() and heapify() do avoidable assignments of "the moving" Data to indices where it doesn't stay.
You don't need continue in heapify():
if (min == _root)
break
swap(min, _root)
_root = min
min = _root
But I think the loop termination indirect anyway:
/** Set data and keep position */
private fun put(item:Data<T>?, at:Int) {
data[at] = item
positions[item!!.item] = at
}
protected fun heapify(root: Int) {
var left = left(root)
val sinking = data[root]!! // pick up
var _root = root // an index to bubble up to
// two conditions to quit looping:
while (left < heap_size) { // 1: at leaf
var min = left
var minData = data[left]!!
val right = right(_root)
if (right < heap_size) {
val rightData = data[right]!!
if (rightData < minData) {
min = right
minData = rightData
}
}
if (sinking <= minData) // 2: sunk deep enough
break
put(minData, _root) // bubble up
_root = min
left = left(_root)
}
if (root != _root)
put(sinking, _root) // put down
}