From 3247a3d70baa307a9805e015f50453f8804a6697 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 13:16:59 -0600 Subject: [PATCH 01/19] add AvlTreeNode --- src/avlTreeNode.js | 158 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/avlTreeNode.js diff --git a/src/avlTreeNode.js b/src/avlTreeNode.js new file mode 100644 index 0000000..2b21db2 --- /dev/null +++ b/src/avlTreeNode.js @@ -0,0 +1,158 @@ +/** + * datastructures-js/binary-search-tree + * @copyright 2020 Eyas Ranjous + * @license MIT + */ + +const BinarySearchTreeNode = require('./binarySearchTreeNode'); + +/** + * @class AvlTreeNode + * @extends BinarySearchTreeNode + */ + +class AvlTreeNode extends BinarySearchTreeNode { + constructor(key, value) { + super(key, value); + this.height = 1; + } + + /** + * @public + * rotates left (counter-clockwise) & updates parent and children positions + */ + rotateLeft() { + // set the node as a left child of its right child + if (this.right !== null) { + if (this.right.getLeft() !== null) { + this.right.getLeft().setParent(this); + } + this.right.setLeft(this); + this.right.setParent(this.parent); + } + + // rebase parent's child to node's right child + if (this.parent !== null && this.right !== null) { + if (this.parent.getKey() < this.right.getKey()) { + this.parent.setRight(this.right); + } else { + this.parent.setLeft(this.right); + } + } + + // rebase parent to node's right child + this.parent = this.right; + + // rebase right child to node's right left child. + if (this.right !== null) { + this.right = this.right.getLeft(); + } + + if (this.parent !== null) { + this.parent.updateHeight(); + } + this.updateHeight(); + } + + /** + * @public + * rotates right (clockwise) & updates parent and children positions + */ + rotateRight() { + // set the node as a right child of its left child + if (this.left !== null) { + if (this.left.getRight() !== null) { + this.left.getRight().setParent(this); + } + this.left.setRight(this); + this.left.setParent(this.parent); + } + + // rebase parent to node's left child + if (this.parent !== null && this.left !== null) { + if (this.parent.getKey() > this.left.getKey()) { + this.parent.setLeft(this.left); + } else { + this.parent.setRight(this.left); + } + } + + // rebase parent to node's right child + this.parent = this.left; + + // rebase right child to node's right left child. + if (this.left !== null) { + this.left = this.left.getRight(); + } + + if (this.parent !== null) { + this.parent.updateHeight(); + } + this.updateHeight(); + } + + /** + * @public + * rotates left child to left then itself to right + */ + rotateLeftRight() { + if (this.left !== null) { + this.left.rotateLeft(); + } + this.rotateRight(); + } + + /** + * @public + * rotates right child to right then itself to left + */ + rotateRightLeft() { + if (this.right !== null) { + this.right.rotateRight(); + } + this.rotateLeft(); + } + + /** + * @public + * @return {number} + */ + getLeftHeight() { + return this.left !== null ? this.left.getHeight() : 0; + } + + /** + * @public + * @return {number} + */ + getRightHeight() { + return this.right !== null ? this.right.getHeight() : 0; + } + + /** + * @public + * updates the height of a node as the max height of its children + */ + updateHeight() { + this.height = Math.max(this.getLeftHeight(), this.getRightHeight()) + 1; + } + + /** + * @public + * @return {number} + */ + getHeight() { + return this.height; + } + + /** + * @public + * calculate the balance of a node as the diff between left & right heights + * @return {number} + */ + calculateBalance() { + return this.getLeftHeight() - this.getRightHeight(); + } +} + +module.exports = AvlTreeNode; From cba0aa76798aebc927d04e8e53dc4305dce3fb6e Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 13:17:20 -0600 Subject: [PATCH 02/19] remove unused props & fns --- src/binarySearchTreeNode.js | 40 ------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/binarySearchTreeNode.js b/src/binarySearchTreeNode.js index ceca90f..42af547 100644 --- a/src/binarySearchTreeNode.js +++ b/src/binarySearchTreeNode.js @@ -14,7 +14,6 @@ class BinarySearchTreeNode { this.left = null; this.right = null; this.parent = null; - this.height = 1; } /** @@ -96,45 +95,6 @@ class BinarySearchTreeNode { getParent() { return this.parent; } - - /** - * @public - * @return {number} - */ - getLeftHeight() { - return this.left !== null ? this.left.getHeight() : 0; - } - - /** - * @public - * @return {number} - */ - getRightHeight() { - return this.right !== null ? this.right.getHeight() : 0; - } - - /** - * @public - */ - updateHeight() { - this.height = Math.max(this.getLeftHeight(), this.getRightHeight()) + 1; - } - - /** - * @public - * @return {number} - */ - getHeight() { - return this.height; - } - - /** - * @public - * @return {number} - */ - getBalance() { - return this.getLeftHeight() - this.getRightHeight(); - } } module.exports = BinarySearchTreeNode; From ae888abeb3f4a67591b3eb65bc5f3d388a2aec64 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 13:17:56 -0600 Subject: [PATCH 03/19] text --- test/binarySearchTree.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.test.js index ce6cc3c..8814eb3 100644 --- a/test/binarySearchTree.test.js +++ b/test/binarySearchTree.test.js @@ -2,7 +2,7 @@ const { expect } = require('chai'); const BinarySearchTreeNode = require('../src/binarySearchTreeNode'); const BinarySearchTree = require('../src/binarySearchTree'); -describe('binarySearchTree tests', () => { +describe('BinarySearchTree tests', () => { const bst = new BinarySearchTree(); describe('.insert(key, value)', () => { From c2d6e7423d84d18728ea4792c70fb5c0e08312e2 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 22:45:52 -0600 Subject: [PATCH 04/19] text --- test/binarySearchTree.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.test.js index 8814eb3..0051859 100644 --- a/test/binarySearchTree.test.js +++ b/test/binarySearchTree.test.js @@ -66,7 +66,7 @@ describe('BinarySearchTree tests', () => { }); describe('.max()', () => { - it('should get the node with max value', () => { + it('get the node with max key', () => { const max = bst.max(); expect(max.getKey()).to.equal(90); expect(max.getValue()).to.equal('n4'); @@ -74,7 +74,7 @@ describe('BinarySearchTree tests', () => { }); describe('.min()', () => { - it('should get the node with min value', () => { + it('get the node with min key', () => { const min = bst.min(); expect(min.getKey()).to.equal(20); expect(min.getValue()).to.equal('n7'); From bda71a801dd9e556475b24b954adfcaebd6561d5 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 22:47:47 -0600 Subject: [PATCH 05/19] fix height update order --- src/avlTreeNode.js | 70 ++++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/avlTreeNode.js b/src/avlTreeNode.js index 2b21db2..7dd3224 100644 --- a/src/avlTreeNode.js +++ b/src/avlTreeNode.js @@ -19,76 +19,78 @@ class AvlTreeNode extends BinarySearchTreeNode { /** * @public - * rotates left (counter-clockwise) & updates parent and children positions + * rotates left (counter-clockwise) and updates parent and children */ rotateLeft() { + const right = this.getRight(); // this.right will be re-assigned + // set the node as a left child of its right child - if (this.right !== null) { - if (this.right.getLeft() !== null) { - this.right.getLeft().setParent(this); + if (right !== null) { + if (right.getLeft() !== null) { + right.getLeft().setParent(this); } - this.right.setLeft(this); - this.right.setParent(this.parent); + + // rebase right child to node's right left child. + this.right = right.getLeft(); + + right.setLeft(this); + right.setParent(this.parent); } // rebase parent's child to node's right child - if (this.parent !== null && this.right !== null) { - if (this.parent.getKey() < this.right.getKey()) { - this.parent.setRight(this.right); + if (this.parent !== null && right !== null) { + if (this.parent.getKey() < right.getKey()) { + this.parent.setRight(right); } else { - this.parent.setLeft(this.right); + this.parent.setLeft(right); } } // rebase parent to node's right child - this.parent = this.right; - - // rebase right child to node's right left child. - if (this.right !== null) { - this.right = this.right.getLeft(); - } + this.parent = right; + this.updateHeight(); if (this.parent !== null) { this.parent.updateHeight(); } - this.updateHeight(); } /** * @public - * rotates right (clockwise) & updates parent and children positions + * rotates right (clockwise) and updates parent and children */ rotateRight() { + const left = this.getLeft(); // this.left will be re-assigned + // set the node as a right child of its left child - if (this.left !== null) { - if (this.left.getRight() !== null) { - this.left.getRight().setParent(this); + if (left !== null) { + if (left.getRight() !== null) { + left.getRight().setParent(this); } - this.left.setRight(this); - this.left.setParent(this.parent); + + // rebase right child to node's right left child. + this.left = left.getRight(); + + left.setRight(this); + left.setParent(this.parent); } // rebase parent to node's left child - if (this.parent !== null && this.left !== null) { - if (this.parent.getKey() > this.left.getKey()) { - this.parent.setLeft(this.left); + if (this.parent !== null && left !== null) { + if (this.parent.getKey() > left.getKey()) { + this.parent.setLeft(left); } else { - this.parent.setRight(this.left); + this.parent.setRight(left); } } // rebase parent to node's right child - this.parent = this.left; - - // rebase right child to node's right left child. - if (this.left !== null) { - this.left = this.left.getRight(); - } + this.parent = left; + this.updateHeight(); if (this.parent !== null) { this.parent.updateHeight(); } - this.updateHeight(); } /** From 1a55307d9ea4f38bf9e696418b171e7953a5f2ac Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Mon, 30 Mar 2020 22:51:20 -0600 Subject: [PATCH 06/19] add AvlTree class with insert implementation --- src/avlTree.js | 93 +++++++++++++++++++ test/avlTree.test.js | 207 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 src/avlTree.js create mode 100644 test/avlTree.test.js diff --git a/src/avlTree.js b/src/avlTree.js new file mode 100644 index 0000000..207390a --- /dev/null +++ b/src/avlTree.js @@ -0,0 +1,93 @@ +/** + * datastructures-js/binary-search-tree + * @copyright 2020 Eyas Ranjous + * @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) { + 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(); + } + } + + /** + * @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; + } +} + +module.exports = AvlTree; diff --git a/test/avlTree.test.js b/test/avlTree.test.js new file mode 100644 index 0000000..7b1814f --- /dev/null +++ b/test/avlTree.test.js @@ -0,0 +1,207 @@ +const { expect } = require('chai'); +const AvlTree = require('../src/avlTree'); + +describe('AvlTree tests', () => { + const avlTree = new AvlTree(); + + describe('.insert(key, value)', () => { + it('left rotation balancing', () => { + avlTree.insert(50, 'n1'); + avlTree.insert(80, 'n2'); + avlTree.insert(90, 'n3'); + /* + 50 (balance = -2) + \ + 80 + \ + 90 + + lef-rotation of 50 to ==> + + 80 + / \ + 50 90 + */ + expect(avlTree.root().getKey()).to.equal(80); + expect(avlTree.root().getRight().getKey()).to.equal(90); + expect(avlTree.root().getLeft().getKey()).to.equal(50); + }); + + it('right rotation balancing', () => { + avlTree.insert(40, 'n4'); + avlTree.insert(30, 'n5'); + + /* + 80 + / \ + (balance = 2) 50 90 + / + 40 + / + 30 + + right-rotation of 50 to ==> + + 80 + / \ + 40 90 + / \ + 30 50 + */ + expect(avlTree.root().getKey()).to.equal(80); + expect(avlTree.root().getRight().getKey()).to.equal(90); + expect(avlTree.root().getLeft().getKey()).to.equal(40); + expect(avlTree.root().getLeft().getRight().getKey()).to.equal(50); + expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(30); + + avlTree.insert(20, 'n6'); + /* + 80 (balance = 2) + / \ + 40 90 + / \ + 30 50 + / + 20 + + right-rotation of 80 ==> + + 40 + / \ + 30 80 + / / \ + 20 50 90 + */ + expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getLeft().getKey()).to.equal(30); + expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(20); + expect(avlTree.root().getRight().getKey()).to.equal(80); + expect(avlTree.root().getRight().getRight().getKey()).to.equal(90); + expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); + }); + + it('left-right rotation balancing', () => { + avlTree.insert(35, 'n7'); + avlTree.insert(10, 'n8'); + avlTree.insert(15, 'n9'); + /* + verify left-right rotation + 40 + / \ + 30 80 + / \ / \ + 20 35 50 90 + / + 10 + \ + 15 + + left-right rotation of 20 ==> + 40 + / \ + 30 80 + / \ / \ + 15 35 50 90 + / \ + 10 20 + */ + expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getRight().getKey()).to.equal(80); + expect(avlTree.root().getRight().getRight().getKey()).to.equal(90); + expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); + expect(avlTree.root().getLeft().getKey()).to.equal(30); + expect(avlTree.root().getLeft().getRight().getKey()).to.equal(35); + expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(15); + expect(avlTree.root() + .getLeft() + .getLeft() + .getRight() + .getKey()).to.equal(20); + expect(avlTree.root() + .getLeft() + .getLeft() + .getLeft() + .getKey()).to.equal(10); + }); + + it('right-left rotation balancing', () => { + avlTree.insert(100, 'n10'); + avlTree.insert(95, 'n11'); + /* + verify right-left rotation + 40 + / \ + 30 80 + / \ / \ + 15 35 50 90 + / \ \ + 10 20 100 + / + 95 + + right-left rotation of 90 ==> + + 40 + / \ + 30 80 + / \ / \ + 15 35 50 95 + / \ / \ + 10 20 90 100 + */ + expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getRight().getKey()).to.equal(80); + expect(avlTree.root().getRight().getRight().getKey()).to.equal(95); + expect(avlTree.root() + .getRight() + .getRight() + .getRight() + .getKey()).to.equal(100); + expect(avlTree.root() + .getRight() + .getRight() + .getLeft() + .getKey()).to.equal(90); + expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); + expect(avlTree.root().getLeft().getKey()).to.equal(30); + expect(avlTree.root().getLeft().getRight().getKey()).to.equal(35); + expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(15); + expect(avlTree.root() + .getLeft() + .getLeft() + .getRight() + .getKey()).to.equal(20); + expect(avlTree.root() + .getLeft() + .getLeft() + .getLeft() + .getKey()).to.equal(10); + }); + }); + + + describe('.min()', () => { + it('get the node with min key', () => { + expect(avlTree.min().getKey(15)); + }); + }); + + describe('.max()', () => { + it('get the node with min key', () => { + expect(avlTree.max().getKey(100)); + }); + }); + + describe('.root()', () => { + it('should get root node', () => { + expect(avlTree.root().getKey(40)); + }); + }); + + describe('.find(key)', () => { + it('find a node by its key', () => { + expect(avlTree.find(35).getKey()).to.equal(35); + expect(avlTree.find(1000)).to.equal(null); + }); + }); +}); From cfa917dd8d42cc0aa6e5cb885ee4389f58cf900f Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 20:15:19 -0600 Subject: [PATCH 07/19] add avl tree node removal algorithm --- src/avlTree.js | 94 ++++++++++++ test/avlTree.test.js | 352 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 394 insertions(+), 52 deletions(-) diff --git a/src/avlTree.js b/src/avlTree.js index 207390a..96df94b 100644 --- a/src/avlTree.js +++ b/src/avlTree.js @@ -18,6 +18,8 @@ class AvlTree extends BinarySearchTree { * @param {AvlTreeNode} node */ balanceAfterInsert(key, node) { + if (!node) return; + node.updateHeight(); const balance = node.calculateBalance(); if (balance > 1) { @@ -38,6 +40,34 @@ class AvlTree extends BinarySearchTree { } } + /** + * @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 * @@ -88,6 +118,70 @@ class AvlTree extends BinarySearchTree { 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; diff --git a/test/avlTree.test.js b/test/avlTree.test.js index 7b1814f..7ac0e78 100644 --- a/test/avlTree.test.js +++ b/test/avlTree.test.js @@ -22,9 +22,14 @@ describe('AvlTree tests', () => { / \ 50 90 */ - expect(avlTree.root().getKey()).to.equal(80); - expect(avlTree.root().getRight().getKey()).to.equal(90); - expect(avlTree.root().getLeft().getKey()).to.equal(50); + const root = avlTree.root(); + expect(root.getKey()).to.equal(80); + + expect(root.getRight().getKey()).to.equal(90); + expect(root.getRight().getParent().getKey()).to.equal(80); + + expect(root.getLeft().getKey()).to.equal(50); + expect(root.getLeft().getParent().getKey()).to.equal(80); }); it('right rotation balancing', () => { @@ -48,11 +53,20 @@ describe('AvlTree tests', () => { / \ 30 50 */ - expect(avlTree.root().getKey()).to.equal(80); - expect(avlTree.root().getRight().getKey()).to.equal(90); - expect(avlTree.root().getLeft().getKey()).to.equal(40); - expect(avlTree.root().getLeft().getRight().getKey()).to.equal(50); - expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(30); + const root = avlTree.root(); + expect(root.getKey()).to.equal(80); + + expect(root.getRight().getKey()).to.equal(90); + expect(root.getRight().getParent().getKey()).to.equal(80); + + expect(root.getLeft().getKey()).to.equal(40); + expect(root.getLeft().getParent().getKey()).to.equal(80); + + expect(root.getLeft().getRight().getKey()).to.equal(50); + expect(root.getLeft().getRight().getParent().getKey()).to.equal(40); + + expect(root.getLeft().getLeft().getKey()).to.equal(30); + expect(root.getLeft().getLeft().getParent().getKey()).to.equal(40); avlTree.insert(20, 'n6'); /* @@ -73,8 +87,10 @@ describe('AvlTree tests', () => { 20 50 90 */ expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getLeft().getKey()).to.equal(30); expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(20); + expect(avlTree.root().getRight().getKey()).to.equal(80); expect(avlTree.root().getRight().getRight().getKey()).to.equal(90); expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); @@ -105,23 +121,18 @@ describe('AvlTree tests', () => { / \ 10 20 */ - expect(avlTree.root().getKey()).to.equal(40); - expect(avlTree.root().getRight().getKey()).to.equal(80); - expect(avlTree.root().getRight().getRight().getKey()).to.equal(90); - expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); - expect(avlTree.root().getLeft().getKey()).to.equal(30); - expect(avlTree.root().getLeft().getRight().getKey()).to.equal(35); - expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(15); - expect(avlTree.root() - .getLeft() - .getLeft() - .getRight() - .getKey()).to.equal(20); - expect(avlTree.root() - .getLeft() - .getLeft() - .getLeft() - .getKey()).to.equal(10); + const root = avlTree.root(); + expect(root.getKey()).to.equal(40); + + expect(root.getRight().getKey()).to.equal(80); + expect(root.getRight().getRight().getKey()).to.equal(90); + expect(root.getRight().getLeft().getKey()).to.equal(50); + + expect(root.getLeft().getKey()).to.equal(30); + expect(root.getLeft().getRight().getKey()).to.equal(35); + expect(root.getLeft().getLeft().getKey()).to.equal(15); + expect(root.getLeft().getLeft().getRight().getKey()).to.equal(20); + expect(root.getLeft().getLeft().getLeft().getKey()).to.equal(10); }); it('right-left rotation balancing', () => { @@ -149,33 +160,20 @@ describe('AvlTree tests', () => { / \ / \ 10 20 90 100 */ - expect(avlTree.root().getKey()).to.equal(40); - expect(avlTree.root().getRight().getKey()).to.equal(80); - expect(avlTree.root().getRight().getRight().getKey()).to.equal(95); - expect(avlTree.root() - .getRight() - .getRight() - .getRight() - .getKey()).to.equal(100); - expect(avlTree.root() - .getRight() - .getRight() - .getLeft() - .getKey()).to.equal(90); - expect(avlTree.root().getRight().getLeft().getKey()).to.equal(50); - expect(avlTree.root().getLeft().getKey()).to.equal(30); - expect(avlTree.root().getLeft().getRight().getKey()).to.equal(35); - expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(15); - expect(avlTree.root() - .getLeft() - .getLeft() - .getRight() - .getKey()).to.equal(20); - expect(avlTree.root() - .getLeft() - .getLeft() - .getLeft() - .getKey()).to.equal(10); + const root = avlTree.root(); + expect(root.getKey()).to.equal(40); + + expect(root.getRight().getKey()).to.equal(80); + expect(root.getRight().getRight().getKey()).to.equal(95); + expect(root.getRight().getRight().getRight().getKey()).to.equal(100); + expect(root.getRight().getRight().getLeft().getKey()).to.equal(90); + expect(root.getRight().getLeft().getKey()).to.equal(50); + + expect(root.getLeft().getKey()).to.equal(30); + expect(root.getLeft().getRight().getKey()).to.equal(35); + expect(root.getLeft().getLeft().getKey()).to.equal(15); + expect(root.getLeft().getLeft().getRight().getKey()).to.equal(20); + expect(root.getLeft().getLeft().getLeft().getKey()).to.equal(10); }); }); @@ -204,4 +202,254 @@ describe('AvlTree tests', () => { expect(avlTree.find(1000)).to.equal(null); }); }); + + describe('.traverseInOrder(cb)', () => { + it('traverse the tree in order', () => { + const keys = []; + avlTree.traverseInOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([ + 10, 15, 20, 30, 35, 40, 50, 80, 90, 95, 100 + ]); + }); + }); + + describe('.traversePreOrder(cb)', () => { + it('traverse the tree in order', () => { + const keys = []; + avlTree.traversePreOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([ + 40, 30, 15, 10, 20, 35, 80, 50, 95, 90, 100 + ]); + }); + }); + + describe('.traversePostOrder(cb)', () => { + it('traverse the tree post order', () => { + const keys = []; + avlTree.traversePostOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([ + 10, 20, 15, 35, 30, 50, 90, 100, 95, 80, 40 + ]); + }); + }); + + describe('.remove(key)', () => { + it('right rotation balancing', () => { + /* + 40 + / \ + 30 80 + / \ / \ + 15 35 50 95 + / \ / \ + 10 20 90 100 + */ + + avlTree.remove(35); + + /* + 40 + / \ + (balance = 2) 30 80 + / / \ + 15 50 95 + / \ / \ + 10 20 90 100 + + right rotation of 30 ==> + + 40 + / \ + 15 80 + / \ / \ + 10 30 50 95 + / / \ + 20 90 100 + */ + const root = avlTree.root(); + expect(root.getKey()).to.equal(40); + expect(root.getRight().getKey()).to.equal(80); + expect(root.getRight().getRight().getKey()).to.equal(95); + expect(root.getRight().getRight().getRight().getKey()).to.equal(100); + expect(root.getRight().getRight().getLeft().getKey()).to.equal(90); + expect(root.getRight().getLeft().getKey()).to.equal(50); + expect(root.getLeft().getKey()).to.equal(15); + expect(root.getLeft().getRight().getKey()).to.equal(30); + expect(root.getLeft().getRight().getLeft().getKey()).to.equal(20); + expect(root.getLeft().getLeft().getKey()).to.equal(10); + }); + + it('right-left rotation balancing', () => { + /* + 40 + / \ + 15 80 + / \ / \ + 10 30 50 95 + / / \ + 20 90 100 + */ + + avlTree.remove(10); + + /* + 40 + / \ + (balance = -2) 15 80 + \ / \ + 30 50 95 + / / \ + 20 90 100 + + right-left rotation of 15 ==> + + 40 + / \ + 20 80 + / \ / \ + 15 30 50 95 + / \ + 90 100 + */ + const root = avlTree.root(); + expect(root.getKey()).to.equal(40); + expect(root.getRight().getKey()).to.equal(80); + expect(root.getRight().getRight().getKey()).to.equal(95); + expect(root.getRight().getRight().getRight().getKey()).to.equal(100); + expect(root.getRight().getRight().getLeft().getKey()).to.equal(90); + expect(root.getRight().getLeft().getKey()).to.equal(50); + expect(root.getLeft().getKey()).to.equal(20); + expect(root.getLeft().getRight().getKey()).to.equal(30); + expect(root.getLeft().getLeft().getKey()).to.equal(15); + }); + + it('left rotation balancing', () => { + /* + + 40 + / \ + 20 80 + / \ / \ + 15 30 50 95 + / \ + 90 100 + */ + + avlTree.remove(90); + avlTree.remove(50); + + /* + 40 + / \ + 20 80 (balance = -2) + / \ \ + 15 30 95 + \ + 100 + + left rotation of 15 ==> + + 40 + / \ + 20 95 + / \ / \ + 15 30 80 100 + */ + expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getRight().getKey()).to.equal(95); + expect(avlTree.root().getRight().getRight().getKey()).to.equal(100); + expect(avlTree.root().getRight().getLeft().getKey()).to.equal(80); + expect(avlTree.root().getLeft().getKey()).to.equal(20); + expect(avlTree.root().getLeft().getRight().getKey()).to.equal(30); + expect(avlTree.root().getLeft().getLeft().getKey()).to.equal(15); + }); + + it('left-right rotation balancing', () => { + avlTree.insert(85); + /* + 40 + / \ + 20 95 + / \ / \ + 15 30 80 100 + \ + 85 + */ + + avlTree.remove(100); + /* + 40 + / \ + 20 95 (balance = 2) + / \ / + 15 30 80 + \ + 85 + + + left-right rotation of 95 ==> + + 40 + / \ + 20 85 + / \ / \ + 15 30 80 95 + */ + const root = avlTree.root(); + expect(root.getKey()).to.equal(40); + expect(root.getLeft().getKey()).to.equal(20); + expect(root.getLeft().getRight().getKey()).to.equal(30); + expect(root.getLeft().getLeft().getKey()).to.equal(15); + expect(root.getRight().getKey()).to.equal(85); + expect(root.getRight().getLeft().getKey()).to.equal(80); + expect(root.getRight().getRight().getKey()).to.equal(95); + }); + + it('removes the rest of nodes properly', () => { + /* + 40 + / \ + 20 85 + / \ / \ + 15 30 80 95 + */ + avlTree.remove(30); + avlTree.remove(80); + avlTree.remove(95); + avlTree.remove(85); + /* + 40 (balance = 2) + / + 20 + / + 15 + + right rotation of 40 ==> + 20 + / \ + 15 40 + */ + + + expect(avlTree.root().getKey()).to.equal(20); + expect(avlTree.root().getLeft().getKey()).to.equal(15); + expect(avlTree.root().getRight().getKey()).to.equal(40); + + avlTree.remove(20); + expect(avlTree.root().getKey()).to.equal(40); + expect(avlTree.root().getLeft().getKey()).to.equal(15); + + avlTree.remove(40); + expect(avlTree.root().getKey()).to.equal(15); + expect(avlTree.count()).to.equal(1); + + avlTree.insert(20, 'n12'); + avlTree.remove(15); + expect(avlTree.root().getKey()).to.equal(20); + expect(avlTree.count()).to.equal(1); + avlTree.remove(20); + expect(avlTree.root()).to.equal(null); + expect(avlTree.count()).to.equal(0); + }); + }); }); From 191ef27ef5afba9f48d9b5433efb0136732daee0 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:09:08 -0600 Subject: [PATCH 08/19] update --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ec9c7d..5bb5bee 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,18 @@ [![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. + +**BinarySearchTree** Binary Search Tree + +**AVL Tree** + +AVL Tree + + # Table of Contents * [Install](#install) * [API](#api) From b32166b7e9a6379527ed18489ded44312153d70f Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:10:13 -0600 Subject: [PATCH 09/19] update --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5bb5bee..d95c0c5 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,10 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript. **BinarySearchTree** - Binary Search Tree - +

**AVL Tree** - AVL Tree From a58552035b36b0c2a8892b5fb8b48a8c19e4b3dd Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:12:08 -0600 Subject: [PATCH 10/19] update --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d95c0c5..7ff962b 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,23 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript. -**BinarySearchTree** + + + + + +
Binary Search Tree Binary Search Tree +
-

-**AVL Tree** + + + + + +
AVL Tree AVL Tree - +
# Table of Contents * [Install](#install) From baeec11e72931ee09d0c640a7db53cec55c7a901 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:14:19 -0600 Subject: [PATCH 11/19] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ff962b..01395da 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript - + + + @@ -17,7 +19,9 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript
Binary Search TreeBinary Search Tree
Binary Search Tree
- + + + From dbeb633a6c6b20ee4440b734f400e98f36c231ff Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:18:48 -0600 Subject: [PATCH 12/19] update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01395da..1f7a3db 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Binary Search Tree & AVL Tree (Self Balancing Tree) implementation in javascript
AVL TreeAVL Tree
AVL Tree
- +
AVL TreeAVL Tree (Self Balancing Tree)
@@ -57,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 From aae0b947bf6b9b28442016ad5a67d709312edd98 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:22:48 -0600 Subject: [PATCH 13/19] update --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f7a3db..e64d0e9 100644 --- a/README.md +++ b/README.md @@ -72,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. @@ -343,7 +347,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.
From e16e5317f958491c7425f70722196daaffec4fc7 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:43:02 -0600 Subject: [PATCH 14/19] jsdoc --- src/avlTreeNode.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/avlTreeNode.js b/src/avlTreeNode.js index 7dd3224..59c5833 100644 --- a/src/avlTreeNode.js +++ b/src/avlTreeNode.js @@ -18,7 +18,7 @@ class AvlTreeNode extends BinarySearchTreeNode { } /** - * @public + * @internal * rotates left (counter-clockwise) and updates parent and children */ rotateLeft() { @@ -56,7 +56,7 @@ class AvlTreeNode extends BinarySearchTreeNode { } /** - * @public + * @internal * rotates right (clockwise) and updates parent and children */ rotateRight() { @@ -94,7 +94,7 @@ class AvlTreeNode extends BinarySearchTreeNode { } /** - * @public + * @internal * rotates left child to left then itself to right */ rotateLeftRight() { @@ -105,7 +105,7 @@ class AvlTreeNode extends BinarySearchTreeNode { } /** - * @public + * @internal * rotates right child to right then itself to left */ rotateRightLeft() { @@ -132,7 +132,7 @@ class AvlTreeNode extends BinarySearchTreeNode { } /** - * @public + * @internal * updates the height of a node as the max height of its children */ updateHeight() { From d4b06b3bb81c69784d114dade4fb4a1874d31176 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:43:07 -0600 Subject: [PATCH 15/19] update --- README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e64d0e9..b871990 100644 --- a/README.md +++ b/README.md @@ -96,14 +96,20 @@ inserts a node with key/value into the tree. Inserting an node with existing key value: {object}
- {BinarySearchTreeNode} the inserted node -

+ {BinarySearchTreeNode} for BinarySearchTree +
.getKey() {number|string} returns the node's key that is used to compare with other nodes.
.setValue(value) change the value that is associated with a node.
.getValue() {object} returns the value that is associated with a node.
.getLeft() {BinarySearchTreeNode} returns node's left child node.
.getRight() {BinarySearchTreeNode} returns node's right child node.
.getParent() {BinarySearchTreeNode} returns node's parent node. +

+ {AvlTreeNode} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods: +
+ .getHeight() {number} the height of the node in the tree. root is 1. + .getLeftHeight() {number} the height of the left child. 0 if no left child. + .getRightHeight() {number} the height of the right child. 0 if no right child.
@@ -158,7 +164,9 @@ finds a node in the tree by its key. key: {number} or {string}
- {BinarySearchTreeNode} + {BinarySearchTreeNode} for BinarySearchTree +

+ {AvlTreeNode} for AvlTree
@@ -182,7 +190,9 @@ finds the node with min key in the tree. O(log(n)) - {BinarySearchTreeNode} + {BinarySearchTreeNode} for BinarySearchTree +

+ {AvlTreeNode} for AvlTree @@ -204,7 +214,9 @@ finds the node with max key in the tree. O(log(n)) - {BinarySearchTreeNode} + {BinarySearchTreeNode} for BinarySearchTree +

+ {AvlTreeNode} for AvlTree @@ -225,7 +237,9 @@ returns the root node of the tree. O(1) - {BinarySearchTreeNode} + {BinarySearchTreeNode} for BinarySearchTree +

+ {AvlTreeNode} for AvlTree From 6c1ed483291af1f1af0d81bdafef986106f62e63 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:43:55 -0600 Subject: [PATCH 16/19] update --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b871990..3853e78 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,12 @@ inserts a node with key/value into the tree. Inserting an node with existing key .getLeft() {BinarySearchTreeNode} returns node's left child node.
.getRight() {BinarySearchTreeNode} returns node's right child node.
.getParent() {BinarySearchTreeNode} returns node's parent node. -

+


{AvlTreeNode} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods: -
- .getHeight() {number} the height of the node in the tree. root is 1. - .getLeftHeight() {number} the height of the left child. 0 if no left child. - .getRightHeight() {number} the height of the right child. 0 if no right child. +

+ .getHeight() {number} the height of the node in the tree. root is 1.
+ .getLeftHeight() {number} the height of the left child. 0 if no left child.
+ .getRightHeight() {number} the height of the right child. 0 if no right child.
From bd43bf85ce2ff001c3b54d812f8c0f59537471cd Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:44:33 -0600 Subject: [PATCH 17/19] update --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3853e78..42ebb3b 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ inserts a node with key/value into the tree. Inserting an node with existing key value: {object} - {BinarySearchTreeNode} for BinarySearchTree -
+ {BinarySearchTreeNode} for BinarySearchTree +

.getKey() {number|string} returns the node's key that is used to compare with other nodes.
.setValue(value) change the value that is associated with a node.
.getValue() {object} returns the value that is associated with a node.
@@ -105,7 +105,7 @@ inserts a node with key/value into the tree. Inserting an node with existing key .getRight() {BinarySearchTreeNode} returns node's right child node.
.getParent() {BinarySearchTreeNode} returns node's parent node.


- {AvlTreeNode} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods: + {AvlTreeNode} for AvlTree. It extends the BinarySearchTreeNode and adds the following methods:

.getHeight() {number} the height of the node in the tree. root is 1.
.getLeftHeight() {number} the height of the left child. 0 if no left child.
From dfcaee8ad46a43045cc971ad8e0d34cd8b288a51 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:46:11 -0600 Subject: [PATCH 18/19] v3.1.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2bcda3..56a4224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` From b40b5b2337c74bc051c54d1ce325076d12583c43 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Tue, 31 Mar 2020 21:47:22 -0600 Subject: [PATCH 19/19] v3.1.0 --- package.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1e8e7a3..b8093b5 100644 --- a/package.json +++ b/package.json @@ -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" @@ -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 ", "license": "MIT",