In the last article we saw one way of implementing Binary Search Trees and Red Black Trees. This data structure can be used to implement a Set (like we saw before), but there are many other use cases if we can augment the data stored on the nodes. One example is a TreeMap which is a key-value store - similar to HashMap but with log(n) time guarantee for operations. Other examples are Interval trees, dynamic order statistics etc.
How to Augment the Data
Recall that a Node
in our RBT looked like the following:
|
|
One way to augment the data is to subclass Node
and add relevant data to that class. But there is a simpler approach. Note that in the above declaration, the data stored has an abstract type T
whose only constraint is that it should be Orderable
. So we could package all the data we need in T
itself and provide appropriate ordering logic. For example, in case of a TreeMap, T
can be a pair of (K, V) so that ordering is only done on K. A generic AugmentedData
class can be written as follows:
|
|
So an AugmentedData
would have a value v
which is orderable and additional data
of some type U
.
Example - Drawing Trees
Let's look at an example where we augment data. Let's say we need to draw a given RBT (or BST). There are two logical steps to it:
- Identify position of each Node in the picture
- Draw the picture
We separate these two steps because drawing a picture can be done in different ways - as an image, in a terminal etc.
Step 1 - Finding positions
First we need to Augment the data stored in a Node
to include its position (x,y) as well. Then we need to find and populate the positions.
We could just use the depth of a node in the tree as its vertical position (y-coordinate). Finding a horizontal position (x-coordinate) is slightly tricky and there are many different ways to do it. We will use the simple approach of taking a node's position in the inorder traversal of the tree. By definition, node on the left comes first and will have a lower x coordinate value than its root which will be lesser than its right child etc. This is not the best approach but suffices for our discussion.
We are going to define a function which takes a RBT node and gives back an RBT node which is the root of tree with augmented data. So it will look like:
|
|
It has two additional parameters; to adjust the space between nodes: d
and start position: start
. To simplify things, we can use a helper function calcPos
which finds the position and propogates it back.
|
|
Step 2 - Drawing the Tree
Once we have the positions, drawing a tree is trivial. A natural way to represent the tree is using SVG format. Since it is xml the tree structure can be directly represented. Scala also support XML literals which makes things easier.
|
|
This produces svg images like the following:
See the complete code here.