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` diff --git a/README.md b/README.md index 3ec9c7d..42ebb3b 100644 --- a/README.md +++ b/README.md @@ -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. + + + + + + + +
Binary Search Tree
Binary Search Tree +
+ + + + + + + + +
AVL Tree (Self Balancing Tree)
+AVL Tree +
# Table of Contents * [Install](#install) @@ -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 @@ -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. @@ -70,7 +96,7 @@ 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.
@@ -78,6 +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.
@@ -132,7 +164,9 @@ finds a node in the tree by its key. key: {number} or {string} - {BinarySearchTreeNode} + {BinarySearchTreeNode} for BinarySearchTree +

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

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

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

+ {AvlTreeNode} for AvlTree @@ -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. 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", diff --git a/src/avlTree.js b/src/avlTree.js new file mode 100644 index 0000000..96df94b --- /dev/null +++ b/src/avlTree.js @@ -0,0 +1,187 @@ +/** + * 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) { + 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; diff --git a/src/avlTreeNode.js b/src/avlTreeNode.js new file mode 100644 index 0000000..59c5833 --- /dev/null +++ b/src/avlTreeNode.js @@ -0,0 +1,160 @@ +/** + * 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; + } + + /** + * @internal + * 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 (right !== null) { + if (right.getLeft() !== null) { + right.getLeft().setParent(this); + } + + // 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 && right !== null) { + if (this.parent.getKey() < right.getKey()) { + this.parent.setRight(right); + } else { + this.parent.setLeft(right); + } + } + + // rebase parent to node's right child + this.parent = right; + + this.updateHeight(); + if (this.parent !== null) { + this.parent.updateHeight(); + } + } + + /** + * @internal + * 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 (left !== null) { + if (left.getRight() !== null) { + left.getRight().setParent(this); + } + + // 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 && left !== null) { + if (this.parent.getKey() > left.getKey()) { + this.parent.setLeft(left); + } else { + this.parent.setRight(left); + } + } + + // rebase parent to node's right child + this.parent = left; + + this.updateHeight(); + if (this.parent !== null) { + this.parent.updateHeight(); + } + } + + /** + * @internal + * rotates left child to left then itself to right + */ + rotateLeftRight() { + if (this.left !== null) { + this.left.rotateLeft(); + } + this.rotateRight(); + } + + /** + * @internal + * 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; + } + + /** + * @internal + * 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; 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; diff --git a/test/avlTree.test.js b/test/avlTree.test.js new file mode 100644 index 0000000..7ac0e78 --- /dev/null +++ b/test/avlTree.test.js @@ -0,0 +1,455 @@ +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 + */ + 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', () => { + 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 + */ + 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'); + /* + 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 + */ + 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', () => { + 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 + */ + 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); + }); + }); + + + 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); + }); + }); + + 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); + }); + }); +}); diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.test.js index ce6cc3c..0051859 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)', () => { @@ -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');