This is part of a series of articles on implementing various functional data structures in Scala. See other articles in this series:
In this part we will implement a basic Priority Queue in Scala. It will support the following operations efficiently:
|
|
Basics
There are different ways to implement a Priority Queue. We will use Binomial Heap. As usual we follow the approach from Okasaki. See this paper for details about implementing efficient and purely functional priority queues. We start with the basic implementation which is simple to understand. If you know how Binomial heaps work, skip to the next section. Else read on.
A Binomial heap is a forest of Binomial trees. A binomial tree of rank 0 has just a single node and of rank k (k > 0) has binomial trees of rank (0, 1, .. k-1) as its children. So it looks like (image from wikipedia):
Another way to think about binomial tree is: to get binomial tree of rank k, take binomial tree of rank k-1 and insert another tree of rank k-1 as a child to the first tree's root.
In any case, a Binomial heap of n elements would contain only trees of the above form and it will correspond to (k1, k2, ..kj) where k1k2..kj is the binary representation of n. So a heap of 5 (101) elements would have trees of rank 0 and 2. This means merging two binomial heap is similar to adding two binary numbers etc.
Ok, but how can we use a Binomial heap as a Priority Queue? Let's walk through an example and add the following items: (3, 5, 1, 2, 13, 15, 11, 12) in this order and see what we get:
So you get the picture. Now to find the min element, we just need to walk through the roots of trees (log(n)
items) and return the min. Adding an element is similar to adding 1 to a binary number and deletion is similar. Merging (aka melding) is like adding two binary numbers. In all operations all we need to make sure is that the root is properly selected (since that should always have the minimum).
Implementation
Now that the idea is clear, let's start writing some code. We have a concrete class BinomialQueue
to represent the queue and a Node
class to represent individual trees. Let's look at the Node
class first:
|
|
For simplicity, every Node
stores its rank and have data and list of children. It is ordered based on the data (which needs to have an implicit Ordering
). Other than this we have a link
method which links two Node
together keeping the min element as new root. For example, in the above figure, when we insert 2
, we link [1,2]
and [3,5]
together and 1
gets to be the root.
Now we can define the BinomialQueue
class:
|
|
Operations
Insert
|
|
We have 3 case to handle. Inserting to:
- Empty heap : Create a new List having a rank-0 Node (Eg. inserting 3 above)
- Heap without a rank-0 node: Just add a rank-0 Node to our list (Eg. inserting 1, 13 etc above)
- Heap with a rank-0 Node: Now we need to merge and recursively insert merged Node (Eg. inserting 5, 2 etc)
Find Minimum
Just returns the Node
with min element.
|
|
Merge
|
|
Logic is similar to insert: If a particular ranked node does not exist on one list we can just copy it to output. Same ranked Nodes are merged via linking them together.
Delete Minimum
|
|
Finds the min Node and removes it. The children of a Binary tree conveniently forms a Binary heap in reversed order, so we just merge that to other Nodes.
Adapting to Scala Collections
We are done and have a working Priority Queue implementation (we could make some operations more efficient by using Skew binary heaps, but thats for a later time). However, the other common operations on collections like iterating, map, filter etc are missing. Scala has a rich collections library and it is not difficult to reuse most of it for a new collection object. See this excellent guide to understand the design of Scala collections and how to adapt your collection to use it.
In a nutshell we need to do two things: Provide a way to iterate over the items (Traversable
or Iterable
) and a way to build a new collection (Builder
). There are also higher level of abstractions that make things easier. So we change the PriorityQueue
definition as follows:
|
|
That's quite a mouthfull, so let's take it one by one. Iterable just defines an iterator
method which yields elements of the collection in some order and an isEmpty
method. We can implement it as follows:
|
|
IterableLike
is for ensuring the same-result-type principle. GenericOrderedTraversableTemplate is an easy way to define the builder using a companion object for ordered collections. It has following methods to be implemented:
|
|
Basically we just need to tell which is the companion object where the builder is specified. Finally we need to define the companion object which defines how to build a BinomialQueue
from other collections:
|
|
Now we can use all the power of Scala collections.
|
|
Isn't that cool?
You can find the complete code here. The Binomial heap images were generated using this code. That's all for now, see you later!