This is an AVL, self-balancing binary search tree data structure in JavaScript!
It supports:
toString
function which prints ASCII
representation of tree for debugging, including with an upper bound to
avoid printing too many levels for large treesPreviously, I published several experiments with AVL tree style of data structure in JS and WASM. This project remedies several issues with those data structures and also improves things in various ways, so this is the one you should use!
If you do find any issues despite the testing I’ve done, do not hesitate to open an issue on GitHub.
See the tests in the repo for more examples of how the functions described in this document work.
You can create a new, empty instance of the AVL
class
like this:
const myAVLTree = new AVL()
It is also possible to “bulk load” the data structure via the constructor if a sorted array is provided as an argument:
const myAVLTree = new AVL([
2,4,8,16,32,64
])
For quick debugging, you can build an ASCII representation of your
tree by calling the toString()
method on an AVL
instance:
console.log(myAVLTree.toString())
You can provide an upper bound for the number of tree levels to print; see the section below on Modes of Iteration.
Retrieve the number of nodes or keys stored in the tree by accessing
the size
property of the tree instance:
.size myAVLTree
Retrieve the root node of the tree by accessing the root
property of the tree instance:
.root myAVLTree
Retrieve the tree’s minimum or maximum values via the
min
and max
properties respectively. Those
properties are simply wrappers around the instance methods
getMinOfSubtree
and getMaxOfSubtree
discussed
below.
.min
myAVLTree.max myAVLTree
Nodes are arrays, where the first five indices are:
search
A test of key membership which returns the matching node if found, otherwise null.
Parameters:
insert
Insert a key and optionally a value into the tree. If non-null value argument provided, it is inserted at index 5 in the node array. Returns a reference to the inserted node.
Parameters:
getMinOfSubtree
Return the node from the tree with the smallest key value.
Parameters:
getMaxOfSubtree
Return the node from the tree with the largest key value.
Parameters:
remove
Remove a node from the tree which has the provided key value.
Parameters:
minGreaterEqual
Provided a search term (number), search for and return the node with the smallest key value greater than or equal to that search term, otherwise null.
maxLesserEqual
Provided a search term (number), search for and return the node with the largest key value less than or equal to that search term, otherwise null.
minGreater
Provided a search term (number), search for and return the node with the smallest key value greater than that search term, otherwise null.
maxLesser
Provided a search term (number), search for and return the node with the largest key value less than that search term, otherwise null.
OSSelect
An order statistic search function. Given a node X
to
start searching at (default is root node) and a positive integer
I
(default is 1), find and return the node with the
Ith smallest key value.
OSRank
An order statistic query function which returns the index or rank of a provided key in an inorder traversal of the tree.
The AVL class comes with a variety of static methods suitable for tree traversal. The general workflow to configure an AVL instance’s iteration behavior before iterating:
[Symbol.iterator]
property the
static method corresponding to the iteration or tree traversal
desired.ITER_LB
.ITER_UB
.After that, you’re ready to use the tree instance anywhere you’d use
an iterable in JS (spread operator, for...of
loops,
etc).
One warning. The toString
method is
just a wrapper around the level order traversal iterator (discussed more
below). So, after using the toString
function, the
subsequent iteration behavior could surprise you. It’s best practice to
explicitly set the iteration properties before every iteration unless
you’re very sure you’ve kept track of how your AVL tree’s
iteration properties are configured.
AVL.ITER_FWD_GE_TO_LE
A generator function yielding nodes from the tree according to a bounded, inorder traversal of the nodes according to their key values. The lower bound and upper bound are both inclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_FWD_GE_TO_LE
T[.ITER_LB = 32
T.ITER_UB = 63
Tconsole.log(
...T].map(n=>n[1])
[
) // 32, 42
AVL.ITER_FWD_GE_TO_LT
A generator function yielding nodes from the tree according to a bounded, inorder traversal of the nodes according to their key values. The lower bound is inclusive, but the upper bound is exclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_FWD_GE_TO_LT
T[.ITER_LB = 32
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 32, 42
AVL.ITER_FWD_GT_TO_LE
A generator function yielding nodes from the tree according to a bounded, inorder traversal of the nodes according to their key values. The lower bound is exclusive, but the upper bound is inclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_FWD_GT_TO_LE
T[.ITER_LB = 32
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 42, 64
AVL.ITER_FWD_GT_TO_LT
A generator function yielding nodes from the tree according to a bounded, inorder traversal of the nodes according to their key values. The lower bound is exclusive, but the upper bound is exclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_FWD_GT_TO_LT
T[.ITER_LB = 16
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 32 42
AVL.ITER_REV_LE_TO_GE
A generator function yielding nodes from the tree according to a bounded, reverse inorder traversal of the nodes according to their key values. The lower bound and upper bound are both inclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_REV_LE_TO_GE
T[.ITER_LB = 32
T.ITER_UB = 63
Tconsole.log(
...T].map(n=>n[1])
[
) // 42, 32
AVL.ITER_REV_LE_TO_GT
A generator function yielding nodes from the tree according to a bounded, reverse inorder traversal of the nodes according to their key values. The lower bound is exclusive, but the upper bound is inclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_REV_LE_TO_GT
T[.ITER_LB = 32
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 64, 42
AVL.ITER_REV_LT_TO_GE
A generator function yielding nodes from the tree according to a bounded, reverse inorder traversal of the nodes according to their key values. The lower bound is inclusive, but the upper bound is exclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_REV_LT_TO_GE
T[.ITER_LB = 32
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 42, 32
AVL.ITER_REV_LT_TO_GT
A generator function yielding nodes from the tree according to a bounded, reverse inorder traversal of the nodes according to their key values. The lower bound and upper bound are both exclusive.
const T = new AVL([
2, 4, 8, 16, 32, 42, 64
;
])Symbol.iterator] = AVL.ITER_REV_LT_TO_GT
T[.ITER_LB = 16
T.ITER_UB = 64
Tconsole.log(
...T].map(n=>n[1])
[
) // 42, 32
AVL.ITER_LEVEL_ORDER
A generator function that yields arrays of nodes, where each array
corresponds to one level in the tree, starting from the root. Obeys the
value of ITER_UB
property and will stop yielding levels
once it reaches that limit. This function is used by the
toString
function, so reset the value of
[Symbol.iterator]
after using that function if needed.
AVL.ITER_PREORDER
A generator function yielding nodes from the tree according to a preorder traversal of the nodes according to their key values. The lower bound and upper bound are both ignored.
AVL.ITER_POSTORDER
A generator function yielding nodes from the tree according to a postorder traversal of the nodes according to their key values. The lower bound and upper bound are both ignored.
AVL.ITER_REV_PREORDER
A generator function yielding nodes from the tree according to a reverse preorder traversal of the nodes according to their key values. The lower bound and upper bound are both ignored.
AVL.ITER_REV_POSTORDER
A generator function yielding nodes from the tree according to a reverse postorder traversal of the nodes according to their key values. The lower bound and upper bound are both ignored.
All three of these are static methods on the AVL class.
If you are storing values in your nodes, all three of these methods will not preserve those values in the new trees produced by these methods, so you’ll need a different mechanism for mapping values to new tree instances.
union
Given two AVL tree instances, a
and b
,
generate a new AVL tree instance containing the all keys from either
a
or b
, deduplicated.
intersect
Given two AVL tree instances, a
and b
,
generate a new AVL tree instance containing each key which is in both
a
and b
, deduplicated.
difference
Given two AVL tree instances, a
and b
,
generate a new AVL tree instance containing the all keys which are in
a
but not in b
.