Well I'm just learning Scala and I'm trying to implement some algorithms and data structures.
I wrote some code that aims to transform a Vector in a linear binary heap. For instance:
Vector(8,3,4,6,2,5,7,9)
is transformed to Vector(9,8,7,6,2,4,5,3)
In this way, given index i
, its parent is at: (i-1)/2
or (i-2)/2
depending on i
being odd or pair.
I leave the code here, What I'm looking for is some advice on how could I improve my implementation. Or even try it out in another completely different direction.
You can use this like: new Heap(Vector(8,3,4,6,2,5,7,9))
class Heap(vs: Vector[Int]) {
val heap = build()
private def build():Vector[Int] = {
((1 until vs.length) foldLeft Vector[Int](vs.head)) ( (accu, idx) =>
fixFrom(accu :+ vs(idx), idx) )
}
private def fixFrom(heapToFix:Vector[Int], idx: Int): Vector[Int] = {
val parentIndex = parent(idx)
if(parentIndex == -1 || heapToFix(idx) <= heapToFix(parentIndex)) heapToFix
else {
val nextToFix = (heapToFix.updated(parentIndex, heapToFix(idx))) take idx
val fixed = fixFrom(nextToFix, parentIndex)
val swap = heapToFix.updated(idx, heapToFix(parentIndex))
fixed ++ (swap drop idx)
}
}
def children(parentIndex: Int) =
(valid(2*parentIndex + 1), valid(2*parentIndex + 2))
def parent(childIndex: Int) =
if(childIndex % 2 == 0) valid((childIndex-2)/2)
else valid((childIndex-1)/2)
def valid(idx:Int) =
if(idx >= 0 && idx < vs.length) idx else -1
override def toString = heap mkString " "
}
Update 1: Taking the advices below, I've done some changes:
import math.Ordering.Implicits._
class Heap[T: Ordering](vs: Vector[T]) {
val heap = build()
private def build():Vector[T] =
((0 until vs.length) foldLeft Vector.empty[T]) ( (accu, idx) =>
fixUp(accu :+ vs(idx), idx) )
@annotation.tailrec
private def fixUp(h:Vector[T], idx: Int): Vector[T] = {
val parentIdx = parent(idx)
if(parentIdx < 0 || h(idx) <= h(parentIdx)) h
else fixUp(h.updated(parentIdx, h(idx)).updated(idx, h(parentIdx)), parentIdx)
}
def parent(idx: Int) = (idx-1) >> 1
override def toString = heap mkString " "
}
Heaps are commonly implemented with an array. Any binary tree can be stored in an array, but because a binary heap is always a complete binary tree, it can be stored compactly. No space is required for pointers; instead, the parent and children of each node can be found by arithmetic on array indices.
You can store a heap in an array because it's easy to compute the array index of a node's children: the children of the node at index K live at indices 2K+1 and 2K+2 if indices start at 0 (or at indices 2K and 2K+1 if indices start at 1).
Balancing a heap is done by sift-up or sift-down operations (swapping elements which are out of order). As we can build a heap from an array without requiring extra memory (for the nodes, for example), heapsort can be used to sort an array in-place.
So why is Binary Heap Preferred for Priority Queue? Since Binary Heap is implemented using arrays, there is always better locality of reference and operations are more cache friendly. Although operations are of same time complexity, constants in Binary Search Tree are higher. We can build a Binary Heap in O(n) time.
import scala.math.Ordering.Implicits._
def insert[T : Ordering](heap: Vector[T], newItem: T) = {
@annotation.tailrec
def siftUp(h: Vector[T], idx: Int):Vector[T] = {
val parentIdx = (idx - 1) >> 1
if(parentIdx < 0 || h(parentIdx) > h(idx)) h
else siftUp(h.updated(parentIdx, h(idx)).updated(idx, h(parentIdx)), parentIdx)
}
siftUp(heap :+ newItem, heap.length)
}
def heapify[T: Ordering](vs: Vector[T]) = vs.foldLeft(Vector.empty[T])(insert)
assert(heapify(Vector(8, 3, 4, 6, 2, 5, 7, 9)) == Vector(9, 8, 7, 6, 2, 4, 5, 3))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With