From 197a26f065e660f2006d0f8fa9bfb4a354e371d7 Mon Sep 17 00:00:00 2001 From: eyas-ranjous Date: Sun, 19 Oct 2025 19:43:02 -0700 Subject: [PATCH 1/3] implement iterative version of the bst --- src/avlTree.js | 140 +++- src/binarySearchTree.js | 481 ++++++++++++- test/avlTree.iterative.test.js | 647 ++++++++++++++++++ ...Tree.test.js => avlTree.recursive.test.js} | 2 +- test/binarySearchTree.iterative.test.js | 308 +++++++++ ....js => binarySearchTree.recursive.test.js} | 2 +- 6 files changed, 1540 insertions(+), 40 deletions(-) create mode 100644 test/avlTree.iterative.test.js rename test/{avlTree.test.js => avlTree.recursive.test.js} (99%) create mode 100644 test/binarySearchTree.iterative.test.js rename test/{binarySearchTree.test.js => binarySearchTree.recursive.test.js} (99%) diff --git a/src/avlTree.js b/src/avlTree.js index 5533ba6..91804e8 100644 --- a/src/avlTree.js +++ b/src/avlTree.js @@ -68,13 +68,13 @@ class AvlTree extends BinarySearchTree { /** * Inserts a value into the tree and maintains - * the tree balanced by making the necessary rotations + * the tree balanced by making the necessary rotations (recursive implementation) * - * @public + * @private * @param {number|string|object} value * @return {AvlTree} */ - insert(value) { + _insertRecursive(value) { const newNode = new AvlTreeNode(value, this._compare); const insertRecursive = (current) => { const compare = this._compare(value, current.getValue()); @@ -112,14 +112,86 @@ class AvlTree extends BinarySearchTree { } /** - * Removes a node from the tree and maintains + * Inserts a value into the tree and maintains + * the tree balanced by making the necessary rotations (iterative implementation) + * + * @private + * @param {number|string|object} value + * @return {AvlTree} + */ + _insertIterative(value) { + const newNode = new AvlTreeNode(value, this._compare); + + if (this._root === null) { + this._root = newNode; + this._count += 1; + return this; + } + + // Find insertion point and track path + const path = []; + let node = this._root; + let inserted = false; + + while (!inserted) { + path.push(node); + const compare = this._compare(value, node.getValue()); + + if (compare < 0) { + if (node.hasLeft()) { + node = node.getLeft(); + } else { + newNode.setParent(node); + node.setLeft(newNode).updateHeight(); + this._count += 1; + inserted = true; + } + } else if (compare > 0) { + if (node.hasRight()) { + node = node.getRight(); + } else { + newNode.setParent(node); + node.setRight(newNode).updateHeight(); + this._count += 1; + inserted = true; + } + } else { + node.setValue(value); + return this; + } + } + + // Balance all ancestors in reverse order (backward-tracking) + for (let i = path.length - 1; i >= 0; i -= 1) { + this._balanceNode(path[i]); + } + + return this; + } + + /** + * Inserts a value into the tree and maintains * the tree balanced by making the necessary rotations * * @public * @param {number|string|object} value + * @return {AvlTree} + */ + insert(value) { + return this._iterative + ? this._insertIterative(value) + : this._insertRecursive(value); + } + + /** + * Removes a node from the tree and maintains + * the tree balanced by making the necessary rotations (recursive implementation) + * + * @private + * @param {number|string|object} value * @return {boolean} */ - remove(value) { + _removeRecursive(value) { const removeRecursively = (val, current) => { if (current === null) { return false; @@ -145,6 +217,64 @@ class AvlTree extends BinarySearchTree { return removeRecursively(value, this._root); } + /** + * Removes a node from the tree and maintains + * the tree balanced by making the necessary rotations (iterative implementation) + * + * @private + * @param {number|string|object} value + * @return {boolean} + */ + _removeIterative(value) { + if (this._root === null) { + return false; + } + + const path = []; + let node = this._root; + let found = false; + while (node !== null && !found) { + const compare = this._compare(value, node.getValue()); + + if (compare === 0) { + found = true; + } else { + path.push(node); + if (compare < 0) { + node = node.getLeft(); + } else { + node = node.getRight(); + } + } + } + + if (!found) { + return false; + } + + const removed = this.removeNode(node); + for (let i = path.length - 1; i >= 0; i -= 1) { + // Balance all ancestors in reverse order (backward-tracking) + this._balanceNode(path[i]); + } + + return removed; + } + + /** + * Removes a node from the tree and maintains + * the tree balanced by making the necessary rotations + * + * @public + * @param {number|string|object} value + * @return {boolean} + */ + remove(value) { + return this._iterative + ? this._removeIterative(value) + : this._removeRecursive(value); + } + /** * Removes a node from the tree * @public diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 3eb9cbd..42ad289 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -22,17 +22,18 @@ class BinarySearchTree { this._compare = compare || defaultCompare; this._options = options || {}; + this._iterative = this._options.iterative || false; this._root = null; this._count = 0; } /** - * Inserts a node with a key/value into the tree - * @public + * Inserts a node with a key/value into the tree (recursive implementation) + * @private * @param {number|string|object} value * @return {BinarySearchTree} */ - insert(value) { + _insertRecursive(value) { const newNode = new BinarySearchTreeNode(value); const insertRecursive = (current) => { const compare = this._compare(newNode.getValue(), current.getValue()); @@ -66,12 +67,66 @@ class BinarySearchTree { } /** - * Checks if a value exists in the tree by its value + * Inserts a node with a key/value into the tree (iterative implementation) + * @private + * @param {number|string|object} value + * @return {BinarySearchTree} + */ + _insertIterative(value) { + const newNode = new BinarySearchTreeNode(value); + let node = this._root; + if (!node) { + this._root = newNode; + this._count += 1; + } else { + let inserted = false; + while (!inserted) { + const compare = this._compare(newNode.getValue(), node.getValue()); + + if (compare < 0) { + if (node.hasLeft()) { + node = node.getLeft(); + } else { + node.setLeft(newNode.setParent(node)); + this._count += 1; + inserted = true; + } + } else if (compare > 0) { + if (node.hasRight()) { + node = node.getRight(); + } else { + node.setRight(newNode.setParent(node)); + this._count += 1; + inserted = true; + } + } else { + node.setValue(value); + inserted = true; + } + } + } + return this; + } + + /** + * Inserts a node with a key/value into the tree * @public * @param {number|string|object} value + * @return {BinarySearchTree} + */ + insert(value) { + return this._iterative + ? this._insertIterative(value) + : this._insertRecursive(value); + } + + /** + * Checks if a value exists in the tree by its value (recursive implementation) + * @private + * @param {number|string|object} value * @return {boolean} */ - has(value) { + _hasRecursive(value) { const hasRecursive = (current) => { if (current === null) return false; @@ -84,6 +139,41 @@ class BinarySearchTree { return hasRecursive(this._root); } + /** + * Checks if a value exists in the tree by its value (iterative implementation) + * @private + * @param {number|string|object} value + * @return {boolean} + */ + _hasIterative(value) { + let current = this._root; + while (current !== null) { + const compare = this._compare(value, current.getValue()); + + if (compare === 0) { + return true; + } + if (compare < 0) { + current = current.getLeft(); + } else { + current = current.getRight(); + } + } + return false; + } + + /** + * Checks if a value exists in the tree by its value + * @public + * @param {number|string|object} value + * @return {boolean} + */ + has(value) { + return this._iterative + ? this._hasIterative(value) + : this._hasRecursive(value); + } + /** * Checks if a value exists in the tree by its key * @public @@ -98,12 +188,12 @@ class BinarySearchTree { } /** - * Finds a node by its value - * @public + * Finds a node by its value (recursive implementation) + * @private * @param {number|string|object} value * @return {BinarySearchTreeNode} */ - find(value) { + _findRecursive(value) { const findRecursive = (current) => { if (current === null) return null; @@ -116,6 +206,43 @@ class BinarySearchTree { return findRecursive(this._root); } + /** + * Finds a node by its value (iterative implementation) + * @private + * @param {number|string|object} value + * @return {BinarySearchTreeNode} + */ + _findIterative(value) { + let current = this._root; + + while (current !== null) { + const compare = this._compare(value, current.getValue()); + + if (compare === 0) { + return current; + } + if (compare < 0) { + current = current.getLeft(); + } else { + current = current.getRight(); + } + } + + return null; + } + + /** + * Finds a node by its value + * @public + * @param {number|string|object} value + * @return {BinarySearchTreeNode} + */ + find(value) { + return this._iterative + ? this._findIterative(value) + : this._findRecursive(value); + } + /** * Finds a node by its object's key * @public @@ -129,6 +256,33 @@ class BinarySearchTree { return this.find({ [this._options.key]: key }); } + /** + * Finds the node with max key (most right) in the tree (recursive implementation) + * @private + * @param {BinarySearchTreeNode} [current] + * @return {BinarySearchTreeNode} + */ + _maxRecursive(current = this._root) { + if (current === null) return null; + if (current.hasRight()) return this._maxRecursive(current.getRight()); + return current; + } + + /** + * Finds the node with max key (most right) in the tree (iterative implementation) + * @private + * @param {BinarySearchTreeNode} [current] + * @return {BinarySearchTreeNode} + */ + _maxIterative(current = this._root) { + if (current === null) return null; + let node = current; + while (node.hasRight()) { + node = node.getRight(); + } + return node; + } + /** * Finds the node with max key (most right) in the tree * @public @@ -136,11 +290,38 @@ class BinarySearchTree { * @return {BinarySearchTreeNode} */ max(current = this._root) { + return this._iterative + ? this._maxIterative(current) + : this._maxRecursive(current); + } + + /** + * Finds the node with min key (most left) in the tree (recursive implementation) + * @private + * @param {BinarySearchTreeNode} [current] + * @return {BinarySearchTreeNode} + */ + _minRecursive(current = this._root) { if (current === null) return null; - if (current.hasRight()) return this.max(current.getRight()); + if (current.hasLeft()) return this._minRecursive(current.getLeft()); return current; } + /** + * Finds the node with min key (most left) in the tree (iterative implementation) + * @private + * @param {BinarySearchTreeNode} [current] + * @return {BinarySearchTreeNode} + */ + _minIterative(current = this._root) { + if (current === null) return null; + let node = current; + while (node.hasLeft()) { + node = node.getLeft(); + } + return node; + } + /** * Finds the node with min key (most left) in the tree * @public @@ -148,19 +329,19 @@ class BinarySearchTree { * @return {BinarySearchTreeNode} */ min(current = this._root) { - if (current === null) return null; - if (current.hasLeft()) return this.min(current.getLeft()); - return current; + return this._iterative + ? this._minIterative(current) + : this._minRecursive(current); } /** - * Returns the node with the biggest value less or equal a given value - * @public + * Returns the node with the biggest value less or equal a given value (recursive implementation) + * @private * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - lowerBound(value, includeEqual = true) { + _lowerBoundRecursive(value, includeEqual = true) { let lowerBound = null; const lowerBoundRecursive = (current) => { @@ -180,6 +361,46 @@ class BinarySearchTree { return lowerBoundRecursive(this._root); } + /** + * Returns the node with the biggest value less or equal a given value (iterative implementation) + * @private + * @param {number|string|object} value + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + _lowerBoundIterative(value, includeEqual = true) { + let lowerBound = null; + let current = this._root; + + while (current !== null) { + const compare = this._compare(value, current.getValue()); + + if (compare > 0 || (includeEqual && compare === 0)) { + if (lowerBound === null || this._compare(lowerBound.getValue(), current.getValue()) < 0) { + lowerBound = current; + } + current = current.getRight(); + } else { + current = current.getLeft(); + } + } + + return lowerBound; + } + + /** + * Returns the node with the biggest value less or equal a given value + * @public + * @param {number|string|object} value + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + lowerBound(value, includeEqual = true) { + return this._iterative + ? this._lowerBoundIterative(value, includeEqual) + : this._lowerBoundRecursive(value, includeEqual); + } + /** * Returns the node with the biggest object's key less or equal a given key * @public @@ -218,13 +439,13 @@ class BinarySearchTree { } /** - * Returns the node with the smallest value greater or equal a given value - * @public + * Returns the node with the smallest value greater or equal a given value (recursive implementation) + * @private * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - upperBound(value, includeEqual = true) { + _upperBoundRecursive(value, includeEqual = true) { let upperBound = null; const upperBoundRecursive = (current) => { @@ -244,6 +465,46 @@ class BinarySearchTree { return upperBoundRecursive(this._root); } + /** + * Returns the node with the smallest value greater or equal a given value (iterative implementation) + * @private + * @param {number|string|object} value + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + _upperBoundIterative(value, includeEqual = true) { + let upperBound = null; + let current = this._root; + + while (current !== null) { + const compare = this._compare(value, current.getValue()); + + if (compare < 0 || (includeEqual && compare === 0)) { + if (upperBound === null || this._compare(upperBound.getValue(), current.getValue()) > 0) { + upperBound = current; + } + current = current.getLeft(); + } else { + current = current.getRight(); + } + } + + return upperBound; + } + + /** + * Returns the node with the smallest value greater or equal a given value + * @public + * @param {number|string|object} value + * @param {boolean} includeEqual + * @return {BinarySearchTreeNode|null} + */ + upperBound(value, includeEqual = true) { + return this._iterative + ? this._upperBoundIterative(value, includeEqual) + : this._upperBoundRecursive(value, includeEqual); + } + /** * Returns the node with the smallest object's key greater or equal a given key * @public @@ -300,12 +561,12 @@ class BinarySearchTree { } /** - * Removes a node by its value - * @public + * Removes a node by its value (recursive implementation) + * @private * @param {number|string|object} value * @return {boolean} */ - remove(value) { + _removeRecursive(value) { const removeRecursively = (val, current) => { if (current === null) return false; @@ -319,6 +580,44 @@ class BinarySearchTree { return removeRecursively(value, this._root); } + /** + * Removes a node by its value (iterative implementation) + * @private + * @param {number|string|object} value + * @return {boolean} + */ + _removeIterative(value) { + let current = this._root; + + while (current !== null) { + const compare = this._compare(value, current.getValue()); + + if (compare === 0) { + this.removeNode(current); + return true; + } + if (compare < 0) { + current = current.getLeft(); + } else { + current = current.getRight(); + } + } + + return false; + } + + /** + * Removes a node by its value + * @public + * @param {number|string|object} value + * @return {boolean} + */ + remove(value) { + return this._iterative + ? this._removeIterative(value) + : this._removeRecursive(value); + } + /** * Removes a node from the tree * @public @@ -377,6 +676,51 @@ class BinarySearchTree { return this.removeNode(minRight); } + /** + * Traverses the tree in-order (left-node-right) (recursive implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traverseInOrderRecursive(cb, abortCb) { + const traverseRecursive = (current) => { + if (current === null || (abortCb && abortCb())) return; + traverseRecursive(current.getLeft()); + if (abortCb && abortCb()) return; + cb(current); + traverseRecursive(current.getRight()); + }; + + traverseRecursive(this._root); + } + + /** + * Traverses the tree in-order (left-node-right) (iterative implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traverseInOrderIterative(cb, abortCb) { + let current = this._root; + const stack = []; + + while (current !== null || stack.length > 0) { + while (current !== null) { + stack.push(current); + current = current.getLeft(); + } + current = stack.pop(); + + if (abortCb && abortCb()) { + return; + } + + cb(current); + + current = current.getRight(); + } + } + /** * Traverses the tree in-order (left-node-right) * @public @@ -388,17 +732,49 @@ class BinarySearchTree { throw new Error('.traverseInOrder expects a callback function'); } + return this._iterative + ? this._traverseInOrderIterative(cb, abortCb) + : this._traverseInOrderRecursive(cb, abortCb); + } + + /** + * Traverses the tree pre-order (node-left-right) (recursive implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traversePreOrderRecursive(cb, abortCb) { const traverseRecursive = (current) => { if (current === null || (abortCb && abortCb())) return; - traverseRecursive(current.getLeft()); - if (abortCb && abortCb()) return; cb(current); + traverseRecursive(current.getLeft()); traverseRecursive(current.getRight()); }; traverseRecursive(this._root); } + /** + * Traverses the tree pre-order (node-left-right) (iterative implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traversePreOrderIterative(cb, abortCb) { + const stack = [this._root]; + + while (stack.length) { + const current = stack.pop(); + + if (abortCb && abortCb()) break; + if (current) { + cb(current); + stack.push(current.getRight()); + stack.push(current.getLeft()); + } + } + } + /** * Traverses the tree pre-order (node-left-right) * @public @@ -410,16 +786,61 @@ class BinarySearchTree { throw new Error('.traversePreOrder expects a callback function'); } + return this._iterative + ? this._traversePreOrderIterative(cb, abortCb) + : this._traversePreOrderRecursive(cb, abortCb); + } + + /** + * Traverses the tree post-order (left-right-node) (recursive implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traversePostOrderRecursive(cb, abortCb) { const traverseRecursive = (current) => { if (current === null || (abortCb && abortCb())) return; - cb(current); traverseRecursive(current.getLeft()); traverseRecursive(current.getRight()); + if (abortCb && abortCb()) return; + cb(current); }; traverseRecursive(this._root); } + /** + * Traverses the tree post-order (left-right-node) (iterative implementation) + * @private + * @param {function} cb + * @param {function} [abortCb] + */ + _traversePostOrderIterative(cb, abortCb) { + const s1 = []; + const s2 = []; + s1.push(this._root); + + while (s1.length) { + const current = s1.pop(); + + s2.push(current); + + if (abortCb && abortCb()) break; + if (current) { + s1.push(current.getLeft()); + s1.push(current.getRight()); + } + } + + while (s2.length) { + const current = s2.pop(); + if (abortCb && abortCb()) break; + if (current) { + cb(current); + } + } + } + /** * Traverses the tree post-order (left-right-node) * @public @@ -431,15 +852,9 @@ class BinarySearchTree { throw new Error('.traversePostOrder expects a callback function'); } - const traverseRecursive = (current) => { - if (current === null || (abortCb && abortCb())) return; - traverseRecursive(current.getLeft()); - traverseRecursive(current.getRight()); - if (abortCb && abortCb()) return; - cb(current); - }; - - traverseRecursive(this._root); + return this._iterative + ? this._traversePostOrderIterative(cb, abortCb) + : this._traversePostOrderRecursive(cb, abortCb); } /** diff --git a/test/avlTree.iterative.test.js b/test/avlTree.iterative.test.js new file mode 100644 index 0000000..c2e8fca --- /dev/null +++ b/test/avlTree.iterative.test.js @@ -0,0 +1,647 @@ +const { expect } = require('chai'); +const { AvlTree } = require('../src/avlTree'); + +describe('AvlTree tests (iterative implementation)', () => { + const avlTree = new AvlTree(null, { iterative: true }); + + describe('.insert(value)', () => { + it('left rotation balancing', () => { + avlTree.insert(50); + avlTree.insert(80); + avlTree.insert(90); + /* + 50 (balance = -2) + \ + 80 + \ + 90 + + lef-rotation of 50 to ==> + + 80 + / \ + 50 90 + */ + const root = avlTree.root(); + expect(root.getValue()).to.equal(80); + + expect(root.getRight().getValue()).to.equal(90); + expect(root.getRight().getParent().getValue()).to.equal(80); + + expect(root.getLeft().getValue()).to.equal(50); + expect(root.getLeft().getParent().getValue()).to.equal(80); + }); + + it('right rotation balancing', () => { + avlTree.insert(40); + avlTree.insert(30); + + /* + 80 + / \ + (balance = 2) 50 90 + / + 40 + / + 30 + + right-rotation of 50 to ==> + + 80 + / \ + 40 90 + / \ + 30 50 + */ + const root = avlTree.root(); + expect(root.getValue()).to.equal(80); + + expect(root.getRight().getValue()).to.equal(90); + expect(root.getRight().getParent().getValue()).to.equal(80); + + expect(root.getLeft().getValue()).to.equal(40); + expect(root.getLeft().getParent().getValue()).to.equal(80); + + expect(root.getLeft().getRight().getValue()).to.equal(50); + expect(root.getLeft().getRight().getParent().getValue()).to.equal(40); + + expect(root.getLeft().getLeft().getValue()).to.equal(30); + expect(root.getLeft().getLeft().getParent().getValue()).to.equal(40); + + avlTree.insert(20); + /* + 80 (balance = 2) + / \ + 40 90 + / \ + 30 50 + / + 20 + + right-rotation of 80 ==> + + 40 + / \ + 30 80 + / / \ + 20 50 90 + */ + expect(avlTree.root().getValue()).to.equal(40); + + expect(avlTree.root().getLeft().getValue()).to.equal(30); + expect(avlTree.root().getLeft().getLeft().getValue()).to.equal(20); + + expect(avlTree.root().getRight().getValue()).to.equal(80); + expect(avlTree.root().getRight().getRight().getValue()).to.equal(90); + expect(avlTree.root().getRight().getLeft().getValue()).to.equal(50); + }); + + it('left-right rotation balancing', () => { + avlTree.insert(35); + avlTree.insert(10); + avlTree.insert(15); + /* + 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.getValue()).to.equal(40); + + expect(root.getRight().getValue()).to.equal(80); + expect(root.getRight().getRight().getValue()).to.equal(90); + expect(root.getRight().getLeft().getValue()).to.equal(50); + + expect(root.getLeft().getValue()).to.equal(30); + expect(root.getLeft().getRight().getValue()).to.equal(35); + expect(root.getLeft().getLeft().getValue()).to.equal(15); + expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); + expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); + }); + + it('right-left rotation balancing', () => { + avlTree.insert(100); + avlTree.insert(95); + /* + 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.getValue()).to.equal(40); + + expect(root.getRight().getValue()).to.equal(80); + expect(root.getRight().getRight().getValue()).to.equal(95); + expect(root.getRight().getRight().getRight().getValue()).to.equal(100); + expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); + expect(root.getRight().getLeft().getValue()).to.equal(50); + + expect(root.getLeft().getValue()).to.equal(30); + expect(root.getLeft().getRight().getValue()).to.equal(35); + expect(root.getLeft().getLeft().getValue()).to.equal(15); + expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); + expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); + }); + + it('keep balance when inserting so many elements', () => { + const elements = [ + 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, + 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, + 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, + 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, + 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, + 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, + 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, + 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, + 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, + 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, + 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, + 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, + 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, + 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, + 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, + 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, + 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, + 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, + 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, + 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, + 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, + 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, + 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, + 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, + 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, + 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, + 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, + 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, + 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, + 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, + 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, + 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, + 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, + 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, + 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, + 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, + 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, + 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, + 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, + 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, + 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, + 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, + 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, + 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, + 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, + 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, + 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, + 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, + 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, + 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, + 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, + 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, + 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, + 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, + 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, + 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, + 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, + 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, + 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, + 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, + 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, + 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 + ]; + + const tree = new AvlTree(null, { iterative: true }); + elements.forEach((n) => tree.insert(n)); + tree.traversePreOrder((node) => { + const balance = node.getBalance(); + if (balance > 1 || balance < -1) { + throw new Error(`not balance , balance is ${balance}`); + } + }); + }); + }); + + describe('.min()', () => { + it('get the node with min value', () => { + expect(avlTree.min().getValue(15)); + }); + }); + + describe('.max()', () => { + it('get the node with min value', () => { + expect(avlTree.max().getValue(100)); + }); + }); + + describe('.root()', () => { + it('should get root node', () => { + expect(avlTree.root().getValue(40)); + }); + }); + + describe('.find(value)', () => { + it('find a node by its value', () => { + expect(avlTree.find(35).getValue()).to.equal(35); + expect(avlTree.find(1000)).to.equal(null); + }); + }); + + describe('.traverseInOrder(cb)', () => { + it('traverse the tree in order', () => { + const values = []; + avlTree.traverseInOrder((node) => values.push(node.getValue())); + expect(values).to.deep.equal([ + 10, 15, 20, 30, 35, 40, 50, 80, 90, 95, 100 + ]); + }); + }); + + describe('.traversePreOrder(cb)', () => { + it('traverse the tree in order', () => { + const values = []; + avlTree.traversePreOrder((node) => values.push(node.getValue())); + expect(values).to.deep.equal([ + 40, 30, 15, 10, 20, 35, 80, 50, 95, 90, 100 + ]); + }); + }); + + describe('.traversePostOrder(cb)', () => { + it('traverse the tree post order', () => { + const values = []; + avlTree.traversePostOrder((node) => values.push(node.getValue())); + expect(values).to.deep.equal([ + 10, 20, 15, 35, 30, 50, 90, 100, 95, 80, 40 + ]); + }); + }); + + describe('.remove(value)', () => { + 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.getValue()).to.equal(40); + expect(root.getRight().getValue()).to.equal(80); + expect(root.getRight().getRight().getValue()).to.equal(95); + expect(root.getRight().getRight().getRight().getValue()).to.equal(100); + expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); + expect(root.getRight().getLeft().getValue()).to.equal(50); + expect(root.getLeft().getValue()).to.equal(15); + expect(root.getLeft().getRight().getValue()).to.equal(30); + expect(root.getLeft().getRight().getLeft().getValue()).to.equal(20); + expect(root.getLeft().getLeft().getValue()).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.getValue()).to.equal(40); + expect(root.getRight().getValue()).to.equal(80); + expect(root.getRight().getRight().getValue()).to.equal(95); + expect(root.getRight().getRight().getRight().getValue()).to.equal(100); + expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); + expect(root.getRight().getLeft().getValue()).to.equal(50); + expect(root.getLeft().getValue()).to.equal(20); + expect(root.getLeft().getRight().getValue()).to.equal(30); + expect(root.getLeft().getLeft().getValue()).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().getValue()).to.equal(40); + expect(avlTree.root().getRight().getValue()).to.equal(95); + expect(avlTree.root().getRight().getRight().getValue()).to.equal(100); + expect(avlTree.root().getRight().getLeft().getValue()).to.equal(80); + expect(avlTree.root().getLeft().getValue()).to.equal(20); + expect(avlTree.root().getLeft().getRight().getValue()).to.equal(30); + expect(avlTree.root().getLeft().getLeft().getValue()).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.getValue()).to.equal(40); + expect(root.getLeft().getValue()).to.equal(20); + expect(root.getLeft().getRight().getValue()).to.equal(30); + expect(root.getLeft().getLeft().getValue()).to.equal(15); + expect(root.getRight().getValue()).to.equal(85); + expect(root.getRight().getLeft().getValue()).to.equal(80); + expect(root.getRight().getRight().getValue()).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().getValue()).to.equal(20); + expect(avlTree.root().getLeft().getValue()).to.equal(15); + expect(avlTree.root().getRight().getValue()).to.equal(40); + + avlTree.remove(20); + expect(avlTree.root().getValue()).to.equal(40); + expect(avlTree.root().getLeft().getValue()).to.equal(15); + + avlTree.remove(40); + expect(avlTree.root().getValue()).to.equal(15); + expect(avlTree.count()).to.equal(1); + + avlTree.insert(20); + avlTree.remove(15); + expect(avlTree.root().getValue()).to.equal(20); + expect(avlTree.count()).to.equal(1); + avlTree.remove(20); + expect(avlTree.root()).to.equal(null); + expect(avlTree.count()).to.equal(0); + }); + + it('correctly removes a node with one child', () => { + function getAll(tree) { + const arr = []; + tree.traverseInOrder((n) => arr.push(n.getValue())); + return arr; + } + + const tree = new AvlTree(null, { iterative: true }); + tree.insert(3); + tree.insert(1); + tree.insert(5); + tree.insert(6); + + tree.remove(5); + + expect(getAll(tree)).to.deep.equal([1, 3, 6]); + }); + + it('keeps the tree balanced when removing only nodes with two children', () => { + const insertOrder = [7, 3, 11, 1, 5, 9, 13, 0, 2, 4, 6, 8, 10, 12, 14]; + const deleteOrder = [1, 5, 3, 4, 9, 13, 11, 12, 7, 8, 10]; + + const tree = new AvlTree(null, { iterative: true }); + insertOrder.forEach((n) => tree.insert(n)); + deleteOrder.forEach((n) => tree.remove(n)); + expect(tree.root().getBalance()).to.be.oneOf([-1, 0, 1]); + const elements = []; + tree.traverseInOrder((n) => elements.push(n.getValue())); + expect(elements).to.deep.equal([0, 2, 6, 14]); + }); + + it('correctly removes all nodes from a large tree', () => { + const elements = [ + 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, + 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, + 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, + 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, + 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, + 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, + 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, + 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, + 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, + 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, + 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, + 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, + 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, + 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, + 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, + 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, + 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, + 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, + 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, + 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, + 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, + 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, + 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, + 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, + 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, + 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, + 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, + 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, + 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, + 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, + 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, + 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, + 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, + 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, + 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, + 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, + 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, + 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, + 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, + 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, + 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, + 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, + 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, + 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, + 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, + 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, + 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, + 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, + 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, + 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, + 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, + 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, + 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, + 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, + 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, + 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, + 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, + 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, + 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, + 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, + 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, + 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 + ]; + + const tree = new AvlTree(null, { iterative: true }); + elements.forEach((n) => tree.insert(n)); + elements.forEach((n) => tree.remove(n)); + }); + }); + + describe('.removeNode(node)', () => { + const testRemoveTree = new AvlTree(null, { iterative: true }); + testRemoveTree + .insert(50) + .insert(80) + .insert(30) + .insert(90) + .insert(60) + .insert(40) + .insert(20); + const n80 = testRemoveTree.find(80); + testRemoveTree.removeNode(n80); + expect(testRemoveTree.root().getRight().getValue()).to.equal(90); + expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); + expect(testRemoveTree.root().getRight().getRight()).to.equal(null); + }); +}); diff --git a/test/avlTree.test.js b/test/avlTree.recursive.test.js similarity index 99% rename from test/avlTree.test.js rename to test/avlTree.recursive.test.js index f0bbe30..444f5fa 100644 --- a/test/avlTree.test.js +++ b/test/avlTree.recursive.test.js @@ -1,7 +1,7 @@ const { expect } = require('chai'); const { AvlTree } = require('../src/avlTree'); -describe('AvlTree tests', () => { +describe('AvlTree tests (recursive implementation)', () => { const avlTree = new AvlTree(); describe('.insert(value)', () => { diff --git a/test/binarySearchTree.iterative.test.js b/test/binarySearchTree.iterative.test.js new file mode 100644 index 0000000..ae6c126 --- /dev/null +++ b/test/binarySearchTree.iterative.test.js @@ -0,0 +1,308 @@ +const { expect } = require('chai'); +const { BinarySearchTreeNode } = require('../src/binarySearchTreeNode'); +const { BinarySearchTree } = require('../src/binarySearchTree'); + +describe('BinarySearchTree tests (iterative implementation)', () => { + const bst = new BinarySearchTree(null, { iterative: true }); + + describe('.insert(value)', () => { + it('should insert nodes to the tree', () => { + expect(bst.insert(50)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(80)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(30)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(90)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(60)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(40)).to.be.instanceof(BinarySearchTree); + expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); + + // updates value of existing node + expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); + expect(bst.find(20).getValue()).to.equal(20); + }); + }); + + describe('.root()', () => { + it('should get the root node', () => { + expect(bst.root().getValue()).to.equal(50); + expect(bst.root().getRight().getValue()).to.equal(80); + expect(bst.root().getLeft().getValue()).to.equal(30); + }); + }); + + describe('.count()', () => { + it('get the count of nodes in the tree', () => { + expect(bst.count()).to.be.equal(7); + }); + }); + + describe('.has(value)', () => { + it('checks if a node exists by value', () => { + expect(bst.has(50)).to.equal(true); + expect(bst.has(80)).to.equal(true); + expect(bst.has(30)).to.equal(true); + expect(bst.has(90)).to.equal(true); + expect(bst.has(50)).to.equal(true); + expect(bst.has(40)).to.equal(true); + expect(bst.has(20)).to.equal(true); + expect(bst.has(100)).to.equal(false); + }); + }); + + describe('.has(key)', () => { + it('checks if a node exists by key', () => { + const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); + testTree.insert({ id: 1, name: 'a' }); + testTree.insert({ id: 2, name: 'b' }); + testTree.insert({ id: 3, name: 'c' }); + expect(testTree.has({ id: 1 })).to.equal(true); + expect(testTree.has({ id: 2 })).to.equal(true); + expect(testTree.has({ id: 3 })).to.equal(true); + expect(testTree.hasKey(1)).to.equal(true); + expect(testTree.hasKey(2)).to.equal(true); + expect(testTree.hasKey(3)).to.equal(true); + }); + }); + + describe('.find(value)', () => { + it('should search a node by its value in the tree', () => { + expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(80)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(30)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(90)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(40)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(20)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(100)).to.equal(null); + }); + }); + + describe('.findKey(key)', () => { + it('should search a node by its key in the tree', () => { + const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); + testTree.insert({ id: 1, name: 'a' }); + testTree.insert({ id: 2, name: 'b' }); + testTree.insert({ id: 3, name: 'c' }); + expect(testTree.find({ id: 1 }).getValue()).to.eql({ id: 1, name: 'a' }); + expect(testTree.find({ id: 2 }).getValue()).to.eql({ id: 2, name: 'b' }); + expect(testTree.find({ id: 3 }).getValue()).to.eql({ id: 3, name: 'c' }); + expect(testTree.findKey(1).getValue()).to.eql({ id: 1, name: 'a' }); + expect(testTree.findKey(2).getValue()).to.eql({ id: 2, name: 'b' }); + expect(testTree.findKey(3).getValue()).to.eql({ id: 3, name: 'c' }); + }); + }); + + describe('.max()', () => { + it('get the node with max key', () => { + const max = bst.max(); + expect(max.getValue()).to.equal(90); + }); + }); + + describe('.min()', () => { + it('get the node with min key', () => { + const min = bst.min(); + expect(min.getValue()).to.equal(20); + }); + }); + + describe('.lowerBound(value)', () => { + it('gets the node with biggest key less or equal k', () => { + expect(bst.lowerBound(60).getValue()).to.equal(60); + expect(bst.lowerBound(60, false).getValue()).to.equal(50); + }); + + it('returns null when k is less than all tree keys', () => { + expect(bst.lowerBound(10)).to.equal(null); + }); + + it('returns the biggest lower bound of multiple lower bounds', () => { + const lowerBst = new BinarySearchTree(null, { iterative: true }); + lowerBst.insert(20); + lowerBst.insert(7); + lowerBst.insert(15); + lowerBst.insert(9); + expect(lowerBst.floor(10).getValue()).to.equal(9); + }); + }); + + describe('.lowerBoundKey(key) / floorKey', () => { + it('gets the node with biggest key less or equal k', () => { + const lowerBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); + lowerBst.insert({ id: 20 }); + lowerBst.insert({ id: 7 }); + lowerBst.insert({ id: 15 }); + lowerBst.insert({ id: 9 }); + expect(lowerBst.lowerBoundKey(60).getValue()).to.eql({ id: 20 }); + expect(lowerBst.floorKey(20, false).getValue()).to.eql({ id: 15 }); + }); + }); + + describe('.upperBound(k)', () => { + it('gets the node with smallest key bigger than a key', () => { + expect(bst.upperBound(75).getValue()).to.equal(80); + expect(bst.upperBound(80).getValue()).to.equal(80); + expect(bst.upperBound(80, false).getValue()).to.equal(90); + }); + + it('returns null when k is bigger than all tree keys', () => { + expect(bst.upperBound(110)).to.equal(null); + }); + + it('returns the smallest upper bound of multiple upper bounds', () => { + const upperBst = new BinarySearchTree(null, { iterative: true }); + upperBst.insert(-133195046); + upperBst.insert(-49109668); + upperBst.insert(115062875); + upperBst.insert(-38206732); + upperBst.insert(49311742); + expect(upperBst.ceil(49303013).getValue()).to.equal(49311742); + }); + }); + + describe('.upperBoundKey(key) / ceilKey', () => { + it('gets the node with smallest key bigger than a key', () => { + const upperBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); + upperBst.insert({ id: 20 }); + upperBst.insert({ id: 7 }); + upperBst.insert({ id: 15 }); + upperBst.insert({ id: 9 }); + expect(upperBst.upperBoundKey(15).getValue()).to.eql({ id: 15 }); + expect(upperBst.ceilKey(15, false).getValue()).to.eql({ id: 20 }); + }); + }); + + describe('.traverseInOrder(cb)', () => { + it('traverse the tree in-order', () => { + const keys = []; + bst.traverseInOrder((node) => keys.push(node.getValue())); + expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); + }); + + it('traverse in order and allow aborting traversal', () => { + const keys = []; + let counter = 0; + bst.traverseInOrder((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([20, 30, 40]); + }); + }); + + describe('.traversePreOrder(cb)', () => { + it('traverse the tree pre-order', () => { + const keys = []; + bst.traversePreOrder((node) => keys.push(node.getValue())); + expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); + }); + + it('traverse pre order and allow aborting traversal', () => { + const keys = []; + let counter = 0; + bst.traversePreOrder((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([50, 30, 20]); + }); + }); + + describe('.traversePostOrder(cb)', () => { + it('traverse the tree post-order', () => { + const keys = []; + bst.traversePostOrder((node) => keys.push(node.getValue())); + expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); + }); + + it('traverse post order and allow aborting traversal', () => { + const keys = []; + let counter = 0; + bst.traversePostOrder((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([20, 40, 30]); + }); + }); + + describe('.remove(value)', () => { + it('should remove a leaf node', () => { + bst.remove(20); + expect(bst.has(20)).to.equal(false); + expect(bst.find(30).getLeft()).to.equal(null); + expect(bst.count()).to.equal(6); + }); + + it('should remove a node with a right child only', () => { + bst.remove(30); + expect(bst.has(30)).to.equal(false); + expect(bst.root().getLeft().getValue()).to.equal(40); + expect(bst.count()).to.equal(5); + }); + + it('should remove a node with a left child only', () => { + bst.insert(30); + bst.remove(40); + expect(bst.has(40)).to.equal(false); + expect(bst.root().getLeft().getValue()).to.equal(30); + expect(bst.count()).to.equal(5); + }); + + it('should remove a node with two children', () => { + bst.remove(80); + expect(bst.has(80)).to.equal(false); + expect(bst.root().getRight().getValue()).to.equal(90); + expect(bst.find(90).getRight()).to.equal(null); + expect(bst.find(90).getLeft().getValue()).to.equal(60); + expect(bst.count()).to.equal(4); + }); + + it('should remove root node with right child', () => { + bst.insert(100); + bst.remove(60); + bst.remove(90); + bst.remove(30); + bst.remove(50); + expect(bst.root().getValue()).to.equal(100); + }); + + it('should remove root node with left child', () => { + bst.insert(20); + bst.insert(30); + bst.insert(25); + bst.remove(30); + bst.remove(25); + bst.remove(100); + expect(bst.root().getValue()).to.equal(20); + }); + + it('should remove root node', () => { + bst.remove(20); + expect(bst.root()).to.equal(null); + }); + }); + + describe('.removeNode(node)', () => { + const testRemoveTree = new BinarySearchTree(null, { iterative: true }); + testRemoveTree + .insert(50) + .insert(80) + .insert(30) + .insert(90) + .insert(60) + .insert(40) + .insert(20); + const n80 = testRemoveTree.find(80); + testRemoveTree.removeNode(n80); + expect(testRemoveTree.root().getRight().getValue()).to.equal(90); + expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); + expect(testRemoveTree.root().getRight().getRight()).to.equal(null); + }); + + describe('.clear()', () => { + bst.clear(); + expect(bst.count()).to.equal(0); + expect(bst.root()).to.equal(null); + expect(bst.remove(10)).to.equal(false); + }); +}); diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.recursive.test.js similarity index 99% rename from test/binarySearchTree.test.js rename to test/binarySearchTree.recursive.test.js index b9685e9..ede1c10 100644 --- a/test/binarySearchTree.test.js +++ b/test/binarySearchTree.recursive.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 (recursive implementation)', () => { const bst = new BinarySearchTree(); describe('.insert(value)', () => { From 916ac97a0f49fb79e0aeb605f71a52d9e5c1a1d7 Mon Sep 17 00:00:00 2001 From: eyas-ranjous Date: Sun, 19 Oct 2025 20:23:15 -0700 Subject: [PATCH 2/3] update --- src/avlTree.js | 44 +--- src/binarySearchTree.js | 251 ++++++----------------- test/avlTree.iterative.test.js | 102 +++++----- test/binarySearchTree.iterative.test.js | 254 ++++++++++++------------ 4 files changed, 250 insertions(+), 401 deletions(-) diff --git a/src/avlTree.js b/src/avlTree.js index 91804e8..317570c 100644 --- a/src/avlTree.js +++ b/src/avlTree.js @@ -70,11 +70,11 @@ class AvlTree extends BinarySearchTree { * Inserts a value into the tree and maintains * the tree balanced by making the necessary rotations (recursive implementation) * - * @private + * @public * @param {number|string|object} value * @return {AvlTree} */ - _insertRecursive(value) { + insert(value) { const newNode = new AvlTreeNode(value, this._compare); const insertRecursive = (current) => { const compare = this._compare(value, current.getValue()); @@ -115,11 +115,11 @@ class AvlTree extends BinarySearchTree { * Inserts a value into the tree and maintains * the tree balanced by making the necessary rotations (iterative implementation) * - * @private + * @public * @param {number|string|object} value * @return {AvlTree} */ - _insertIterative(value) { + insertIterative(value) { const newNode = new AvlTreeNode(value, this._compare); if (this._root === null) { @@ -169,29 +169,15 @@ class AvlTree extends BinarySearchTree { return this; } - /** - * Inserts a value into the tree and maintains - * the tree balanced by making the necessary rotations - * - * @public - * @param {number|string|object} value - * @return {AvlTree} - */ - insert(value) { - return this._iterative - ? this._insertIterative(value) - : this._insertRecursive(value); - } - /** * Removes a node from the tree and maintains * the tree balanced by making the necessary rotations (recursive implementation) * - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _removeRecursive(value) { + remove(value) { const removeRecursively = (val, current) => { if (current === null) { return false; @@ -221,11 +207,11 @@ class AvlTree extends BinarySearchTree { * Removes a node from the tree and maintains * the tree balanced by making the necessary rotations (iterative implementation) * - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _removeIterative(value) { + removeIterative(value) { if (this._root === null) { return false; } @@ -261,20 +247,6 @@ class AvlTree extends BinarySearchTree { return removed; } - /** - * Removes a node from the tree and maintains - * the tree balanced by making the necessary rotations - * - * @public - * @param {number|string|object} value - * @return {boolean} - */ - remove(value) { - return this._iterative - ? this._removeIterative(value) - : this._removeRecursive(value); - } - /** * Removes a node from the tree * @public diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 42ad289..e5576ce 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -22,18 +22,17 @@ class BinarySearchTree { this._compare = compare || defaultCompare; this._options = options || {}; - this._iterative = this._options.iterative || false; this._root = null; this._count = 0; } /** * Inserts a node with a key/value into the tree (recursive implementation) - * @private + * @public * @param {number|string|object} value * @return {BinarySearchTree} */ - _insertRecursive(value) { + insert(value) { const newNode = new BinarySearchTreeNode(value); const insertRecursive = (current) => { const compare = this._compare(newNode.getValue(), current.getValue()); @@ -68,11 +67,11 @@ class BinarySearchTree { /** * Inserts a node with a key/value into the tree (iterative implementation) - * @private + * @public * @param {number|string|object} value * @return {BinarySearchTree} */ - _insertIterative(value) { + insertIterative(value) { const newNode = new BinarySearchTreeNode(value); let node = this._root; if (!node) { @@ -108,25 +107,13 @@ class BinarySearchTree { return this; } - /** - * Inserts a node with a key/value into the tree - * @public - * @param {number|string|object} value - * @return {BinarySearchTree} - */ - insert(value) { - return this._iterative - ? this._insertIterative(value) - : this._insertRecursive(value); - } - /** * Checks if a value exists in the tree by its value (recursive implementation) - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _hasRecursive(value) { + has(value) { const hasRecursive = (current) => { if (current === null) return false; @@ -141,11 +128,11 @@ class BinarySearchTree { /** * Checks if a value exists in the tree by its value (iterative implementation) - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _hasIterative(value) { + hasIterative(value) { let current = this._root; while (current !== null) { const compare = this._compare(value, current.getValue()); @@ -162,18 +149,6 @@ class BinarySearchTree { return false; } - /** - * Checks if a value exists in the tree by its value - * @public - * @param {number|string|object} value - * @return {boolean} - */ - has(value) { - return this._iterative - ? this._hasIterative(value) - : this._hasRecursive(value); - } - /** * Checks if a value exists in the tree by its key * @public @@ -189,11 +164,11 @@ class BinarySearchTree { /** * Finds a node by its value (recursive implementation) - * @private + * @public * @param {number|string|object} value * @return {BinarySearchTreeNode} */ - _findRecursive(value) { + find(value) { const findRecursive = (current) => { if (current === null) return null; @@ -208,11 +183,11 @@ class BinarySearchTree { /** * Finds a node by its value (iterative implementation) - * @private + * @public * @param {number|string|object} value * @return {BinarySearchTreeNode} */ - _findIterative(value) { + findIterative(value) { let current = this._root; while (current !== null) { @@ -231,18 +206,6 @@ class BinarySearchTree { return null; } - /** - * Finds a node by its value - * @public - * @param {number|string|object} value - * @return {BinarySearchTreeNode} - */ - find(value) { - return this._iterative - ? this._findIterative(value) - : this._findRecursive(value); - } - /** * Finds a node by its object's key * @public @@ -258,23 +221,23 @@ class BinarySearchTree { /** * Finds the node with max key (most right) in the tree (recursive implementation) - * @private + * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} */ - _maxRecursive(current = this._root) { + max(current = this._root) { if (current === null) return null; - if (current.hasRight()) return this._maxRecursive(current.getRight()); + if (current.hasRight()) return this.max(current.getRight()); return current; } /** * Finds the node with max key (most right) in the tree (iterative implementation) - * @private + * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} */ - _maxIterative(current = this._root) { + maxIterative(current = this._root) { if (current === null) return null; let node = current; while (node.hasRight()) { @@ -283,37 +246,25 @@ class BinarySearchTree { return node; } - /** - * Finds the node with max key (most right) in the tree - * @public - * @param {BinarySearchTreeNode} [current] - * @return {BinarySearchTreeNode} - */ - max(current = this._root) { - return this._iterative - ? this._maxIterative(current) - : this._maxRecursive(current); - } - /** * Finds the node with min key (most left) in the tree (recursive implementation) - * @private + * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} */ - _minRecursive(current = this._root) { + min(current = this._root) { if (current === null) return null; - if (current.hasLeft()) return this._minRecursive(current.getLeft()); + if (current.hasLeft()) return this.min(current.getLeft()); return current; } /** * Finds the node with min key (most left) in the tree (iterative implementation) - * @private + * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} */ - _minIterative(current = this._root) { + minIterative(current = this._root) { if (current === null) return null; let node = current; while (node.hasLeft()) { @@ -322,26 +273,14 @@ class BinarySearchTree { return node; } - /** - * Finds the node with min key (most left) in the tree - * @public - * @param {BinarySearchTreeNode} [current] - * @return {BinarySearchTreeNode} - */ - min(current = this._root) { - return this._iterative - ? this._minIterative(current) - : this._minRecursive(current); - } - /** * Returns the node with the biggest value less or equal a given value (recursive implementation) - * @private + * @public * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - _lowerBoundRecursive(value, includeEqual = true) { + lowerBound(value, includeEqual = true) { let lowerBound = null; const lowerBoundRecursive = (current) => { @@ -363,12 +302,12 @@ class BinarySearchTree { /** * Returns the node with the biggest value less or equal a given value (iterative implementation) - * @private + * @public * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - _lowerBoundIterative(value, includeEqual = true) { + lowerBoundIterative(value, includeEqual = true) { let lowerBound = null; let current = this._root; @@ -388,19 +327,6 @@ class BinarySearchTree { return lowerBound; } - /** - * Returns the node with the biggest value less or equal a given value - * @public - * @param {number|string|object} value - * @param {boolean} includeEqual - * @return {BinarySearchTreeNode|null} - */ - lowerBound(value, includeEqual = true) { - return this._iterative - ? this._lowerBoundIterative(value, includeEqual) - : this._lowerBoundRecursive(value, includeEqual); - } - /** * Returns the node with the biggest object's key less or equal a given key * @public @@ -440,12 +366,12 @@ class BinarySearchTree { /** * Returns the node with the smallest value greater or equal a given value (recursive implementation) - * @private + * @public * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - _upperBoundRecursive(value, includeEqual = true) { + upperBound(value, includeEqual = true) { let upperBound = null; const upperBoundRecursive = (current) => { @@ -467,12 +393,12 @@ class BinarySearchTree { /** * Returns the node with the smallest value greater or equal a given value (iterative implementation) - * @private + * @public * @param {number|string|object} value * @param {boolean} includeEqual * @return {BinarySearchTreeNode|null} */ - _upperBoundIterative(value, includeEqual = true) { + upperBoundIterative(value, includeEqual = true) { let upperBound = null; let current = this._root; @@ -492,19 +418,6 @@ class BinarySearchTree { return upperBound; } - /** - * Returns the node with the smallest value greater or equal a given value - * @public - * @param {number|string|object} value - * @param {boolean} includeEqual - * @return {BinarySearchTreeNode|null} - */ - upperBound(value, includeEqual = true) { - return this._iterative - ? this._upperBoundIterative(value, includeEqual) - : this._upperBoundRecursive(value, includeEqual); - } - /** * Returns the node with the smallest object's key greater or equal a given key * @public @@ -562,11 +475,11 @@ class BinarySearchTree { /** * Removes a node by its value (recursive implementation) - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _removeRecursive(value) { + remove(value) { const removeRecursively = (val, current) => { if (current === null) return false; @@ -582,11 +495,11 @@ class BinarySearchTree { /** * Removes a node by its value (iterative implementation) - * @private + * @public * @param {number|string|object} value * @return {boolean} */ - _removeIterative(value) { + removeIterative(value) { let current = this._root; while (current !== null) { @@ -606,18 +519,6 @@ class BinarySearchTree { return false; } - /** - * Removes a node by its value - * @public - * @param {number|string|object} value - * @return {boolean} - */ - remove(value) { - return this._iterative - ? this._removeIterative(value) - : this._removeRecursive(value); - } - /** * Removes a node from the tree * @public @@ -678,11 +579,15 @@ class BinarySearchTree { /** * Traverses the tree in-order (left-node-right) (recursive implementation) - * @private + * @public * @param {function} cb * @param {function} [abortCb] */ - _traverseInOrderRecursive(cb, abortCb) { + traverseInOrder(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traverseInOrder expects a callback function'); + } + const traverseRecursive = (current) => { if (current === null || (abortCb && abortCb())) return; traverseRecursive(current.getLeft()); @@ -696,11 +601,15 @@ class BinarySearchTree { /** * Traverses the tree in-order (left-node-right) (iterative implementation) - * @private + * @public * @param {function} cb * @param {function} [abortCb] */ - _traverseInOrderIterative(cb, abortCb) { + traverseInOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traverseInOrderIterative expects a callback function'); + } + let current = this._root; const stack = []; @@ -722,28 +631,16 @@ class BinarySearchTree { } /** - * Traverses the tree in-order (left-node-right) + * Traverses the tree pre-order (node-left-right) (recursive implementation) * @public * @param {function} cb * @param {function} [abortCb] */ - traverseInOrder(cb, abortCb) { + traversePreOrder(cb, abortCb) { if (typeof cb !== 'function') { - throw new Error('.traverseInOrder expects a callback function'); + throw new Error('.traversePreOrder expects a callback function'); } - return this._iterative - ? this._traverseInOrderIterative(cb, abortCb) - : this._traverseInOrderRecursive(cb, abortCb); - } - - /** - * Traverses the tree pre-order (node-left-right) (recursive implementation) - * @private - * @param {function} cb - * @param {function} [abortCb] - */ - _traversePreOrderRecursive(cb, abortCb) { const traverseRecursive = (current) => { if (current === null || (abortCb && abortCb())) return; cb(current); @@ -756,11 +653,15 @@ class BinarySearchTree { /** * Traverses the tree pre-order (node-left-right) (iterative implementation) - * @private + * @public * @param {function} cb * @param {function} [abortCb] */ - _traversePreOrderIterative(cb, abortCb) { + traversePreOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traversePreOrderIterative expects a callback function'); + } + const stack = [this._root]; while (stack.length) { @@ -776,28 +677,16 @@ class BinarySearchTree { } /** - * Traverses the tree pre-order (node-left-right) + * Traverses the tree post-order (left-right-node) (recursive implementation) * @public * @param {function} cb * @param {function} [abortCb] */ - traversePreOrder(cb, abortCb) { + traversePostOrder(cb, abortCb) { if (typeof cb !== 'function') { - throw new Error('.traversePreOrder expects a callback function'); + throw new Error('.traversePostOrder expects a callback function'); } - return this._iterative - ? this._traversePreOrderIterative(cb, abortCb) - : this._traversePreOrderRecursive(cb, abortCb); - } - - /** - * Traverses the tree post-order (left-right-node) (recursive implementation) - * @private - * @param {function} cb - * @param {function} [abortCb] - */ - _traversePostOrderRecursive(cb, abortCb) { const traverseRecursive = (current) => { if (current === null || (abortCb && abortCb())) return; traverseRecursive(current.getLeft()); @@ -811,11 +700,15 @@ class BinarySearchTree { /** * Traverses the tree post-order (left-right-node) (iterative implementation) - * @private + * @public * @param {function} cb * @param {function} [abortCb] */ - _traversePostOrderIterative(cb, abortCb) { + traversePostOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traversePostOrderIterative expects a callback function'); + } + const s1 = []; const s2 = []; s1.push(this._root); @@ -841,22 +734,6 @@ class BinarySearchTree { } } - /** - * Traverses the tree post-order (left-right-node) - * @public - * @param {function} cb - * @param {function} [abortCb] - */ - traversePostOrder(cb, abortCb) { - if (typeof cb !== 'function') { - throw new Error('.traversePostOrder expects a callback function'); - } - - return this._iterative - ? this._traversePostOrderIterative(cb, abortCb) - : this._traversePostOrderRecursive(cb, abortCb); - } - /** * Clears the tree * @public diff --git a/test/avlTree.iterative.test.js b/test/avlTree.iterative.test.js index c2e8fca..22c61bd 100644 --- a/test/avlTree.iterative.test.js +++ b/test/avlTree.iterative.test.js @@ -2,13 +2,13 @@ const { expect } = require('chai'); const { AvlTree } = require('../src/avlTree'); describe('AvlTree tests (iterative implementation)', () => { - const avlTree = new AvlTree(null, { iterative: true }); + const avlTree = new AvlTree(); - describe('.insert(value)', () => { + describe('.insertIterative(value)', () => { it('left rotation balancing', () => { - avlTree.insert(50); - avlTree.insert(80); - avlTree.insert(90); + avlTree.insertIterative(50); + avlTree.insertIterative(80); + avlTree.insertIterative(90); /* 50 (balance = -2) \ @@ -33,8 +33,8 @@ describe('AvlTree tests (iterative implementation)', () => { }); it('right rotation balancing', () => { - avlTree.insert(40); - avlTree.insert(30); + avlTree.insertIterative(40); + avlTree.insertIterative(30); /* 80 @@ -68,7 +68,7 @@ describe('AvlTree tests (iterative implementation)', () => { expect(root.getLeft().getLeft().getValue()).to.equal(30); expect(root.getLeft().getLeft().getParent().getValue()).to.equal(40); - avlTree.insert(20); + avlTree.insertIterative(20); /* 80 (balance = 2) / \ @@ -97,9 +97,9 @@ describe('AvlTree tests (iterative implementation)', () => { }); it('left-right rotation balancing', () => { - avlTree.insert(35); - avlTree.insert(10); - avlTree.insert(15); + avlTree.insertIterative(35); + avlTree.insertIterative(10); + avlTree.insertIterative(15); /* verify left-right rotation 40 @@ -136,8 +136,8 @@ describe('AvlTree tests (iterative implementation)', () => { }); it('right-left rotation balancing', () => { - avlTree.insert(100); - avlTree.insert(95); + avlTree.insertIterative(100); + avlTree.insertIterative(95); /* verify right-left rotation 40 @@ -242,8 +242,8 @@ describe('AvlTree tests (iterative implementation)', () => { 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 ]; - const tree = new AvlTree(null, { iterative: true }); - elements.forEach((n) => tree.insert(n)); + const tree = new AvlTree(); + elements.forEach((n) => tree.insertIterative(n)); tree.traversePreOrder((node) => { const balance = node.getBalance(); if (balance > 1 || balance < -1) { @@ -320,7 +320,7 @@ describe('AvlTree tests (iterative implementation)', () => { 10 20 90 100 */ - avlTree.remove(35); + avlTree.removeIterative(35); /* 40 @@ -365,7 +365,7 @@ describe('AvlTree tests (iterative implementation)', () => { 20 90 100 */ - avlTree.remove(10); + avlTree.removeIterative(10); /* 40 @@ -410,8 +410,8 @@ describe('AvlTree tests (iterative implementation)', () => { 90 100 */ - avlTree.remove(90); - avlTree.remove(50); + avlTree.removeIterative(90); + avlTree.removeIterative(50); /* 40 @@ -440,7 +440,7 @@ describe('AvlTree tests (iterative implementation)', () => { }); it('left-right rotation balancing', () => { - avlTree.insert(85); + avlTree.insertIterative(85); /* 40 / \ @@ -451,7 +451,7 @@ describe('AvlTree tests (iterative implementation)', () => { 85 */ - avlTree.remove(100); + avlTree.removeIterative(100); /* 40 / \ @@ -487,10 +487,10 @@ describe('AvlTree tests (iterative implementation)', () => { / \ / \ 15 30 80 95 */ - avlTree.remove(30); - avlTree.remove(80); - avlTree.remove(95); - avlTree.remove(85); + avlTree.removeIterative(30); + avlTree.removeIterative(80); + avlTree.removeIterative(95); + avlTree.removeIterative(85); /* 40 (balance = 2) / @@ -508,19 +508,19 @@ describe('AvlTree tests (iterative implementation)', () => { expect(avlTree.root().getLeft().getValue()).to.equal(15); expect(avlTree.root().getRight().getValue()).to.equal(40); - avlTree.remove(20); + avlTree.removeIterative(20); expect(avlTree.root().getValue()).to.equal(40); expect(avlTree.root().getLeft().getValue()).to.equal(15); - avlTree.remove(40); + avlTree.removeIterative(40); expect(avlTree.root().getValue()).to.equal(15); expect(avlTree.count()).to.equal(1); - avlTree.insert(20); - avlTree.remove(15); + avlTree.insertIterative(20); + avlTree.removeIterative(15); expect(avlTree.root().getValue()).to.equal(20); expect(avlTree.count()).to.equal(1); - avlTree.remove(20); + avlTree.removeIterative(20); expect(avlTree.root()).to.equal(null); expect(avlTree.count()).to.equal(0); }); @@ -532,13 +532,13 @@ describe('AvlTree tests (iterative implementation)', () => { return arr; } - const tree = new AvlTree(null, { iterative: true }); - tree.insert(3); - tree.insert(1); - tree.insert(5); - tree.insert(6); + const tree = new AvlTree(); + tree.insertIterative(3); + tree.insertIterative(1); + tree.insertIterative(5); + tree.insertIterative(6); - tree.remove(5); + tree.removeIterative(5); expect(getAll(tree)).to.deep.equal([1, 3, 6]); }); @@ -547,9 +547,9 @@ describe('AvlTree tests (iterative implementation)', () => { const insertOrder = [7, 3, 11, 1, 5, 9, 13, 0, 2, 4, 6, 8, 10, 12, 14]; const deleteOrder = [1, 5, 3, 4, 9, 13, 11, 12, 7, 8, 10]; - const tree = new AvlTree(null, { iterative: true }); - insertOrder.forEach((n) => tree.insert(n)); - deleteOrder.forEach((n) => tree.remove(n)); + const tree = new AvlTree(); + insertOrder.forEach((n) => tree.insertIterative(n)); + deleteOrder.forEach((n) => tree.removeIterative(n)); expect(tree.root().getBalance()).to.be.oneOf([-1, 0, 1]); const elements = []; tree.traverseInOrder((n) => elements.push(n.getValue())); @@ -622,23 +622,23 @@ describe('AvlTree tests (iterative implementation)', () => { 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 ]; - const tree = new AvlTree(null, { iterative: true }); - elements.forEach((n) => tree.insert(n)); - elements.forEach((n) => tree.remove(n)); + const tree = new AvlTree(); + elements.forEach((n) => tree.insertIterative(n)); + elements.forEach((n) => tree.removeIterative(n)); }); }); describe('.removeNode(node)', () => { - const testRemoveTree = new AvlTree(null, { iterative: true }); + const testRemoveTree = new AvlTree(); testRemoveTree - .insert(50) - .insert(80) - .insert(30) - .insert(90) - .insert(60) - .insert(40) - .insert(20); - const n80 = testRemoveTree.find(80); + .insertIterative(50) + .insertIterative(80) + .insertIterative(30) + .insertIterative(90) + .insertIterative(60) + .insertIterative(40) + .insertIterative(20); + const n80 = testRemoveTree.findIterative(80); testRemoveTree.removeNode(n80); expect(testRemoveTree.root().getRight().getValue()).to.equal(90); expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); diff --git a/test/binarySearchTree.iterative.test.js b/test/binarySearchTree.iterative.test.js index ae6c126..ecb4113 100644 --- a/test/binarySearchTree.iterative.test.js +++ b/test/binarySearchTree.iterative.test.js @@ -3,21 +3,21 @@ const { BinarySearchTreeNode } = require('../src/binarySearchTreeNode'); const { BinarySearchTree } = require('../src/binarySearchTree'); describe('BinarySearchTree tests (iterative implementation)', () => { - const bst = new BinarySearchTree(null, { iterative: true }); + const bst = new BinarySearchTree(); - describe('.insert(value)', () => { + describe('.insertIterative(value)', () => { it('should insert nodes to the tree', () => { - expect(bst.insert(50)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(80)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(30)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(90)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(60)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(40)).to.be.instanceof(BinarySearchTree); - expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(50)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(80)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(30)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(90)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(60)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(40)).to.be.instanceof(BinarySearchTree); + expect(bst.insertIterative(20)).to.be.instanceof(BinarySearchTree); // updates value of existing node - expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); - expect(bst.find(20).getValue()).to.equal(20); + expect(bst.insertIterative(20)).to.be.instanceof(BinarySearchTree); + expect(bst.findIterative(20).getValue()).to.equal(20); }); }); @@ -35,153 +35,153 @@ describe('BinarySearchTree tests (iterative implementation)', () => { }); }); - describe('.has(value)', () => { + describe('.hasIterative(value)', () => { it('checks if a node exists by value', () => { - expect(bst.has(50)).to.equal(true); - expect(bst.has(80)).to.equal(true); - expect(bst.has(30)).to.equal(true); - expect(bst.has(90)).to.equal(true); - expect(bst.has(50)).to.equal(true); - expect(bst.has(40)).to.equal(true); - expect(bst.has(20)).to.equal(true); - expect(bst.has(100)).to.equal(false); + expect(bst.hasIterative(50)).to.equal(true); + expect(bst.hasIterative(80)).to.equal(true); + expect(bst.hasIterative(30)).to.equal(true); + expect(bst.hasIterative(90)).to.equal(true); + expect(bst.hasIterative(50)).to.equal(true); + expect(bst.hasIterative(40)).to.equal(true); + expect(bst.hasIterative(20)).to.equal(true); + expect(bst.hasIterative(100)).to.equal(false); }); }); - describe('.has(key)', () => { + describe('.hasIterative(key)', () => { it('checks if a node exists by key', () => { - const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); - testTree.insert({ id: 1, name: 'a' }); - testTree.insert({ id: 2, name: 'b' }); - testTree.insert({ id: 3, name: 'c' }); - expect(testTree.has({ id: 1 })).to.equal(true); - expect(testTree.has({ id: 2 })).to.equal(true); - expect(testTree.has({ id: 3 })).to.equal(true); + const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); + testTree.insertIterative({ id: 1, name: 'a' }); + testTree.insertIterative({ id: 2, name: 'b' }); + testTree.insertIterative({ id: 3, name: 'c' }); + expect(testTree.hasIterative({ id: 1 })).to.equal(true); + expect(testTree.hasIterative({ id: 2 })).to.equal(true); + expect(testTree.hasIterative({ id: 3 })).to.equal(true); expect(testTree.hasKey(1)).to.equal(true); expect(testTree.hasKey(2)).to.equal(true); expect(testTree.hasKey(3)).to.equal(true); }); }); - describe('.find(value)', () => { + describe('.findIterative(value)', () => { it('should search a node by its value in the tree', () => { - expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(80)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(30)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(90)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(40)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(20)).to.be.instanceof(BinarySearchTreeNode); - expect(bst.find(100)).to.equal(null); + expect(bst.findIterative(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(80)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(30)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(90)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(40)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(20)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.findIterative(100)).to.equal(null); }); }); - describe('.findKey(key)', () => { + describe('.findKeyIterative(key)', () => { it('should search a node by its key in the tree', () => { - const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); - testTree.insert({ id: 1, name: 'a' }); - testTree.insert({ id: 2, name: 'b' }); - testTree.insert({ id: 3, name: 'c' }); - expect(testTree.find({ id: 1 }).getValue()).to.eql({ id: 1, name: 'a' }); - expect(testTree.find({ id: 2 }).getValue()).to.eql({ id: 2, name: 'b' }); - expect(testTree.find({ id: 3 }).getValue()).to.eql({ id: 3, name: 'c' }); + const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); + testTree.insertIterative({ id: 1, name: 'a' }); + testTree.insertIterative({ id: 2, name: 'b' }); + testTree.insertIterative({ id: 3, name: 'c' }); + expect(testTree.findIterative({ id: 1 }).getValue()).to.eql({ id: 1, name: 'a' }); + expect(testTree.findIterative({ id: 2 }).getValue()).to.eql({ id: 2, name: 'b' }); + expect(testTree.findIterative({ id: 3 }).getValue()).to.eql({ id: 3, name: 'c' }); expect(testTree.findKey(1).getValue()).to.eql({ id: 1, name: 'a' }); expect(testTree.findKey(2).getValue()).to.eql({ id: 2, name: 'b' }); expect(testTree.findKey(3).getValue()).to.eql({ id: 3, name: 'c' }); }); }); - describe('.max()', () => { + describe('.maxIterative()', () => { it('get the node with max key', () => { - const max = bst.max(); + const max = bst.maxIterative(); expect(max.getValue()).to.equal(90); }); }); - describe('.min()', () => { + describe('.minIterative()', () => { it('get the node with min key', () => { - const min = bst.min(); + const min = bst.minIterative(); expect(min.getValue()).to.equal(20); }); }); - describe('.lowerBound(value)', () => { + describe('.lowerBoundIterative(value)', () => { it('gets the node with biggest key less or equal k', () => { - expect(bst.lowerBound(60).getValue()).to.equal(60); - expect(bst.lowerBound(60, false).getValue()).to.equal(50); + expect(bst.lowerBoundIterative(60).getValue()).to.equal(60); + expect(bst.lowerBoundIterative(60, false).getValue()).to.equal(50); }); it('returns null when k is less than all tree keys', () => { - expect(bst.lowerBound(10)).to.equal(null); + expect(bst.lowerBoundIterative(10)).to.equal(null); }); it('returns the biggest lower bound of multiple lower bounds', () => { - const lowerBst = new BinarySearchTree(null, { iterative: true }); - lowerBst.insert(20); - lowerBst.insert(7); - lowerBst.insert(15); - lowerBst.insert(9); - expect(lowerBst.floor(10).getValue()).to.equal(9); + const lowerBst = new BinarySearchTree(); + lowerBst.insertIterative(20); + lowerBst.insertIterative(7); + lowerBst.insertIterative(15); + lowerBst.insertIterative(9); + expect(lowerBst.lowerBoundIterative(10).getValue()).to.equal(9); }); }); - describe('.lowerBoundKey(key) / floorKey', () => { + describe('.lowerBoundKeyIterative(key) / floorKeyIterative', () => { it('gets the node with biggest key less or equal k', () => { - const lowerBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); - lowerBst.insert({ id: 20 }); - lowerBst.insert({ id: 7 }); - lowerBst.insert({ id: 15 }); - lowerBst.insert({ id: 9 }); + const lowerBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); + lowerBst.insertIterative({ id: 20 }); + lowerBst.insertIterative({ id: 7 }); + lowerBst.insertIterative({ id: 15 }); + lowerBst.insertIterative({ id: 9 }); expect(lowerBst.lowerBoundKey(60).getValue()).to.eql({ id: 20 }); expect(lowerBst.floorKey(20, false).getValue()).to.eql({ id: 15 }); }); }); - describe('.upperBound(k)', () => { + describe('.upperBoundIterative(k)', () => { it('gets the node with smallest key bigger than a key', () => { - expect(bst.upperBound(75).getValue()).to.equal(80); - expect(bst.upperBound(80).getValue()).to.equal(80); - expect(bst.upperBound(80, false).getValue()).to.equal(90); + expect(bst.upperBoundIterative(75).getValue()).to.equal(80); + expect(bst.upperBoundIterative(80).getValue()).to.equal(80); + expect(bst.upperBoundIterative(80, false).getValue()).to.equal(90); }); it('returns null when k is bigger than all tree keys', () => { - expect(bst.upperBound(110)).to.equal(null); + expect(bst.upperBoundIterative(110)).to.equal(null); }); it('returns the smallest upper bound of multiple upper bounds', () => { - const upperBst = new BinarySearchTree(null, { iterative: true }); - upperBst.insert(-133195046); - upperBst.insert(-49109668); - upperBst.insert(115062875); - upperBst.insert(-38206732); - upperBst.insert(49311742); - expect(upperBst.ceil(49303013).getValue()).to.equal(49311742); + const upperBst = new BinarySearchTree(); + upperBst.insertIterative(-133195046); + upperBst.insertIterative(-49109668); + upperBst.insertIterative(115062875); + upperBst.insertIterative(-38206732); + upperBst.insertIterative(49311742); + expect(upperBst.upperBoundIterative(49303013).getValue()).to.equal(49311742); }); }); - describe('.upperBoundKey(key) / ceilKey', () => { + describe('.upperBoundKeyIterative(key) / ceilKeyIterative', () => { it('gets the node with smallest key bigger than a key', () => { - const upperBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id', iterative: true }); - upperBst.insert({ id: 20 }); - upperBst.insert({ id: 7 }); - upperBst.insert({ id: 15 }); - upperBst.insert({ id: 9 }); + const upperBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); + upperBst.insertIterative({ id: 20 }); + upperBst.insertIterative({ id: 7 }); + upperBst.insertIterative({ id: 15 }); + upperBst.insertIterative({ id: 9 }); expect(upperBst.upperBoundKey(15).getValue()).to.eql({ id: 15 }); expect(upperBst.ceilKey(15, false).getValue()).to.eql({ id: 20 }); }); }); - describe('.traverseInOrder(cb)', () => { + describe('.traverseInOrderIterative(cb)', () => { it('traverse the tree in-order', () => { const keys = []; - bst.traverseInOrder((node) => keys.push(node.getValue())); + bst.traverseInOrderIterative((node) => keys.push(node.getValue())); expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); }); it('traverse in order and allow aborting traversal', () => { const keys = []; let counter = 0; - bst.traverseInOrder((node) => { + bst.traverseInOrderIterative((node) => { keys.push(node.getValue()); counter += 1; }, () => counter > 2); @@ -189,17 +189,17 @@ describe('BinarySearchTree tests (iterative implementation)', () => { }); }); - describe('.traversePreOrder(cb)', () => { + describe('.traversePreOrderIterative(cb)', () => { it('traverse the tree pre-order', () => { const keys = []; - bst.traversePreOrder((node) => keys.push(node.getValue())); + bst.traversePreOrderIterative((node) => keys.push(node.getValue())); expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); }); it('traverse pre order and allow aborting traversal', () => { const keys = []; let counter = 0; - bst.traversePreOrder((node) => { + bst.traversePreOrderIterative((node) => { keys.push(node.getValue()); counter += 1; }, () => counter > 2); @@ -207,17 +207,17 @@ describe('BinarySearchTree tests (iterative implementation)', () => { }); }); - describe('.traversePostOrder(cb)', () => { + describe('.traversePostOrderIterative(cb)', () => { it('traverse the tree post-order', () => { const keys = []; - bst.traversePostOrder((node) => keys.push(node.getValue())); + bst.traversePostOrderIterative((node) => keys.push(node.getValue())); expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); }); it('traverse post order and allow aborting traversal', () => { const keys = []; let counter = 0; - bst.traversePostOrder((node) => { + bst.traversePostOrderIterative((node) => { keys.push(node.getValue()); counter += 1; }, () => counter > 2); @@ -225,74 +225,74 @@ describe('BinarySearchTree tests (iterative implementation)', () => { }); }); - describe('.remove(value)', () => { + describe('.removeIterative(value)', () => { it('should remove a leaf node', () => { - bst.remove(20); - expect(bst.has(20)).to.equal(false); - expect(bst.find(30).getLeft()).to.equal(null); + bst.removeIterative(20); + expect(bst.hasIterative(20)).to.equal(false); + expect(bst.findIterative(30).getLeft()).to.equal(null); expect(bst.count()).to.equal(6); }); it('should remove a node with a right child only', () => { - bst.remove(30); - expect(bst.has(30)).to.equal(false); + bst.removeIterative(30); + expect(bst.hasIterative(30)).to.equal(false); expect(bst.root().getLeft().getValue()).to.equal(40); expect(bst.count()).to.equal(5); }); it('should remove a node with a left child only', () => { - bst.insert(30); - bst.remove(40); - expect(bst.has(40)).to.equal(false); + bst.insertIterative(30); + bst.removeIterative(40); + expect(bst.hasIterative(40)).to.equal(false); expect(bst.root().getLeft().getValue()).to.equal(30); expect(bst.count()).to.equal(5); }); it('should remove a node with two children', () => { - bst.remove(80); - expect(bst.has(80)).to.equal(false); + bst.removeIterative(80); + expect(bst.hasIterative(80)).to.equal(false); expect(bst.root().getRight().getValue()).to.equal(90); - expect(bst.find(90).getRight()).to.equal(null); - expect(bst.find(90).getLeft().getValue()).to.equal(60); + expect(bst.findIterative(90).getRight()).to.equal(null); + expect(bst.findIterative(90).getLeft().getValue()).to.equal(60); expect(bst.count()).to.equal(4); }); it('should remove root node with right child', () => { - bst.insert(100); - bst.remove(60); - bst.remove(90); - bst.remove(30); - bst.remove(50); + bst.insertIterative(100); + bst.removeIterative(60); + bst.removeIterative(90); + bst.removeIterative(30); + bst.removeIterative(50); expect(bst.root().getValue()).to.equal(100); }); it('should remove root node with left child', () => { - bst.insert(20); - bst.insert(30); - bst.insert(25); - bst.remove(30); - bst.remove(25); - bst.remove(100); + bst.insertIterative(20); + bst.insertIterative(30); + bst.insertIterative(25); + bst.removeIterative(30); + bst.removeIterative(25); + bst.removeIterative(100); expect(bst.root().getValue()).to.equal(20); }); it('should remove root node', () => { - bst.remove(20); + bst.removeIterative(20); expect(bst.root()).to.equal(null); }); }); describe('.removeNode(node)', () => { - const testRemoveTree = new BinarySearchTree(null, { iterative: true }); + const testRemoveTree = new BinarySearchTree(); testRemoveTree - .insert(50) - .insert(80) - .insert(30) - .insert(90) - .insert(60) - .insert(40) - .insert(20); - const n80 = testRemoveTree.find(80); + .insertIterative(50) + .insertIterative(80) + .insertIterative(30) + .insertIterative(90) + .insertIterative(60) + .insertIterative(40) + .insertIterative(20); + const n80 = testRemoveTree.findIterative(80); testRemoveTree.removeNode(n80); expect(testRemoveTree.root().getRight().getValue()).to.equal(90); expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); @@ -303,6 +303,6 @@ describe('BinarySearchTree tests (iterative implementation)', () => { bst.clear(); expect(bst.count()).to.equal(0); expect(bst.root()).to.equal(null); - expect(bst.remove(10)).to.equal(false); + expect(bst.removeIterative(10)).to.equal(false); }); }); From 4080d6b6b16a2e2f28f074d3134657c98c7f33cc Mon Sep 17 00:00:00 2001 From: eyas-ranjous Date: Sun, 19 Oct 2025 20:28:20 -0700 Subject: [PATCH 3/3] update --- src/avlTree.d.ts | 17 ++++++++++++++--- src/binarySearchTree.d.ts | 11 +++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/avlTree.d.ts b/src/avlTree.d.ts index c9ec400..2e24796 100644 --- a/src/avlTree.d.ts +++ b/src/avlTree.d.ts @@ -4,21 +4,32 @@ import { AvlTreeNode } from './avlTreeNode'; export class AvlTree extends BinarySearchTree { constructor(compare?: (a: T, b: T) => number, options?: { key: string }); insert(value: T): AvlTree; + insertIterative(value: T): AvlTree; + remove(value: T): boolean; + removeIterative(value: T): boolean; find(value: T): AvlTreeNode | null; + findIterative(value: T): AvlTreeNode | null; findKey(key: number|string): AvlTreeNode | null; max(node?: AvlTreeNode): AvlTreeNode | null; + maxIterative(node?: AvlTreeNode): AvlTreeNode | null; min(node?: AvlTreeNode): AvlTreeNode | null; + minIterative(node?: AvlTreeNode): AvlTreeNode | null; lowerBound(value: T, includeEqual?: boolean): AvlTreeNode | null; + lowerBoundIterative(value: T, includeEqual?: boolean): AvlTreeNode | null; lowerBoundKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; floor(value: T, includeEqual?: boolean): AvlTreeNode | null; floorKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; upperBound(value: T, includeEqual?: boolean): AvlTreeNode | null; + upperBoundIterative(value: T, includeEqual?: boolean): AvlTreeNode | null; upperBoundKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; ceil(value: T, includeEqual?: boolean): AvlTreeNode | null; ceilKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; root(): AvlTreeNode | null; - traverseInOrder(cb: (node: AvlTreeNode) => void): void; - traversePreOrder(cb: (node: AvlTreeNode) => void): void; - traversePostOrder(cb: (node: AvlTreeNode) => void): void; + traverseInOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; + traverseInOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; + traversePreOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; + traversePreOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; + traversePostOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; + traversePostOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; removeNode(node: AvlTreeNode): boolean; } diff --git a/src/binarySearchTree.d.ts b/src/binarySearchTree.d.ts index ec7336d..2af109e 100644 --- a/src/binarySearchTree.d.ts +++ b/src/binarySearchTree.d.ts @@ -3,26 +3,37 @@ import { BinarySearchTreeNode } from './binarySearchTreeNode'; export class BinarySearchTree { constructor(compare?: (a: T, b: T) => number, options?: { key: string }); insert(value: T): BinarySearchTree; + insertIterative(value: T): BinarySearchTree; has(value: T): boolean; + hasIterative(value: T): boolean; hasKey(key: number|string): boolean; find(value: T): BinarySearchTreeNode | null; + findIterative(value: T): BinarySearchTreeNode | null; findKey(key: number|string): BinarySearchTreeNode | null; max(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; + maxIterative(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; min(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; + minIterative(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; lowerBound(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; + lowerBoundIterative(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; lowerBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; floor(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; floorKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; upperBound(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; + upperBoundIterative(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; upperBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; ceil(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; ceilKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; root(): BinarySearchTreeNode | null; count(): number; remove(value: T): boolean; + removeIterative(value: T): boolean; removeNode(node: BinarySearchTreeNode): boolean; traverseInOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; + traverseInOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; traversePreOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; + traversePreOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; traversePostOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; + traversePostOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; clear(): void; }