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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.1.0] - 2020-03-31
### Added
- AvlTreeNode & AvlTree implementation.

## [3.0.1] - 2020-03-29
### Fixed
- return the updated node in `.insert`
Expand Down
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,29 @@
[![npm](https://img.shields.io/npm/v/@datastructures-js/binary-search-tree.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree)
[![npm](https://img.shields.io/npm/dm/@datastructures-js/binary-search-tree.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree) [![npm](https://img.shields.io/badge/node-%3E=%206.0-blue.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree)

javascript implementation of Binary Search Tree.
Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript.

<table>
<tr>
<td align="center"><b>Binary Search Tree</b></td>
</tr>
<tr>
<td>
<img width="413" alt="Binary Search Tree" src="https://user-images.githubusercontent.com/6517308/35762621-74a72626-085f-11e8-8934-ef6facdd6e10.png">
</td>
</tr>
</table>

<table>
<tr>
<td align="center"><b>AVL Tree (Self Balancing Tree)</b></td>
</tr>
<tr>
<td>
<img width="1387" alt="AVL Tree" src="https://user-images.githubusercontent.com/6517308/37691467-e9c7db8a-2c77-11e8-844a-c6b6c0ff93dd.png">
</td>
</tr>
</table>

# Table of Contents
* [Install](#install)
Expand Down Expand Up @@ -37,8 +57,10 @@ npm install --save @datastructures-js/binary-search-tree
## API

### require
Both trees have the same interface except that AVL tree will maintain itself balance due to rotating nodes that becomes unbalanced on insertion and deletion. If your code requires a strictly balanced tree that always benefits from the **log(n)** runtime of insert & remove, you should use AVL.

```js
const { BinarySearchTree } = require('@datastructures-js/binary-search-tree');
const { BinarySearchTree, AvlTree } = require('@datastructures-js/binary-search-tree');
```

### import
Expand All @@ -50,11 +72,15 @@ import { BinarySearchTree } from '@datastructures-js/binary-search-tree';

```js
const bst = new BinarySearchTree();

// OR a self balancing tree

const bst = new AvlTree();
```

### .insert(key, value)

inserts a node with key/value into the tree. Inserting an node with existing key, would update the existing node's value with the new inserted one.
inserts a node with key/value into the tree. Inserting an node with existing key, would update the existing node's value with the new inserted one. AVL tree will rotate nodes properly if the tree becomes unbalanced with the insertion.

<table>
<tr>
Expand All @@ -70,14 +96,20 @@ inserts a node with key/value into the tree. Inserting an node with existing key
<b>value</b>: {object}
</td>
<td>
{BinarySearchTreeNode} the inserted node
{<b>BinarySearchTreeNode</b>} for BinarySearchTree
<br><br>
<b>.getKey()</b> {number|string} returns the node's key that is used to compare with other nodes.<br>
<b>.setValue(value)</b> change the value that is associated with a node.<br>
<b>.getValue()</b> {object} returns the value that is associated with a node.<br>
<b>.getLeft()</b> {BinarySearchTreeNode} returns node's left child node.<br>
<b>.getRight()</b> {BinarySearchTreeNode} returns node's right child node.<br>
<b>.getParent()</b> {BinarySearchTreeNode} returns node's parent node.
<br><br><br>
{<b>AvlTreeNode</b>} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods:
<br><br>
<b>.getHeight()</b> {number} the height of the node in the tree. root is 1.<br>
<b>.getLeftHeight()</b> {number} the height of the left child. 0 if no left child.<br>
<b>.getRightHeight()</b> {number} the height of the right child. 0 if no right child.<br>
</td>
</tr>
</table>
Expand Down Expand Up @@ -132,7 +164,9 @@ finds a node in the tree by its key.
<b>key</b>: {number} or {string}
</td>
<td>
{BinarySearchTreeNode}
{BinarySearchTreeNode} for BinarySearchTree
<br><br>
{AvlTreeNode} for AvlTree
</td>
</tr>
</table>
Expand All @@ -156,7 +190,9 @@ finds the node with min key in the tree.
<tr>
<td>O(log(n))</td>
<td>
{BinarySearchTreeNode}
{BinarySearchTreeNode} for BinarySearchTree
<br><br>
{AvlTreeNode} for AvlTree
</td>
</tr>
</table>
Expand All @@ -178,7 +214,9 @@ finds the node with max key in the tree.
<tr>
<td>O(log(n))</td>
<td>
{BinarySearchTreeNode}
{BinarySearchTreeNode} for BinarySearchTree
<br><br>
{AvlTreeNode} for AvlTree
</td>
</tr>
</table>
Expand All @@ -199,7 +237,9 @@ returns the root node of the tree.
<tr>
<td>O(1)</td>
<td>
{BinarySearchTreeNode}
{BinarySearchTreeNode} for BinarySearchTree
<br><br>
{AvlTreeNode} for AvlTree
</td>
</tr>
</table>
Expand Down Expand Up @@ -321,7 +361,7 @@ bst.traversePostOrder((node) => console.log(node.getKey()));
```

### .remove(key)
removes a node from the tree by its key.
removes a node from the tree by its key. AVL tree will rotate nodes properly if the tree becomes unbalanced with the deletion.

<table>
<tr>
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@datastructures-js/binary-search-tree",
"version": "3.0.1",
"description": "binary search tree implementation in javascript",
"version": "3.1.0",
"description": "binary search tree & avl tree (self balancing tree) implementation in javascript",
"main": "index.js",
"scripts": {
"test": "grunt test"
Expand All @@ -15,7 +15,11 @@
"bst js",
"bst es6",
"binary search tree es6",
"binary search tree js"
"binary search tree js",
"avl tree",
"avl tree es6",
"avl tree js",
"self balancing tree"
],
"author": "Eyas Ranjous <eyas.ranjous@gmail.com>",
"license": "MIT",
Expand Down
187 changes: 187 additions & 0 deletions src/avlTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* datastructures-js/binary-search-tree
* @copyright 2020 Eyas Ranjous <eyas.ranjous@gmail.com>
* @license MIT
*/

const BinarySearchTree = require('./binarySearchTree');
const AvlTreeNode = require('./avlTreeNode');

/**
* @class AvlTree
* @extends BinarySearchTree
*/
class AvlTree extends BinarySearchTree {
/**
* @private
* applies the proper rotation on nodes after inserting a node
* @param {AvlTreeNode} node
*/
balanceAfterInsert(key, node) {
if (!node) return;

node.updateHeight();
const balance = node.calculateBalance();
if (balance > 1) {
if (key < node.getLeft().getKey()) {
node.rotateRight();
} else {
node.rotateLeftRight();
}
} else if (balance < -1) {
if (key > node.getRight().getKey()) {
node.rotateLeft();
} else {
node.rotateRightLeft();
}
}
if (node === this.rootNode && (balance < -1 || balance > 1)) {
this.rootNode = node.getParent();
}
}

/**
* @private
* applies the proper rotation on nodes after removing a node
* @param {AvlTreeNode} node
*/
balanceAfterRemove(node) {
if (!node) return;

node.updateHeight();
const balance = node.calculateBalance();
if (balance > 1) {
if (node.getLeft().getLeft() !== null) {
node.rotateRight();
} else if (node.getLeft().getRight() !== null) {
node.rotateLeftRight();
}
} else if (balance < -1) {
if (node.getRight().getRight() !== null) {
node.rotateLeft();
} else if (node.getRight().getLeft() !== null) {
node.rotateRightLeft();
}
}
if (node === this.rootNode && (balance < -1 || balance > 1)) {
this.rootNode = node.getParent();
}
}

/**
* @public
*
* inserts a node with a key/value into tree
* and maintains the tree balanced by applying the necessary rotations
*
* @param {number|string} key
* @param {object} vaue
* @return {AvlTreeNode} the inserted node
*/
insert(key, value, node = this.rootNode) {
if (node === null) {
this.rootNode = new AvlTreeNode(key, value);
this.nodesCount += 1;
return this.rootNode;
}

if (key < node.getKey() && node.getLeft() === null) {
const newNode = new AvlTreeNode(key, value);
node.setLeft(newNode);
newNode.setParent(node);
node.updateHeight();
this.nodesCount += 1;
return newNode;
}

if (key > node.getKey() && node.getRight() === null) {
const newNode = new AvlTreeNode(key, value);
node.setRight(newNode);
newNode.setParent(node);
node.updateHeight();
this.nodesCount += 1;
return newNode;
}

if (key === node.getKey()) {
node.setValue(value);
return node;
}

if (key < node.getKey()) {
const newNode = this.insert(key, value, node.getLeft());
this.balanceAfterInsert(key, node); // back-tracking
return newNode;
}

const newNode = this.insert(key, value, node.getRight());
this.balanceAfterInsert(key, node); // back-tracking
return newNode;
}

remove(key, node = this.rootNode) {
if (node === null) return false;

if (key < node.getKey()) {
const removed = this.remove(key, node.getLeft());
this.balanceAfterRemove(node);
return removed;
}

if (key > node.getKey()) {
const removed = this.remove(key, node.getRight());
this.balanceAfterRemove(node);
return removed;
}

if (node.getLeft() === null && node.getRight() === null) {
if (node.getParent() === null) {
this.rootNode = null;
} else if (key < node.getParent().getKey()) {
node.getParent().setLeft(null);
node.getParent().updateHeight();
} else {
node.getParent().setRight(null);
node.getParent().updateHeight();
}
this.nodesCount -= 1;
return true;
}

if (node.getRight() === null) {
if (node.getParent() === null) {
this.rootNode = node.getLeft();
} else if (key < node.getParent().getKey()) {
node.getParent().setLeft(node.getLeft());
node.getParent().updateHeight();
} else {
node.getParent().setRight(node.getLeft());
node.getParent().updateHeight();
}
node.getLeft().setParent(node.getParent());
this.nodesCount -= 1;
return true;
}

if (node.getLeft() === null) {
if (node.getParent() === null) {
this.rootNode = node.getRight();
} else if (key < node.getParent().getKey()) {
node.getParent().setLeft(node.getRight());
node.getParent().updateHeight();
} else {
node.getParent().setRight(node.getRight());
node.getParent().updateHeight();
}
node.getRight().setParent(node.getParent());
this.nodesCount -= 1;
return true;
}

const minRight = this.min(node.getRight());
node.setKey(minRight.getKey());
return this.remove(minRight.getKey(), minRight);
}
}

module.exports = AvlTree;
Loading