Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [5.3.0] - 2023-01-30
### Added
- `removeNode` to remove a node by its reference.
- `upperBoundKey`, `floorKey`, `lowerBoundKey`, `ceilKey` to support finding nodes by the object comparison key.

## [5.2.0] - 2022-12-12

### Added
Expand Down
56 changes: 51 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript
* [min](#min)
* [max](#max)
* [lowerBound (floor)](#lowerbound-floor)
* [lowerBoundKey (floorKey)](#lowerboundkey-floorkey)
* [upperBound (ceil)](#upperbound-ceil)
* [upperBoundKey (ceilKey)](#upperboundkey-ceilkey)
* [root](#root)
* [count](#count)
* [traverseInOrder](#traverseinorder)
* [traversePreOrder](#traversepreorder)
* [traversePostOrder](#traversepostorder)
* [remove](#remove)
* [removeNode](#removeNode)
* [clear](#clear)
* [BinarySearchTreeNode](#binarysearchtreenodet)
* [AvlTreeNode](#avltreenodet)
Expand Down Expand Up @@ -78,13 +81,19 @@ constructor also accepts an options param, where the comparison key prob name ca
###### BinarySearchTree
```js
const nums = new BinarySearchTree();
const employees = new BinarySearchTree((a, b) => a.id - b.id);
const employees = new BinarySearchTree(
(a, b) => a.id - b.id,
{ key: 'id }
);
```

###### AvlTree
```js
const nums = new AvlTree();
const employees = new AvlTree((a, b) => a.id - b.id, { key: 'id' });
const employees = new AvlTree(
(a, b) => a.id - b.id,
{ key: 'id' }
);
```

##### TS
Expand Down Expand Up @@ -147,7 +156,7 @@ employees.has({ id: 100 }); // false
### hasKey
O(log(n))

checks if a value exists by its key if the node's key prob is provided in the constructor.
checks if an object exists by its key if the comparison key prob is provided in the constructor.

```js
employees.hasKey(50); // true
Expand All @@ -170,7 +179,7 @@ employees.find({ id: 100 }); // null
### findKey
O(log(n))

finds a node by its key if the node's key prob is provided in the constructor.
finds a node by its object key if the comparison key prob is provided in the constructor.

```js
employees.findKey(60).getValue(); // { id: 60 }
Expand Down Expand Up @@ -214,6 +223,17 @@ employees.floor({ id: 60 }, false).getValue(); // { id: 50 }
employees.floor({ id: 10 }); // null
```

### lowerBoundKey (floorKey)
O(log(n))

finds the node with the biggest key less or equal a given key if the comparison key prob is provided in the constructor. You can eliminate equal values by passing second param as false. `.floorKey` is an alias to the same function.

```js
employees.floorKey(60).getValue(); // { id: 60 }
employees.floorKey(60, false).getValue(); // { id: 50 }
employees.floorKey(10); // null
```

### upperBound (ceil)
O(log(n))

Expand All @@ -231,6 +251,19 @@ employees.ceil({ id: 80 }, false).getValue(); // { id: 90 }
employees.ceil({ id: 110 }); // null
```


### upperBoundKey (ceilKey)
O(log(n))

finds the node with the smallest key bigger or equal a given key if the comparison key prob is provided in the constructor. You can eliminate equal values by passing second param as false. `.ceilKey` is an alias to the same function.

```js
employees.ceilKey(75).getValue(); // { id: 80 }
employees.ceilKey(80).getValue(); // { id: 80 }
employees.ceilKey(80, false).getValue(); // { id: 90 }
employees.ceilKey(110); // null
```

### root
O(1)

Expand Down Expand Up @@ -376,7 +409,7 @@ employees.traversePostOrder((node) => {
### remove
O(log(n))

removes a node from the tree by its value. AVL tree will rotate nodes properly if the tree becomes unbalanced during deletion.
removes a node from the tree by its value. The function will first find the node that corresponds to the value and then remove it. AVL tree will rotate nodes properly if the tree becomes unbalanced.

```js
nums.remove(20); // true
Expand All @@ -388,6 +421,19 @@ employees.remove({ id: 100 }); // false
employees.count(); // 6
```

### removeNode
O(log(n))

removes a node from the tree by its reference.

```js
const node1 = nums.find(50);
nums.removeNode(node1); // true

const node2 = employees.findKey(50);
employees.removeNode(node2); // true
```

### clear
O(1)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@datastructures-js/binary-search-tree",
"version": "5.2.0",
"version": "5.3.0",
"description": "binary search tree & avl tree (self balancing tree) implementation in javascript",
"main": "index.js",
"types": "index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/avlTree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export class AvlTree<T> extends BinarySearchTree<T> {
traverseInOrder(cb: (node: AvlTreeNode<T>) => void): void;
traversePreOrder(cb: (node: AvlTreeNode<T>) => void): void;
traversePostOrder(cb: (node: AvlTreeNode<T>) => void): void;
removeNode(node: AvlTreeNode): boolean;
}
101 changes: 57 additions & 44 deletions src/avlTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,57 +124,70 @@ class AvlTree extends BinarySearchTree {
}

// current node is the node to remove
return this.removeNode(current);
};

// case 1: node has no children
if (current.isLeaf()) {
if (current.isRoot()) {
this._root = null;
} else if (this._compare(val, current.getParent().getValue()) < 0) {
current.getParent().setLeft(null).updateHeight();
} else {
current.getParent().setRight(null).updateHeight();
}
this._count -= 1;
return true;
}
return removeRecursively(value, this._root);
}

// case 2: node has a left child and no right child
if (!current.hasRight()) {
if (current.isRoot()) {
this._root = current.getLeft();
} else if (this._compare(val, current.getParent().getValue()) < 0) {
current.getParent().setLeft(current.getLeft()).updateHeight();
} else {
current.getParent().setRight(current.getLeft()).updateHeight();
}
current.getLeft().setParent(current.getParent());
this._count -= 1;
return true;
/**
* Removes a node from the tree
* @public
* @param {AvlTreeNode} node
* @return {boolean}
*/
removeNode(node) {
if (node === null || !(node instanceof AvlTreeNode)) {
return false;
}

// case 1: node has no children
if (node.isLeaf()) {
if (node.isRoot()) {
this._root = null;
} else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) {
node.getParent().setLeft(null).updateHeight();
} else {
node.getParent().setRight(null).updateHeight();
}
this._count -= 1;
return true;
}

// case 3: node has a right child and no left child
if (!current.hasLeft()) {
if (current.isRoot()) {
this._root = current.getRight();
} else if (this._compare(val, current.getParent().getValue()) < 0) {
current.getParent().setLeft(current.getRight()).updateHeight();
} else {
current.getParent().setRight(current.getRight()).updateHeight();
}
current.getRight().setParent(current.getParent());
this._count -= 1;
return true;
// case 2: node has a left child and no right child
if (!node.hasRight()) {
if (node.isRoot()) {
this._root = node.getLeft();
} else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) {
node.getParent().setLeft(node.getLeft()).updateHeight();
} else {
node.getParent().setRight(node.getLeft()).updateHeight();
}
node.getLeft().setParent(node.getParent());
this._count -= 1;
return true;
}

// case 4: node has left and right children
const minRight = this.min(current.getRight());
const removed = removeRecursively(minRight.getValue(), minRight);
current.setValue(minRight.getValue());
this._balanceNode(current);
return removed;
};
// case 3: node has a right child and no left child
if (!node.hasLeft()) {
if (node.isRoot()) {
this._root = node.getRight();
} else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) {
node.getParent().setLeft(node.getRight()).updateHeight();
} else {
node.getParent().setRight(node.getRight()).updateHeight();
}
node.getRight().setParent(node.getParent());
this._count -= 1;
return true;
}

return removeRecursively(value, this._root);
// case 4: node has left and right children
const minRight = this.min(node.getRight());
const removed = this.removeNode(minRight);
node.setValue(minRight.getValue());
this._balanceNode(node);
return removed;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/binarySearchTree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ export class BinarySearchTree<T> {
max(node?: BinarySearchTreeNode<T>): BinarySearchTreeNode<T> | null;
min(node?: BinarySearchTreeNode<T>): BinarySearchTreeNode<T> | null;
lowerBound(value: T, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
lowerBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
floor(value: T, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
floorKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
upperBound(value: T, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
upperBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
ceil(value: T, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
ceilKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode<T> | null;
root(): BinarySearchTreeNode<T> | null;
count(): number;
remove(value: T): boolean;
removeNode(node: BinarySearchTreeNode): boolean;
traverseInOrder(cb: (node: BinarySearchTreeNode<T>) => void, abortCb?: () => boolean): void;
traversePreOrder(cb: (node: BinarySearchTreeNode<T>) => void, abortCb?: () => boolean): void;
traversePostOrder(cb: (node: BinarySearchTreeNode<T>) => void, abortCb?: () => boolean): void;
Expand Down
Loading