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/avlTree.js b/src/avlTree.js index 5533ba6..317570c 100644 --- a/src/avlTree.js +++ b/src/avlTree.js @@ -68,7 +68,7 @@ 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 * @param {number|string|object} value @@ -111,9 +111,67 @@ class AvlTree extends BinarySearchTree { return this; } + /** + * Inserts a value into the tree and maintains + * the tree balanced by making the necessary rotations (iterative implementation) + * + * @public + * @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; + } + /** * Removes a node from the tree and maintains - * the tree balanced by making the necessary rotations + * the tree balanced by making the necessary rotations (recursive implementation) * * @public * @param {number|string|object} value @@ -145,6 +203,50 @@ 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) + * + * @public + * @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 * @public 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; } diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 3eb9cbd..e5576ce 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -27,7 +27,7 @@ class BinarySearchTree { } /** - * Inserts a node with a key/value into the tree + * Inserts a node with a key/value into the tree (recursive implementation) * @public * @param {number|string|object} value * @return {BinarySearchTree} @@ -66,7 +66,49 @@ 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) + * @public + * @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; + } + + /** + * Checks if a value exists in the tree by its value (recursive implementation) * @public * @param {number|string|object} value * @return {boolean} @@ -84,6 +126,29 @@ class BinarySearchTree { return hasRecursive(this._root); } + /** + * Checks if a value exists in the tree by its value (iterative implementation) + * @public + * @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 key * @public @@ -98,7 +163,7 @@ class BinarySearchTree { } /** - * Finds a node by its value + * Finds a node by its value (recursive implementation) * @public * @param {number|string|object} value * @return {BinarySearchTreeNode} @@ -116,6 +181,31 @@ class BinarySearchTree { return findRecursive(this._root); } + /** + * Finds a node by its value (iterative implementation) + * @public + * @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 object's key * @public @@ -130,7 +220,7 @@ class BinarySearchTree { } /** - * Finds the node with max key (most right) in the tree + * Finds the node with max key (most right) in the tree (recursive implementation) * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} @@ -142,7 +232,22 @@ class BinarySearchTree { } /** - * Finds the node with min key (most left) in the tree + * Finds the node with max key (most right) in the tree (iterative implementation) + * @public + * @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 min key (most left) in the tree (recursive implementation) * @public * @param {BinarySearchTreeNode} [current] * @return {BinarySearchTreeNode} @@ -154,7 +259,22 @@ class BinarySearchTree { } /** - * Returns the node with the biggest value less or equal a given value + * Finds the node with min key (most left) in the tree (iterative implementation) + * @public + * @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; + } + + /** + * Returns the node with the biggest value less or equal a given value (recursive implementation) * @public * @param {number|string|object} value * @param {boolean} includeEqual @@ -180,6 +300,33 @@ class BinarySearchTree { return lowerBoundRecursive(this._root); } + /** + * Returns the node with the biggest value less or equal a given value (iterative implementation) + * @public + * @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 object's key less or equal a given key * @public @@ -218,7 +365,7 @@ class BinarySearchTree { } /** - * Returns the node with the smallest value greater or equal a given value + * Returns the node with the smallest value greater or equal a given value (recursive implementation) * @public * @param {number|string|object} value * @param {boolean} includeEqual @@ -244,6 +391,33 @@ class BinarySearchTree { return upperBoundRecursive(this._root); } + /** + * Returns the node with the smallest value greater or equal a given value (iterative implementation) + * @public + * @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 object's key greater or equal a given key * @public @@ -300,7 +474,7 @@ class BinarySearchTree { } /** - * Removes a node by its value + * Removes a node by its value (recursive implementation) * @public * @param {number|string|object} value * @return {boolean} @@ -319,6 +493,32 @@ class BinarySearchTree { return removeRecursively(value, this._root); } + /** + * Removes a node by its value (iterative implementation) + * @public + * @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 from the tree * @public @@ -378,7 +578,7 @@ class BinarySearchTree { } /** - * Traverses the tree in-order (left-node-right) + * Traverses the tree in-order (left-node-right) (recursive implementation) * @public * @param {function} cb * @param {function} [abortCb] @@ -400,7 +600,38 @@ class BinarySearchTree { } /** - * Traverses the tree pre-order (node-left-right) + * Traverses the tree in-order (left-node-right) (iterative implementation) + * @public + * @param {function} cb + * @param {function} [abortCb] + */ + traverseInOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traverseInOrderIterative expects a callback function'); + } + + 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 pre-order (node-left-right) (recursive implementation) * @public * @param {function} cb * @param {function} [abortCb] @@ -421,7 +652,32 @@ class BinarySearchTree { } /** - * Traverses the tree post-order (left-right-node) + * Traverses the tree pre-order (node-left-right) (iterative implementation) + * @public + * @param {function} cb + * @param {function} [abortCb] + */ + traversePreOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traversePreOrderIterative expects a callback function'); + } + + 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 post-order (left-right-node) (recursive implementation) * @public * @param {function} cb * @param {function} [abortCb] @@ -442,6 +698,42 @@ class BinarySearchTree { traverseRecursive(this._root); } + /** + * Traverses the tree post-order (left-right-node) (iterative implementation) + * @public + * @param {function} cb + * @param {function} [abortCb] + */ + traversePostOrderIterative(cb, abortCb) { + if (typeof cb !== 'function') { + throw new Error('.traversePostOrderIterative expects a callback function'); + } + + 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); + } + } + } + /** * Clears the tree * @public diff --git a/test/avlTree.iterative.test.js b/test/avlTree.iterative.test.js new file mode 100644 index 0000000..22c61bd --- /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(); + + describe('.insertIterative(value)', () => { + it('left rotation balancing', () => { + avlTree.insertIterative(50); + avlTree.insertIterative(80); + avlTree.insertIterative(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.insertIterative(40); + avlTree.insertIterative(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.insertIterative(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.insertIterative(35); + avlTree.insertIterative(10); + avlTree.insertIterative(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.insertIterative(100); + avlTree.insertIterative(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(); + elements.forEach((n) => tree.insertIterative(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.removeIterative(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.removeIterative(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.removeIterative(90); + avlTree.removeIterative(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.insertIterative(85); + /* + 40 + / \ + 20 95 + / \ / \ + 15 30 80 100 + \ + 85 + */ + + avlTree.removeIterative(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.removeIterative(30); + avlTree.removeIterative(80); + avlTree.removeIterative(95); + avlTree.removeIterative(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.removeIterative(20); + expect(avlTree.root().getValue()).to.equal(40); + expect(avlTree.root().getLeft().getValue()).to.equal(15); + + avlTree.removeIterative(40); + expect(avlTree.root().getValue()).to.equal(15); + expect(avlTree.count()).to.equal(1); + + avlTree.insertIterative(20); + avlTree.removeIterative(15); + expect(avlTree.root().getValue()).to.equal(20); + expect(avlTree.count()).to.equal(1); + avlTree.removeIterative(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(); + tree.insertIterative(3); + tree.insertIterative(1); + tree.insertIterative(5); + tree.insertIterative(6); + + tree.removeIterative(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(); + 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())); + 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(); + elements.forEach((n) => tree.insertIterative(n)); + elements.forEach((n) => tree.removeIterative(n)); + }); + }); + + describe('.removeNode(node)', () => { + const testRemoveTree = new AvlTree(); + testRemoveTree + .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); + 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..ecb4113 --- /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(); + + describe('.insertIterative(value)', () => { + it('should insert nodes to the tree', () => { + 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.insertIterative(20)).to.be.instanceof(BinarySearchTree); + expect(bst.findIterative(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('.hasIterative(value)', () => { + it('checks if a node exists by value', () => { + 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('.hasIterative(key)', () => { + it('checks if a node exists by key', () => { + 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('.findIterative(value)', () => { + it('should search a node by its value in the tree', () => { + 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('.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' }); + 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('.maxIterative()', () => { + it('get the node with max key', () => { + const max = bst.maxIterative(); + expect(max.getValue()).to.equal(90); + }); + }); + + describe('.minIterative()', () => { + it('get the node with min key', () => { + const min = bst.minIterative(); + expect(min.getValue()).to.equal(20); + }); + }); + + describe('.lowerBoundIterative(value)', () => { + it('gets the node with biggest key less or equal k', () => { + 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.lowerBoundIterative(10)).to.equal(null); + }); + + it('returns the biggest lower bound of multiple lower bounds', () => { + 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('.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' }); + 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('.upperBoundIterative(k)', () => { + it('gets the node with smallest key bigger than a key', () => { + 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.upperBoundIterative(110)).to.equal(null); + }); + + it('returns the smallest upper bound of multiple upper bounds', () => { + 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('.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' }); + 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('.traverseInOrderIterative(cb)', () => { + it('traverse the tree in-order', () => { + const keys = []; + 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.traverseInOrderIterative((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([20, 30, 40]); + }); + }); + + describe('.traversePreOrderIterative(cb)', () => { + it('traverse the tree pre-order', () => { + const keys = []; + 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.traversePreOrderIterative((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([50, 30, 20]); + }); + }); + + describe('.traversePostOrderIterative(cb)', () => { + it('traverse the tree post-order', () => { + const keys = []; + 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.traversePostOrderIterative((node) => { + keys.push(node.getValue()); + counter += 1; + }, () => counter > 2); + expect(keys).to.deep.equal([20, 40, 30]); + }); + }); + + describe('.removeIterative(value)', () => { + it('should remove a leaf node', () => { + 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.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.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.removeIterative(80); + expect(bst.hasIterative(80)).to.equal(false); + expect(bst.root().getRight().getValue()).to.equal(90); + 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.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.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.removeIterative(20); + expect(bst.root()).to.equal(null); + }); + }); + + describe('.removeNode(node)', () => { + const testRemoveTree = new BinarySearchTree(); + testRemoveTree + .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); + 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.removeIterative(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)', () => {