Skip to content

Commit 7042880

Browse files
authored
Merge pull request #17 from datastructures-js/development
v3.1.0
2 parents 7d85bfa + b40b5b2 commit 7042880

File tree

8 files changed

+865
-55
lines changed

8 files changed

+865
-55
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [3.1.0] - 2020-03-31
10+
### Added
11+
- AvlTreeNode & AvlTree implementation.
12+
913
## [3.0.1] - 2020-03-29
1014
### Fixed
1115
- return the updated node in `.insert`

README.md

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,29 @@
44
[![npm](https://img.shields.io/npm/v/@datastructures-js/binary-search-tree.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree)
55
[![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)
66

7-
javascript implementation of Binary Search Tree.
7+
Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript.
88

9+
<table>
10+
<tr>
11+
<td align="center"><b>Binary Search Tree</b></td>
12+
</tr>
13+
<tr>
14+
<td>
915
<img width="413" alt="Binary Search Tree" src="https://user-images.githubusercontent.com/6517308/35762621-74a72626-085f-11e8-8934-ef6facdd6e10.png">
16+
</td>
17+
</tr>
18+
</table>
19+
20+
<table>
21+
<tr>
22+
<td align="center"><b>AVL Tree (Self Balancing Tree)</b></td>
23+
</tr>
24+
<tr>
25+
<td>
26+
<img width="1387" alt="AVL Tree" src="https://user-images.githubusercontent.com/6517308/37691467-e9c7db8a-2c77-11e8-844a-c6b6c0ff93dd.png">
27+
</td>
28+
</tr>
29+
</table>
1030

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

3959
### require
60+
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.
61+
4062
```js
41-
const { BinarySearchTree } = require('@datastructures-js/binary-search-tree');
63+
const { BinarySearchTree, AvlTree } = require('@datastructures-js/binary-search-tree');
4264
```
4365

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

5173
```js
5274
const bst = new BinarySearchTree();
75+
76+
// OR a self balancing tree
77+
78+
const bst = new AvlTree();
5379
```
5480

5581
### .insert(key, value)
5682

57-
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.
83+
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.
5884

5985
<table>
6086
<tr>
@@ -70,14 +96,20 @@ inserts a node with key/value into the tree. Inserting an node with existing key
7096
<b>value</b>: {object}
7197
</td>
7298
<td>
73-
{BinarySearchTreeNode} the inserted node
99+
{<b>BinarySearchTreeNode</b>} for BinarySearchTree
74100
<br><br>
75101
<b>.getKey()</b> {number|string} returns the node's key that is used to compare with other nodes.<br>
76102
<b>.setValue(value)</b> change the value that is associated with a node.<br>
77103
<b>.getValue()</b> {object} returns the value that is associated with a node.<br>
78104
<b>.getLeft()</b> {BinarySearchTreeNode} returns node's left child node.<br>
79105
<b>.getRight()</b> {BinarySearchTreeNode} returns node's right child node.<br>
80106
<b>.getParent()</b> {BinarySearchTreeNode} returns node's parent node.
107+
<br><br><br>
108+
{<b>AvlTreeNode</b>} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods:
109+
<br><br>
110+
<b>.getHeight()</b> {number} the height of the node in the tree. root is 1.<br>
111+
<b>.getLeftHeight()</b> {number} the height of the left child. 0 if no left child.<br>
112+
<b>.getRightHeight()</b> {number} the height of the right child. 0 if no right child.<br>
81113
</td>
82114
</tr>
83115
</table>
@@ -132,7 +164,9 @@ finds a node in the tree by its key.
132164
<b>key</b>: {number} or {string}
133165
</td>
134166
<td>
135-
{BinarySearchTreeNode}
167+
{BinarySearchTreeNode} for BinarySearchTree
168+
<br><br>
169+
{AvlTreeNode} for AvlTree
136170
</td>
137171
</tr>
138172
</table>
@@ -156,7 +190,9 @@ finds the node with min key in the tree.
156190
<tr>
157191
<td>O(log(n))</td>
158192
<td>
159-
{BinarySearchTreeNode}
193+
{BinarySearchTreeNode} for BinarySearchTree
194+
<br><br>
195+
{AvlTreeNode} for AvlTree
160196
</td>
161197
</tr>
162198
</table>
@@ -178,7 +214,9 @@ finds the node with max key in the tree.
178214
<tr>
179215
<td>O(log(n))</td>
180216
<td>
181-
{BinarySearchTreeNode}
217+
{BinarySearchTreeNode} for BinarySearchTree
218+
<br><br>
219+
{AvlTreeNode} for AvlTree
182220
</td>
183221
</tr>
184222
</table>
@@ -199,7 +237,9 @@ returns the root node of the tree.
199237
<tr>
200238
<td>O(1)</td>
201239
<td>
202-
{BinarySearchTreeNode}
240+
{BinarySearchTreeNode} for BinarySearchTree
241+
<br><br>
242+
{AvlTreeNode} for AvlTree
203243
</td>
204244
</tr>
205245
</table>
@@ -321,7 +361,7 @@ bst.traversePostOrder((node) => console.log(node.getKey()));
321361
```
322362

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

326366
<table>
327367
<tr>

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datastructures-js/binary-search-tree",
3-
"version": "3.0.1",
4-
"description": "binary search tree implementation in javascript",
3+
"version": "3.1.0",
4+
"description": "binary search tree & avl tree (self balancing tree) implementation in javascript",
55
"main": "index.js",
66
"scripts": {
77
"test": "grunt test"
@@ -15,7 +15,11 @@
1515
"bst js",
1616
"bst es6",
1717
"binary search tree es6",
18-
"binary search tree js"
18+
"binary search tree js",
19+
"avl tree",
20+
"avl tree es6",
21+
"avl tree js",
22+
"self balancing tree"
1923
],
2024
"author": "Eyas Ranjous <eyas.ranjous@gmail.com>",
2125
"license": "MIT",

src/avlTree.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* datastructures-js/binary-search-tree
3+
* @copyright 2020 Eyas Ranjous <eyas.ranjous@gmail.com>
4+
* @license MIT
5+
*/
6+
7+
const BinarySearchTree = require('./binarySearchTree');
8+
const AvlTreeNode = require('./avlTreeNode');
9+
10+
/**
11+
* @class AvlTree
12+
* @extends BinarySearchTree
13+
*/
14+
class AvlTree extends BinarySearchTree {
15+
/**
16+
* @private
17+
* applies the proper rotation on nodes after inserting a node
18+
* @param {AvlTreeNode} node
19+
*/
20+
balanceAfterInsert(key, node) {
21+
if (!node) return;
22+
23+
node.updateHeight();
24+
const balance = node.calculateBalance();
25+
if (balance > 1) {
26+
if (key < node.getLeft().getKey()) {
27+
node.rotateRight();
28+
} else {
29+
node.rotateLeftRight();
30+
}
31+
} else if (balance < -1) {
32+
if (key > node.getRight().getKey()) {
33+
node.rotateLeft();
34+
} else {
35+
node.rotateRightLeft();
36+
}
37+
}
38+
if (node === this.rootNode && (balance < -1 || balance > 1)) {
39+
this.rootNode = node.getParent();
40+
}
41+
}
42+
43+
/**
44+
* @private
45+
* applies the proper rotation on nodes after removing a node
46+
* @param {AvlTreeNode} node
47+
*/
48+
balanceAfterRemove(node) {
49+
if (!node) return;
50+
51+
node.updateHeight();
52+
const balance = node.calculateBalance();
53+
if (balance > 1) {
54+
if (node.getLeft().getLeft() !== null) {
55+
node.rotateRight();
56+
} else if (node.getLeft().getRight() !== null) {
57+
node.rotateLeftRight();
58+
}
59+
} else if (balance < -1) {
60+
if (node.getRight().getRight() !== null) {
61+
node.rotateLeft();
62+
} else if (node.getRight().getLeft() !== null) {
63+
node.rotateRightLeft();
64+
}
65+
}
66+
if (node === this.rootNode && (balance < -1 || balance > 1)) {
67+
this.rootNode = node.getParent();
68+
}
69+
}
70+
71+
/**
72+
* @public
73+
*
74+
* inserts a node with a key/value into tree
75+
* and maintains the tree balanced by applying the necessary rotations
76+
*
77+
* @param {number|string} key
78+
* @param {object} vaue
79+
* @return {AvlTreeNode} the inserted node
80+
*/
81+
insert(key, value, node = this.rootNode) {
82+
if (node === null) {
83+
this.rootNode = new AvlTreeNode(key, value);
84+
this.nodesCount += 1;
85+
return this.rootNode;
86+
}
87+
88+
if (key < node.getKey() && node.getLeft() === null) {
89+
const newNode = new AvlTreeNode(key, value);
90+
node.setLeft(newNode);
91+
newNode.setParent(node);
92+
node.updateHeight();
93+
this.nodesCount += 1;
94+
return newNode;
95+
}
96+
97+
if (key > node.getKey() && node.getRight() === null) {
98+
const newNode = new AvlTreeNode(key, value);
99+
node.setRight(newNode);
100+
newNode.setParent(node);
101+
node.updateHeight();
102+
this.nodesCount += 1;
103+
return newNode;
104+
}
105+
106+
if (key === node.getKey()) {
107+
node.setValue(value);
108+
return node;
109+
}
110+
111+
if (key < node.getKey()) {
112+
const newNode = this.insert(key, value, node.getLeft());
113+
this.balanceAfterInsert(key, node); // back-tracking
114+
return newNode;
115+
}
116+
117+
const newNode = this.insert(key, value, node.getRight());
118+
this.balanceAfterInsert(key, node); // back-tracking
119+
return newNode;
120+
}
121+
122+
remove(key, node = this.rootNode) {
123+
if (node === null) return false;
124+
125+
if (key < node.getKey()) {
126+
const removed = this.remove(key, node.getLeft());
127+
this.balanceAfterRemove(node);
128+
return removed;
129+
}
130+
131+
if (key > node.getKey()) {
132+
const removed = this.remove(key, node.getRight());
133+
this.balanceAfterRemove(node);
134+
return removed;
135+
}
136+
137+
if (node.getLeft() === null && node.getRight() === null) {
138+
if (node.getParent() === null) {
139+
this.rootNode = null;
140+
} else if (key < node.getParent().getKey()) {
141+
node.getParent().setLeft(null);
142+
node.getParent().updateHeight();
143+
} else {
144+
node.getParent().setRight(null);
145+
node.getParent().updateHeight();
146+
}
147+
this.nodesCount -= 1;
148+
return true;
149+
}
150+
151+
if (node.getRight() === null) {
152+
if (node.getParent() === null) {
153+
this.rootNode = node.getLeft();
154+
} else if (key < node.getParent().getKey()) {
155+
node.getParent().setLeft(node.getLeft());
156+
node.getParent().updateHeight();
157+
} else {
158+
node.getParent().setRight(node.getLeft());
159+
node.getParent().updateHeight();
160+
}
161+
node.getLeft().setParent(node.getParent());
162+
this.nodesCount -= 1;
163+
return true;
164+
}
165+
166+
if (node.getLeft() === null) {
167+
if (node.getParent() === null) {
168+
this.rootNode = node.getRight();
169+
} else if (key < node.getParent().getKey()) {
170+
node.getParent().setLeft(node.getRight());
171+
node.getParent().updateHeight();
172+
} else {
173+
node.getParent().setRight(node.getRight());
174+
node.getParent().updateHeight();
175+
}
176+
node.getRight().setParent(node.getParent());
177+
this.nodesCount -= 1;
178+
return true;
179+
}
180+
181+
const minRight = this.min(node.getRight());
182+
node.setKey(minRight.getKey());
183+
return this.remove(minRight.getKey(), minRight);
184+
}
185+
}
186+
187+
module.exports = AvlTree;

0 commit comments

Comments
 (0)