BinarySearchTree
import { BinarySearchTree } from "jsr:@ende/data-structures";class
class BinarySearchTree<T> implements Iterable<T>An unbalanced binary search tree. The values are in ascending order by default, using JavaScript's built-in comparison operators to sort the values.
For performance, it's recommended that you use a self-balancing binary search tree instead of this one unless you are extending this to create a self-balancing tree. See RedBlackTree for an example of how BinarySearchTree can be extended to create a self-balancing binary search tree.
The following bounds are for a tree of n values, or an input collection of n
values for from(). Traversal bounds cover consuming the entire iterator;
comparison and mapping functions are assumed to take O(1).
| Method | Average Case | Worst Case |
|---|---|---|
| find(value) | O(log n) | O(n) |
| insert(value) | O(log n) | O(n) |
| remove(value) | O(log n) | O(n) |
| min() | O(log n) | O(n) |
| max() | O(log n) | O(n) |
| size | O(1) | O(1) |
| isEmpty() | O(1) | O(1) |
| clear() | O(1) | O(1) |
| lnrValues() | O(n) | O(n) |
| rnlValues() | O(n) | O(n) |
| nlrValues() | O(n) | O(n) |
| lrnValues() | O(n) | O(n) |
| lvlValues() | O(n) | O(n) |
| Symbol.iterator | O(n) | O(n) |
| BinarySearchTree() | O(1) | O(1) |
| BinarySearchTree.from() | O(n log n) | O(n²) |
BinarySearchTree.from takes O(n) when copying a
BinarySearchTree without a compare or map option, since it
clones the tree structure directly instead of reinserting the values.
Example: Usage
import {
BinarySearchTree,
ascend,
descend,
} from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const values = [3, 10, 13, 4, 6, 7, 1, 14];
const tree = new BinarySearchTree<number>();
values.forEach((value) => tree.insert(value));
assertEquals([...tree], [1, 3, 4, 6, 7, 10, 13, 14]);
assertEquals(tree.min(), 1);
assertEquals(tree.max(), 14);
assertEquals(tree.find(42), null);
assertEquals(tree.find(7), 7);
assertEquals(tree.remove(42), false);
assertEquals(tree.remove(7), true);
assertEquals([...tree], [1, 3, 4, 6, 10, 13, 14]);
const invertedTree = new BinarySearchTree<number>(descend);
values.forEach((value) => invertedTree.insert(value));
assertEquals([...invertedTree], [14, 13, 10, 7, 6, 4, 3, 1]);
assertEquals(invertedTree.min(), 14);
assertEquals(invertedTree.max(), 1);
assertEquals(invertedTree.find(42), null);
assertEquals(invertedTree.find(7), 7);
assertEquals(invertedTree.remove(42), false);
assertEquals(invertedTree.remove(7), true);
assertEquals([...invertedTree], [14, 13, 10, 6, 4, 3, 1]);
const words = new BinarySearchTree<string>((a, b) =>
ascend(a.length, b.length) || ascend(a, b)
);
["truck", "car", "helicopter", "tank", "train", "suv", "semi", "van"]
.forEach((value) => words.insert(value));
assertEquals([...words], [
"car",
"suv",
"van",
"semi",
"tank",
"train",
"truck",
"helicopter",
]);
assertEquals(words.min(), "car");
assertEquals(words.max(), "helicopter");
assertEquals(words.find("scooter"), null);
assertEquals(words.find("tank"), "tank");
assertEquals(words.remove("scooter"), false);
assertEquals(words.remove("tank"), true);
assertEquals([...words], [
"car",
"suv",
"van",
"semi",
"train",
"truck",
"helicopter",
]);
Constructors
constructor
new BinarySearchTree(compare: (a: T, b: T) => number = ascend)Construct an empty binary search tree.
To create a binary search tree from an array like, an iterable object, or an existing binary search tree, use the BinarySearchTree.from method.
compare: (a: T, b: T) => number = ascendA custom comparison function to sort the values in the tree. By default, the values are sorted in ascending order.
Static methods
from
static from<T>(collection: ArrayLike<T> | Iterable<T> | BinarySearchTree<T>, options?: { compare?: (a: T, b: T) => number }): BinarySearchTree<T>Creates a new binary search tree from an array like, an iterable object, or an existing binary search tree.
A custom comparison function can be provided to sort the values in a specific order. By default, the values are sorted in ascending order, unless a BinarySearchTree is passed, in which case the comparison function is copied from the input tree.
collection: ArrayLike<T> | Iterable<T> | BinarySearchTree<T>An array like, an iterable, or existing binary search tree.
options?: { compare?: (a: T, b: T) => number }An optional options object to customize the comparison function.
Returns
A new binary search tree created from the passed collection.
Example: Creating a binary search tree from an array like
import { BinarySearchTree } from "@ende/data-structures";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
Example: Creating a binary search tree from an iterable object
import { BinarySearchTree } from "@ende/data-structures";
const tree = BinarySearchTree.from<number>((function*() {
yield 42;
yield 43;
yield 41;
})());
Example: Creating a binary search tree from an existing binary search tree
import { BinarySearchTree } from "@ende/data-structures";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
const copy = BinarySearchTree.from(tree);
Example: Creating a binary search tree from an array like with a custom comparison function
import { BinarySearchTree, descend } from "@ende/data-structures";
const tree = BinarySearchTree.from<number>(
[42, 43, 41],
{ compare: descend }
);
static from<T, U, V = undefined>(collection: ArrayLike<T> | Iterable<T> | BinarySearchTree<T>, options: { compare?: (a: U, b: U) => number; map: (value: T, index: number) => U; thisArg?: V }): BinarySearchTree<U>Create a new binary search tree from an array like, an iterable object, or an existing binary search tree.
A custom mapping function can be provided to transform the values before inserting them into the tree.
A custom comparison function can be provided to sort the values in a specific order. A custom mapping function can be provided to transform the values before inserting them into the tree. By default, the values are sorted in ascending order, unless a BinarySearchTree is passed, in which case the comparison function is copied from the input tree. The comparison operator is used to sort the values in the tree after mapping the values.
collection: ArrayLike<T> | Iterable<T> | BinarySearchTree<T>An array like, an iterable, or existing binary search tree.
options: { compare?: (a: U, b: U) => number; map: (value: T, index: number) => U; thisArg?: V }The options object to customize the mapping and comparison functions. The
thisArgproperty can be used to set thethisvalue when calling the mapping function.
Returns
A new binary search tree containing the mapped values from the passed collection.
Example: Creating a binary search tree from an array like with a custom mapping function
import { BinarySearchTree } from "@ende/data-structures";
const tree = BinarySearchTree.from<number, string>(
[42, 43, 41],
{ map: (value) => value.toString() }
);
static from<T, U, V>(collection: ArrayLike<T> | Iterable<T> | BinarySearchTree<T>, options?: { compare?: (a: U, b: U) => number; map?: (value: T, index: number) => U; thisArg?: V }): BinarySearchTree<U>Methods
size
get size(): numberThe count of values stored in the binary search tree.
The complexity of this operation is O(1).
Returns
The count of values stored in the binary search tree.
Example: Getting the size of the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
assertEquals(tree.size, 3);
insert
insert(value: T): booleanAdd a value to the binary search tree if it does not already exist in the tree.
The complexity of this operation is on average O(log n), where n is the number of values in the tree. In the worst case, the complexity is O(n).
value: TThe value to insert into the binary search tree.
Returns
true if the value was inserted, false if the value already exists in the tree.
Example: Inserting values into the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = new BinarySearchTree<number>();
assertEquals(tree.insert(42), true);
assertEquals(tree.insert(42), false);
remove
remove(value: T): booleanRemove a value from the binary search tree if it exists in the tree.
The complexity of this operation is on average O(log n), where n is the number of values in the tree. In the worst case, the complexity is O(n).
value: TThe value to remove from the binary search tree.
Returns
true if the value was found and removed, false if the value was not found in the tree.
Example: Removing values from the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42]);
assertEquals(tree.remove(42), true);
assertEquals(tree.remove(42), false);
find
find(value: T): T | nullCheck if a value exists in the binary search tree.
The complexity of this operation depends on the underlying structure of the tree. Refer to the documentation of the structure itself for more details.
value: TThe value to search for in the binary search tree.
Returns
The value if it was found, or null if not found.
Example: Finding values in the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42]);
assertEquals(tree.find(42), 42);
assertEquals(tree.find(43), null);
min
min(): T | nullRetrieve the lowest (left most) value in the binary search tree, or null if the tree is empty.
The complexity of this operation depends on the underlying structure of the tree. Refer to the documentation of the structure itself for more details.
Returns
The minimum value in the binary search tree, or null if the tree is empty.
Example: Finding the minimum value in the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
assertEquals(tree.min(), 41);
max
max(): T | nullRetrieve the highest (right most) value in the binary search tree, or null if the tree is empty.
The complexity of this operation depends on the underlying structure of the tree. Refer to the documentation of the structure itself for more details.
Returns
The maximum value in the binary search tree, or null if the tree is empty.
Example: Finding the maximum value in the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
assertEquals(tree.max(), 43);
clear
clear(): voidRemove all values from the binary search tree.
The complexity of this operation is O(1).
Example: Clearing the tree
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from<number>([42, 43, 41]);
tree.clear();
assertEquals(tree.size, 0);
assertEquals(tree.find(42), null);
isEmpty
isEmpty(): booleanCheck if the binary search tree is empty.
The complexity of this operation is O(1).
Returns
true if the binary search tree is empty, false otherwise.
Example: Checking if the tree is empty
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = new BinarySearchTree<number>();
assertEquals(tree.isEmpty(), true);
tree.insert(42);
assertEquals(tree.isEmpty(), false);
lnrValues
lnrValues(): IterableIterator<T>Create an iterator over this tree that traverses the tree in-order (LNR, Left-Node-Right).
Returns
An iterator that traverses the tree in-order (LNR).
Example: Using the in-order LNR iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
assertEquals([...tree.lnrValues()], [1, 2, 3, 4, 5]);
rnlValues
rnlValues(): IterableIterator<T>Create an iterator over this tree that traverses the tree in reverse in-order (RNL, Right-Node-Left).
Returns
An iterator that traverses the tree in reverse in-order (RNL).
Example: Using the reverse in-order RNL iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
[...tree.rnlValues()] // 5, 4, 3, 2, 1
nlrValues
nlrValues(): IterableIterator<T>Create an iterator over this tree that traverses the tree in pre-order (NLR, Node-Left-Right).
Returns
An iterator that traverses the tree in pre-order (NLR).
Example: Using the pre-order NLR iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
assertEquals([...tree.nlrValues()], [4, 1, 2, 3, 5]);
lrnValues
lrnValues(): IterableIterator<T>Create an iterator over this tree that traverses the tree in post-order (LRN, Left-Right-Node).
Returns
An iterator that traverses the tree in post-order (LRN).
Example: Using the post-order LRN iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
assertEquals([...tree.lrnValues()], [3, 2, 1, 5, 4]);
lvlValues
lvlValues(): IterableIterator<T>Create an iterator over this tree that traverses the tree in level-order (BFS, Breadth-First Search).
Returns
An iterator that traverses the tree in level-order (BFS).
Example: Using the level-order BFS iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
assertEquals([...tree.lvlValues()], [4, 1, 5, 2, 3]);
[Symbol.iterator]
[Symbol.iterator](): IterableIterator<T>Create an iterator over this tree that traverses the tree in-order (LNR, Left-Node-Right).
Returns
An iterator that traverses the tree in-order (LNR).
Example: Using the in-order iterator
import { BinarySearchTree } from "@ende/data-structures";
import { assertEquals } from "@ende/assert";
const tree = BinarySearchTree.from([4, 1, 2, 5, 3]);
assertEquals([...tree], [1, 2, 3, 4, 5]);