├── .gitignore ├── .npmignore ├── README.md ├── index.d.ts ├── index.js ├── .eslintrc ├── .github └── workflows │ └── npm-grunt.yml ├── src ├── binarySearchTreeNode.d.ts ├── avlTreeNode.d.ts ├── avlTree.d.ts ├── binarySearchTree.d.ts ├── binarySearchTreeNode.js ├── avlTreeNode.js ├── avlTree.js └── binarySearchTree.js ├── Gruntfile.js ├── LICENSE ├── package.json ├── CHANGELOG.md └── test ├── binarySearchTree.recursive.test.js ├── binarySearchTree.iterative.test.js ├── avlTree.recursive.test.js └── avlTree.iterative.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .travis.yml 3 | .gitignore 4 | .eslintrc 5 | Gruntfile.js 6 | coverage/ 7 | node_modules/ 8 | test/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @datastructures-js/binary-search-tree 2 | 3 | ## Docs 4 | https://datastructures-js.info/docs/binary-search-tree 5 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { BinarySearchTree } from'./src/binarySearchTree'; 2 | import { BinarySearchTreeNode } from'./src/binarySearchTreeNode'; 3 | import { AvlTree } from'./src/avlTree'; 4 | import { AvlTreeNode } from'./src/avlTreeNode'; 5 | 6 | export { BinarySearchTree } 7 | export { BinarySearchTreeNode } 8 | export { AvlTree } 9 | export { AvlTreeNode } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { BinarySearchTree } = require('./src/binarySearchTree'); 2 | const { BinarySearchTreeNode } = require('./src/binarySearchTreeNode'); 3 | const { AvlTree } = require('./src/avlTree'); 4 | const { AvlTreeNode } = require('./src/avlTreeNode'); 5 | 6 | module.exports = { 7 | BinarySearchTree, 8 | BinarySearchTreeNode, 9 | AvlTree, 10 | AvlTreeNode 11 | }; 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-len": ["error", { "code": 120, "ignoreComments": true }], 4 | "comma-dangle": ["error", { 5 | "functions": "ignore" 6 | }], 7 | "no-underscore-dangle": [ 8 | "error", 9 | { "allowAfterThis": true } 10 | ], 11 | "class-methods-use-this": "off" 12 | }, 13 | "env": { 14 | "mocha": true, 15 | "node": true 16 | }, 17 | "extends": ["airbnb-base"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/npm-grunt.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS with Grunt 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | npm install 28 | grunt build 29 | -------------------------------------------------------------------------------- /src/binarySearchTreeNode.d.ts: -------------------------------------------------------------------------------- 1 | export class BinarySearchTreeNode { 2 | constructor(value: T); 3 | setValue(value?: T): BinarySearchTreeNode; 4 | getValue(): T; 5 | setLeft(left?: BinarySearchTreeNode): BinarySearchTreeNode; 6 | getLeft(): BinarySearchTreeNode | null; 7 | hasLeft(): boolean; 8 | setRight(right?: BinarySearchTreeNode): BinarySearchTreeNode; 9 | getRight(): BinarySearchTreeNode | null; 10 | hasRight(): boolean; 11 | setParent(parent?: BinarySearchTreeNode): BinarySearchTreeNode; 12 | getParent(): BinarySearchTreeNode | null; 13 | hasParent(): boolean; 14 | isRoot(): boolean; 15 | isLeaf(): boolean; 16 | } 17 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) => { 2 | grunt.initConfig({ 3 | eslint: { 4 | src: ['src/*.js', 'test/*.test.js'] 5 | }, 6 | mochaTest: { 7 | files: ['test/*.test.js'] 8 | }, 9 | mocha_istanbul: { 10 | coverage: { 11 | src: 'test', 12 | options: { 13 | mask: '*.test.js' 14 | } 15 | } 16 | } 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-eslint'); 20 | grunt.loadNpmTasks('grunt-mocha-test'); 21 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 22 | 23 | grunt.registerTask('lint', ['eslint']); 24 | grunt.registerTask('test', ['mochaTest']); 25 | grunt.registerTask('coverage', ['mocha_istanbul']); 26 | grunt.registerTask('build', ['lint', 'coverage']); 27 | }; 28 | -------------------------------------------------------------------------------- /src/avlTreeNode.d.ts: -------------------------------------------------------------------------------- 1 | export class AvlTreeNode { 2 | constructor(value: T, compare?: (a: T, b: T) => number); 3 | setValue(value?: T): AvlTreeNode; 4 | getValue(): T; 5 | setLeft(left?: AvlTreeNode): AvlTreeNode; 6 | getLeft(): AvlTreeNode | null; 7 | hasLeft(): boolean; 8 | setRight(right?: AvlTreeNode): AvlTreeNode; 9 | getRight(): AvlTreeNode | null; 10 | hasRight(): boolean; 11 | setParent(parent?: AvlTreeNode): AvlTreeNode; 12 | getParent(): AvlTreeNode | null; 13 | hasParent(): boolean; 14 | isRoot(): boolean; 15 | isLeaf(): boolean; 16 | rotateLeft(): AvlTreeNode; 17 | rotateRight(): AvlTreeNode; 18 | rotateLeftRight(): AvlTreeNode; 19 | rotateRightLeft(): AvlTreeNode; 20 | updateHeight(): AvlTreeNode; 21 | getHeight(): number; 22 | getLeftHeight(): number; 23 | getRightHeight(): number; 24 | getBalance(): number; 25 | isBalanced(): boolean; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Eyas Ranjous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datastructures-js/binary-search-tree", 3 | "version": "5.4.0", 4 | "description": "binary search tree & avl tree (self balancing tree) implementation in javascript", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "grunt test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/datastructures-js/binary-search-tree.git" 13 | }, 14 | "keywords": [ 15 | "binary search tree", 16 | "bst js", 17 | "bst es6", 18 | "binary search tree es6", 19 | "binary search tree js", 20 | "avl tree", 21 | "avl tree es6", 22 | "avl tree js", 23 | "self balancing tree" 24 | ], 25 | "author": "Eyas Ranjous ", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/datastructures-js/binary-search-tree/issues" 29 | }, 30 | "homepage": "https://github.com/datastructures-js/binary-search-tree#readme", 31 | "devDependencies": { 32 | "chai": "^4.2.0", 33 | "eslint": "^6.7.2", 34 | "eslint-config-airbnb-base": "^14.0.0", 35 | "eslint-plugin-import": "^2.19.1", 36 | "grunt": "^1.0.4", 37 | "grunt-eslint": "^22.0.0", 38 | "grunt-mocha-istanbul": "^5.0.2", 39 | "grunt-mocha-test": "^0.13.3", 40 | "istanbul": "^0.4.5", 41 | "mocha": "^6.2.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/avlTree.d.ts: -------------------------------------------------------------------------------- 1 | import { BinarySearchTree } from './binarySearchTree'; 2 | import { AvlTreeNode } from './avlTreeNode'; 3 | 4 | export class AvlTree extends BinarySearchTree { 5 | constructor(compare?: (a: T, b: T) => number, options?: { key: string }); 6 | insert(value: T): AvlTree; 7 | insertIterative(value: T): AvlTree; 8 | remove(value: T): boolean; 9 | removeIterative(value: T): boolean; 10 | find(value: T): AvlTreeNode | null; 11 | findIterative(value: T): AvlTreeNode | null; 12 | findKey(key: number|string): AvlTreeNode | null; 13 | max(node?: AvlTreeNode): AvlTreeNode | null; 14 | maxIterative(node?: AvlTreeNode): AvlTreeNode | null; 15 | min(node?: AvlTreeNode): AvlTreeNode | null; 16 | minIterative(node?: AvlTreeNode): AvlTreeNode | null; 17 | lowerBound(value: T, includeEqual?: boolean): AvlTreeNode | null; 18 | lowerBoundIterative(value: T, includeEqual?: boolean): AvlTreeNode | null; 19 | lowerBoundKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; 20 | floor(value: T, includeEqual?: boolean): AvlTreeNode | null; 21 | floorKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; 22 | upperBound(value: T, includeEqual?: boolean): AvlTreeNode | null; 23 | upperBoundIterative(value: T, includeEqual?: boolean): AvlTreeNode | null; 24 | upperBoundKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; 25 | ceil(value: T, includeEqual?: boolean): AvlTreeNode | null; 26 | ceilKey(key: number|string, includeEqual?: boolean): AvlTreeNode | null; 27 | root(): AvlTreeNode | null; 28 | traverseInOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 29 | traverseInOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 30 | traversePreOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 31 | traversePreOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 32 | traversePostOrder(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 33 | traversePostOrderIterative(cb: (node: AvlTreeNode) => void, abortCb?: () => boolean): void; 34 | removeNode(node: AvlTreeNode): boolean; 35 | } 36 | -------------------------------------------------------------------------------- /src/binarySearchTree.d.ts: -------------------------------------------------------------------------------- 1 | import { BinarySearchTreeNode } from './binarySearchTreeNode'; 2 | 3 | export class BinarySearchTree { 4 | constructor(compare?: (a: T, b: T) => number, options?: { key: string }); 5 | insert(value: T): BinarySearchTree; 6 | insertIterative(value: T): BinarySearchTree; 7 | has(value: T): boolean; 8 | hasIterative(value: T): boolean; 9 | hasKey(key: number|string): boolean; 10 | find(value: T): BinarySearchTreeNode | null; 11 | findIterative(value: T): BinarySearchTreeNode | null; 12 | findKey(key: number|string): BinarySearchTreeNode | null; 13 | max(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; 14 | maxIterative(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; 15 | min(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; 16 | minIterative(node?: BinarySearchTreeNode): BinarySearchTreeNode | null; 17 | lowerBound(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 18 | lowerBoundIterative(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 19 | lowerBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; 20 | floor(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 21 | floorKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; 22 | upperBound(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 23 | upperBoundIterative(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 24 | upperBoundKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; 25 | ceil(value: T, includeEqual?: boolean): BinarySearchTreeNode | null; 26 | ceilKey(key: number|string, includeEqual?: boolean): BinarySearchTreeNode | null; 27 | root(): BinarySearchTreeNode | null; 28 | count(): number; 29 | remove(value: T): boolean; 30 | removeIterative(value: T): boolean; 31 | removeNode(node: BinarySearchTreeNode): boolean; 32 | traverseInOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 33 | traverseInOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 34 | traversePreOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 35 | traversePreOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 36 | traversePostOrder(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 37 | traversePostOrderIterative(cb: (node: BinarySearchTreeNode) => void, abortCb?: () => boolean): void; 38 | clear(): void; 39 | } 40 | -------------------------------------------------------------------------------- /src/binarySearchTreeNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/binary-search-tree 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | /** 8 | * @class BinarySearchTreeNode 9 | */ 10 | class BinarySearchTreeNode { 11 | constructor(value) { 12 | this._value = value; 13 | this._left = null; 14 | this._right = null; 15 | this._parent = null; 16 | } 17 | 18 | /** 19 | * @public 20 | * @param {number|string|object} value 21 | * @returns {BinarySearchTreeNode} 22 | */ 23 | setValue(value) { 24 | this._value = value; 25 | return this; 26 | } 27 | 28 | /** 29 | * @public 30 | * @return {number|string|object} 31 | */ 32 | getValue() { 33 | return this._value; 34 | } 35 | 36 | /** 37 | * @public 38 | * @param {BinarySearchTreeNode} left 39 | * @returns {BinarySearchTreeNode} 40 | */ 41 | setLeft(left) { 42 | if (left && !(left instanceof BinarySearchTreeNode)) { 43 | throw new Error('setLeft expects a BinarySearchTreeNode'); 44 | } 45 | 46 | this._left = left || null; 47 | return this; 48 | } 49 | 50 | /** 51 | * @public 52 | * @return {BinarySearchTreeNode} 53 | */ 54 | getLeft() { 55 | return this._left; 56 | } 57 | 58 | /** 59 | * @public 60 | * @return {boolean} 61 | */ 62 | hasLeft() { 63 | return this._left instanceof BinarySearchTreeNode; 64 | } 65 | 66 | /** 67 | * @public 68 | * @param {BinarySearchTreeNode|null} right 69 | * @returns {BinarySearchTreeNode} 70 | */ 71 | setRight(right) { 72 | if (right && !(right instanceof BinarySearchTreeNode)) { 73 | throw new Error('setRight expects a BinarySearchTreeNode or null'); 74 | } 75 | 76 | this._right = right || null; 77 | return this; 78 | } 79 | 80 | /** 81 | * @public 82 | * @return {BinarySearchTreeNode} 83 | */ 84 | getRight() { 85 | return this._right; 86 | } 87 | 88 | /** 89 | * @public 90 | * @return {boolean} 91 | */ 92 | hasRight() { 93 | return this._right instanceof BinarySearchTreeNode; 94 | } 95 | 96 | /** 97 | * @public 98 | * @param {BinarySearchTreeNode} parent 99 | * @returns {BinarySearchTreeNode} 100 | */ 101 | setParent(parent) { 102 | if (parent && !(parent instanceof BinarySearchTreeNode)) { 103 | throw new Error('setParent expects a BinarySearchTreeNode or null'); 104 | } 105 | 106 | this._parent = parent || null; 107 | return this; 108 | } 109 | 110 | /** 111 | * @public 112 | * @return {BinarySearchTreeNode} 113 | */ 114 | getParent() { 115 | return this._parent; 116 | } 117 | 118 | /** 119 | * @public 120 | * @return {boolean} 121 | */ 122 | hasParent() { 123 | return this._parent instanceof BinarySearchTreeNode; 124 | } 125 | 126 | /** 127 | * @public 128 | * @return {boolean} 129 | */ 130 | isRoot() { 131 | return this._parent === null; 132 | } 133 | 134 | /** 135 | * @public 136 | * @return {boolean} 137 | */ 138 | isLeaf() { 139 | return !this.hasLeft() && !this.hasRight(); 140 | } 141 | } 142 | 143 | exports.BinarySearchTreeNode = BinarySearchTreeNode; 144 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [5.4.0] - 2025-10-19 9 | ### Added 10 | - insertIterative 11 | - hasIterative 12 | - findIterative 13 | - maxIterative 14 | - minIterative 15 | - lowerBoundIterative 16 | - upperBoundIterative 17 | - removeIterative 18 | - traverseInOrderIterative 19 | - traversePreOrderIterative 20 | - traversePostOrderIterative 21 | 22 | ## [5.3.3] - 2025-08-26 23 | ### Fixed 24 | - README 25 | 26 | ## [5.3.2] - 2024-01-07 27 | ### Fixed 28 | - AvlTree balance function. 29 | 30 | ## [5.3.1] - 2023-01-30 31 | ### Fixed 32 | - AvlTree ts types. 33 | 34 | ## [5.3.0] - 2023-01-30 35 | ### Added 36 | - `removeNode` to remove a node by its reference. 37 | - `upperBoundKey`, `floorKey`, `lowerBoundKey`, `ceilKey` to support finding nodes by the object comparison key. 38 | 39 | ## [5.2.0] - 2022-12-12 40 | 41 | ### Added 42 | - findKey & hasKey to find object nodes directly by the key prop value. 43 | 44 | ## [5.1.0] - 2022-12-08 45 | 46 | ### Added 47 | - ability to abort tree traversal using a callback. 48 | 49 | ## [5.0.2] - 2022-08-21 50 | 51 | ### Fixed 52 | - `.remove` typos + was not balancing nodes properly on one case. 53 | 54 | ## [5.0.1] - 2022-07-30 55 | 56 | ### Fixed 57 | - types field in package.json 58 | 59 | ## [5.0.0] - 2022-07-19 60 | 61 | ### Changed 62 | - tree now accepts a compare function. 63 | 64 | ## [4.3.2] - 2022-07-13 65 | 66 | ### Fixed 67 | - readme. 68 | 69 | ## [4.3.1] - 2021-11-01 70 | 71 | ### Fixed 72 | - ts types. 73 | 74 | ## [4.3.0] - 2021-08-09 75 | ### Added 76 | - `.floor` & `.ceil` as delegates to `.lowerBound` & `upperBound`. 77 | 78 | ### Fixed 79 | - `.lowerBound` & `upperBound` now finds the precise bound when multiple ones exist. 80 | - make param (value) optional on `.insert`. 81 | 82 | ## [4.2.2] - 2021-06-20 83 | 84 | ### Fixed 85 | - index.d.ts 86 | 87 | ## [4.2.1] - 2021-06-20 88 | 89 | ### Fixed 90 | - export. 91 | 92 | ## [4.2.0] - 2021-06-19 93 | 94 | ### Added 95 | - typescript. 96 | 97 | ## [4.1.1] - 2021-05-29 98 | 99 | ### Fixed 100 | - typo in export name. 101 | 102 | 103 | ## [4.1.0] - 2021-04-21 104 | 105 | ### Added 106 | - `.lowerBound(k)` to find the node with biggest key less or equal a value k. 107 | - `.upperBound(k)` to find the node with smallest key bigger than a value k. 108 | 109 | ## [4.0.1] - 2021-04-15 110 | ### Fixed 111 | - exported AvlTreeNode path. 112 | 113 | ## [4.0.0] - 2021-04-14 114 | ### Changed 115 | - return inserted node from `insert`. 116 | - BinarySearchTreeNode & AvlTreeNode methods. 117 | 118 | ### Fixed 119 | - README 120 | - jsdoc 121 | 122 | ## [3.1.8] - 2021-04-02 123 | ### Fixed 124 | - update the removed node value with min right one in avl tree. 125 | 126 | ## [3.1.7] - 2021-01-26 127 | ### Fixed 128 | - update the removed node value with min right one. 129 | 130 | ## [3.1.6] - 2020-04-15 131 | ### Fixed 132 | - README 133 | 134 | ## [3.1.5] - 2020-04-12 135 | ### Fixed 136 | - README 137 | 138 | ## [3.1.4] - 2020-04-12 139 | ### Fixed 140 | - jsdoc 141 | 142 | ## [3.1.3] - 2020-04-10 143 | ### Fixed 144 | - README 145 | 146 | ## [3.1.2] - 2020-04-10 147 | ### Fixed 148 | - README 149 | - jsdoc 150 | 151 | ## [3.1.1] - 2020-04-01 152 | ### Fixed 153 | - use the same balancing algorithm for insert and remove. 154 | 155 | ## [3.1.0] - 2020-03-31 156 | ### Added 157 | - AvlTreeNode & AvlTree implementation. 158 | 159 | ## [3.0.1] - 2020-03-29 160 | ### Fixed 161 | - return the updated node in `.insert` 162 | 163 | ## [3.0.0] - 2020-03-28 164 | ### Changed 165 | - New release 166 | -------------------------------------------------------------------------------- /src/avlTreeNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/binary-search-tree 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | const defaultCompare = (a, b) => { 8 | if (a === b) return 0; 9 | return a > b ? 1 : -1; 10 | }; 11 | 12 | /** 13 | * AvlTree node class type 14 | * @class AvlTreeNode 15 | */ 16 | class AvlTreeNode { 17 | constructor(value, compare) { 18 | if (compare && typeof compare !== 'function') { 19 | throw new Error('AvlTreeNode constructor expects a compare function'); 20 | } 21 | 22 | this._value = value; 23 | this._compare = compare || defaultCompare; 24 | this._left = null; 25 | this._right = null; 26 | this._parent = null; 27 | this._height = 1; 28 | } 29 | 30 | /** 31 | * @public 32 | * @param {number|string|object} value 33 | * @returns {AvlTreeNode} 34 | */ 35 | setValue(value) { 36 | this._value = value; 37 | return this; 38 | } 39 | 40 | /** 41 | * @public 42 | * @return {number|string|object} 43 | */ 44 | getValue() { 45 | return this._value; 46 | } 47 | 48 | /** 49 | * @public 50 | * @param {AvlTreeNode} left 51 | * @returns {AvlTreeNode} 52 | */ 53 | setLeft(left) { 54 | if (left && !(left instanceof AvlTreeNode)) { 55 | throw new Error('setLeft expects an AvlTreeNode'); 56 | } 57 | 58 | this._left = left || null; 59 | return this; 60 | } 61 | 62 | /** 63 | * @public 64 | * @return {AvlTreeNode} 65 | */ 66 | getLeft() { 67 | return this._left; 68 | } 69 | 70 | /** 71 | * @public 72 | * @return {boolean} 73 | */ 74 | hasLeft() { 75 | return this._left instanceof AvlTreeNode; 76 | } 77 | 78 | /** 79 | * @public 80 | * @param {AvlTreeNode} right 81 | * @returns {AvlTreeNode} 82 | */ 83 | setRight(right) { 84 | if (right && !(right instanceof AvlTreeNode)) { 85 | throw new Error('setRight expects a AvlTreeNode or null'); 86 | } 87 | 88 | this._right = right || null; 89 | return this; 90 | } 91 | 92 | /** 93 | * @public 94 | * @return {AvlTreeNode} 95 | */ 96 | getRight() { 97 | return this._right; 98 | } 99 | 100 | /** 101 | * @public 102 | * @return {boolean} 103 | */ 104 | hasRight() { 105 | return this._right instanceof AvlTreeNode; 106 | } 107 | 108 | /** 109 | * @public 110 | * @param {AvlTreeNode} parent 111 | * @returns {AvlTreeNode} 112 | */ 113 | setParent(parent) { 114 | if (parent && !(parent instanceof AvlTreeNode)) { 115 | throw new Error('setParent expects an AvlTreeNode'); 116 | } 117 | 118 | this._parent = parent || null; 119 | return this; 120 | } 121 | 122 | /** 123 | * @public 124 | * @return {AvlTreeNode} 125 | */ 126 | getParent() { 127 | return this._parent; 128 | } 129 | 130 | /** 131 | * @public 132 | * @return {boolean} 133 | */ 134 | hasParent() { 135 | return this._parent instanceof AvlTreeNode; 136 | } 137 | 138 | /** 139 | * @public 140 | * @return {boolean} 141 | */ 142 | isRoot() { 143 | return this._parent === null; 144 | } 145 | 146 | /** 147 | * @public 148 | * @return {boolean} 149 | */ 150 | isLeaf() { 151 | return !this.hasLeft() && !this.hasRight(); 152 | } 153 | 154 | /** 155 | * Rotate-self left (counter-clockwise) 156 | * @public 157 | * @returns {AvlTreeNode} 158 | */ 159 | rotateLeft() { 160 | const right = this._right; // this._right will be re-assigned 161 | 162 | // set the node as a left child of its right child 163 | if (right !== null) { 164 | if (right.hasLeft()) { 165 | right.getLeft().setParent(this); 166 | } 167 | 168 | // rebase right child to node's right left child. 169 | this._right = right.getLeft(); 170 | 171 | right.setLeft(this); 172 | right.setParent(this._parent); 173 | } 174 | 175 | // rebase parent's child to node's right child 176 | if (this.hasParent() && right !== null) { 177 | if (this._compare(this._parent.getValue(), right.getValue()) < 0) { 178 | this._parent.setRight(right); 179 | } else { 180 | this._parent.setLeft(right); 181 | } 182 | } 183 | 184 | // rebase parent to node's right child 185 | this._parent = right; 186 | 187 | this.updateHeight(); 188 | if (this.hasParent()) { 189 | this._parent.updateHeight(); 190 | } 191 | 192 | return this; 193 | } 194 | 195 | /** 196 | * Rotate-self right (clockwise) 197 | * @public 198 | * @returns {AvlTreeNode} 199 | */ 200 | rotateRight() { 201 | const left = this._left; // this._left will be re-assigned 202 | 203 | // set the node as a right child of its left child 204 | if (left !== null) { 205 | if (left.hasRight()) { 206 | left.getRight().setParent(this); 207 | } 208 | 209 | // rebase left child to node's left right child. 210 | this._left = left.getRight(); 211 | 212 | left.setRight(this); 213 | left.setParent(this._parent); 214 | } 215 | 216 | // rebase parent's child to node's left child 217 | if (this.hasParent() && left !== null) { 218 | if (this._compare(this._parent.getValue(), left.getValue()) > 0) { 219 | this._parent.setLeft(left); 220 | } else { 221 | this._parent.setRight(left); 222 | } 223 | } 224 | 225 | // rebase parent to node's left child 226 | this._parent = left; 227 | 228 | this.updateHeight(); 229 | if (this.hasParent()) { 230 | this._parent.updateHeight(); 231 | } 232 | 233 | return this; 234 | } 235 | 236 | /** 237 | * Rotate-self to right after rotating left child to left 238 | * @public 239 | * @returns {AvlTreeNode} 240 | */ 241 | rotateLeftRight() { 242 | if (this.hasLeft()) { 243 | this._left.rotateLeft(); 244 | } 245 | this.rotateRight(); 246 | return this; 247 | } 248 | 249 | /** 250 | * Rotate-self to left after rotating right child to right 251 | * @public 252 | * @returns {AvlTreeNode} 253 | */ 254 | rotateRightLeft() { 255 | if (this.hasRight()) { 256 | this._right.rotateRight(); 257 | } 258 | this.rotateLeft(); 259 | return this; 260 | } 261 | 262 | /** 263 | * @public 264 | * @return {number} 265 | */ 266 | getLeftHeight() { 267 | return this.hasLeft() ? this.getLeft().getHeight() : 0; 268 | } 269 | 270 | /** 271 | * @public 272 | * @return {number} 273 | */ 274 | getRightHeight() { 275 | return this.hasRight() ? this.getRight().getHeight() : 0; 276 | } 277 | 278 | /** 279 | * Updates self height based on the max height of children 280 | * @public 281 | * @returns {AvlTreeNode} 282 | */ 283 | updateHeight() { 284 | this._height = Math.max(this.getLeftHeight(), this.getRightHeight()) + 1; 285 | return this; 286 | } 287 | 288 | /** 289 | * @public 290 | * @return {number} 291 | */ 292 | getHeight() { 293 | return this._height; 294 | } 295 | 296 | /** 297 | * Gets the balance of a node as the diff between left & right heights 298 | * @public 299 | * @return {number} 300 | */ 301 | getBalance() { 302 | return this.getLeftHeight() - this.getRightHeight(); 303 | } 304 | 305 | /** 306 | * Checks if the node is balanced 307 | * @public 308 | * @return {boolean} 309 | */ 310 | isBalanced() { 311 | const balance = this.getBalance(); 312 | return balance >= -1 && balance <= 1; 313 | } 314 | } 315 | 316 | exports.AvlTreeNode = AvlTreeNode; 317 | -------------------------------------------------------------------------------- /src/avlTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/binary-search-tree 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | const { BinarySearchTree } = require('./binarySearchTree'); 8 | const { AvlTreeNode } = require('./avlTreeNode'); 9 | 10 | /** 11 | * @class AvlTree 12 | * @extends BinarySearchTree 13 | */ 14 | class AvlTree extends BinarySearchTree { 15 | constructor(compare, options) { 16 | if (compare && typeof compare !== 'function') { 17 | throw new Error('AvlTree constructor expects a compare function'); 18 | } 19 | 20 | super(compare, options); 21 | } 22 | 23 | /** 24 | * Get the node's height 25 | * @private 26 | * @param {AvlTreeNode} node 27 | * @return {number} 28 | */ 29 | _getNodeHeight(node) { 30 | if (!(node instanceof AvlTreeNode)) return 0; 31 | return node.getHeight(); 32 | } 33 | 34 | /** 35 | * Applies the proper rotation on a node 36 | * @private 37 | * @param {AvlTreeNode} node 38 | */ 39 | _balanceNode(node) { 40 | if (!node) return; 41 | 42 | node.updateHeight(); 43 | const balance = node.getBalance(); 44 | if (balance > 1) { 45 | const leftLeft = node.getLeft().getLeft(); 46 | const leftRight = node.getLeft().getRight(); 47 | if (this._getNodeHeight(leftLeft) >= this._getNodeHeight(leftRight)) { 48 | node.rotateRight(); 49 | } else if (node.getLeft().hasRight()) { 50 | node.rotateLeftRight(); 51 | } 52 | } else if (balance < -1) { 53 | const rightRight = node.getRight().getRight(); 54 | const rightLeft = node.getRight().getLeft(); 55 | if (this._getNodeHeight(rightRight) >= this._getNodeHeight(rightLeft)) { 56 | node.rotateLeft(); 57 | } else if (node.getRight().hasLeft()) { 58 | node.rotateRightLeft(); 59 | } 60 | } 61 | 62 | // check if root was rotated 63 | if ((balance < -1 || balance > 1) && node === this._root) { 64 | // replace root when rotated with the child (now parent of root) 65 | this._root = node.getParent(); 66 | } 67 | } 68 | 69 | /** 70 | * Inserts a value into the tree and maintains 71 | * the tree balanced by making the necessary rotations (recursive implementation) 72 | * 73 | * @public 74 | * @param {number|string|object} value 75 | * @return {AvlTree} 76 | */ 77 | insert(value) { 78 | const newNode = new AvlTreeNode(value, this._compare); 79 | const insertRecursive = (current) => { 80 | const compare = this._compare(value, current.getValue()); 81 | if (compare < 0) { 82 | if (current.hasLeft()) { 83 | insertRecursive(current.getLeft()); 84 | this._balanceNode(current); // backward-tracking 85 | } else { 86 | newNode.setParent(current); 87 | current.setLeft(newNode).updateHeight(); 88 | this._count += 1; 89 | } 90 | } else if (compare > 0) { 91 | if (current.hasRight()) { 92 | insertRecursive(current.getRight()); 93 | this._balanceNode(current); // backward-tracking 94 | } else { 95 | newNode.setParent(current); 96 | current.setRight(newNode).updateHeight(); 97 | this._count += 1; 98 | } 99 | } else { 100 | current.setValue(value); 101 | } 102 | }; 103 | 104 | if (this._root === null) { 105 | this._root = newNode; 106 | this._count += 1; 107 | } else { 108 | insertRecursive(this._root); 109 | } 110 | 111 | return this; 112 | } 113 | 114 | /** 115 | * Inserts a value into the tree and maintains 116 | * the tree balanced by making the necessary rotations (iterative implementation) 117 | * 118 | * @public 119 | * @param {number|string|object} value 120 | * @return {AvlTree} 121 | */ 122 | insertIterative(value) { 123 | const newNode = new AvlTreeNode(value, this._compare); 124 | 125 | if (this._root === null) { 126 | this._root = newNode; 127 | this._count += 1; 128 | return this; 129 | } 130 | 131 | // Find insertion point and track path 132 | const path = []; 133 | let node = this._root; 134 | let inserted = false; 135 | 136 | while (!inserted) { 137 | path.push(node); 138 | const compare = this._compare(value, node.getValue()); 139 | 140 | if (compare < 0) { 141 | if (node.hasLeft()) { 142 | node = node.getLeft(); 143 | } else { 144 | newNode.setParent(node); 145 | node.setLeft(newNode).updateHeight(); 146 | this._count += 1; 147 | inserted = true; 148 | } 149 | } else if (compare > 0) { 150 | if (node.hasRight()) { 151 | node = node.getRight(); 152 | } else { 153 | newNode.setParent(node); 154 | node.setRight(newNode).updateHeight(); 155 | this._count += 1; 156 | inserted = true; 157 | } 158 | } else { 159 | node.setValue(value); 160 | return this; 161 | } 162 | } 163 | 164 | // Balance all ancestors in reverse order (backward-tracking) 165 | for (let i = path.length - 1; i >= 0; i -= 1) { 166 | this._balanceNode(path[i]); 167 | } 168 | 169 | return this; 170 | } 171 | 172 | /** 173 | * Removes a node from the tree and maintains 174 | * the tree balanced by making the necessary rotations (recursive implementation) 175 | * 176 | * @public 177 | * @param {number|string|object} value 178 | * @return {boolean} 179 | */ 180 | remove(value) { 181 | const removeRecursively = (val, current) => { 182 | if (current === null) { 183 | return false; 184 | } 185 | 186 | const compare = this._compare(val, current.getValue()); 187 | if (compare < 0) { 188 | const removed = removeRecursively(val, current.getLeft()); 189 | this._balanceNode(current); 190 | return removed; 191 | } 192 | 193 | if (compare > 0) { 194 | const removed = removeRecursively(val, current.getRight()); 195 | this._balanceNode(current); 196 | return removed; 197 | } 198 | 199 | // current node is the node to remove 200 | return this.removeNode(current); 201 | }; 202 | 203 | return removeRecursively(value, this._root); 204 | } 205 | 206 | /** 207 | * Removes a node from the tree and maintains 208 | * the tree balanced by making the necessary rotations (iterative implementation) 209 | * 210 | * @public 211 | * @param {number|string|object} value 212 | * @return {boolean} 213 | */ 214 | removeIterative(value) { 215 | if (this._root === null) { 216 | return false; 217 | } 218 | 219 | const path = []; 220 | let node = this._root; 221 | let found = false; 222 | while (node !== null && !found) { 223 | const compare = this._compare(value, node.getValue()); 224 | 225 | if (compare === 0) { 226 | found = true; 227 | } else { 228 | path.push(node); 229 | if (compare < 0) { 230 | node = node.getLeft(); 231 | } else { 232 | node = node.getRight(); 233 | } 234 | } 235 | } 236 | 237 | if (!found) { 238 | return false; 239 | } 240 | 241 | const removed = this.removeNode(node); 242 | for (let i = path.length - 1; i >= 0; i -= 1) { 243 | // Balance all ancestors in reverse order (backward-tracking) 244 | this._balanceNode(path[i]); 245 | } 246 | 247 | return removed; 248 | } 249 | 250 | /** 251 | * Removes a node from the tree 252 | * @public 253 | * @param {AvlTreeNode} node 254 | * @return {boolean} 255 | */ 256 | removeNode(node) { 257 | if (node === null || !(node instanceof AvlTreeNode)) { 258 | return false; 259 | } 260 | 261 | // case 1: node has no children 262 | if (node.isLeaf()) { 263 | if (node.isRoot()) { 264 | this._root = null; 265 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 266 | node.getParent().setLeft(null).updateHeight(); 267 | } else { 268 | node.getParent().setRight(null).updateHeight(); 269 | } 270 | this._count -= 1; 271 | return true; 272 | } 273 | 274 | // case 2: node has a left child and no right child 275 | if (!node.hasRight()) { 276 | if (node.isRoot()) { 277 | this._root = node.getLeft(); 278 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 279 | node.getParent().setLeft(node.getLeft()).updateHeight(); 280 | } else { 281 | node.getParent().setRight(node.getLeft()).updateHeight(); 282 | } 283 | node.getLeft().setParent(node.getParent()); 284 | this._count -= 1; 285 | return true; 286 | } 287 | 288 | // case 3: node has a right child and no left child 289 | if (!node.hasLeft()) { 290 | if (node.isRoot()) { 291 | this._root = node.getRight(); 292 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 293 | node.getParent().setLeft(node.getRight()).updateHeight(); 294 | } else { 295 | node.getParent().setRight(node.getRight()).updateHeight(); 296 | } 297 | node.getRight().setParent(node.getParent()); 298 | this._count -= 1; 299 | return true; 300 | } 301 | 302 | // case 4: node has left and right children 303 | const minRight = this.min(node.getRight()); 304 | const removed = this.removeNode(minRight); 305 | node.setValue(minRight.getValue()); 306 | this._balanceNode(node); 307 | return removed; 308 | } 309 | } 310 | 311 | exports.AvlTree = AvlTree; 312 | -------------------------------------------------------------------------------- /test/binarySearchTree.recursive.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { BinarySearchTreeNode } = require('../src/binarySearchTreeNode'); 3 | const { BinarySearchTree } = require('../src/binarySearchTree'); 4 | 5 | describe('BinarySearchTree tests (recursive implementation)', () => { 6 | const bst = new BinarySearchTree(); 7 | 8 | describe('.insert(value)', () => { 9 | it('should insert nodes to the tree', () => { 10 | expect(bst.insert(50)).to.be.instanceof(BinarySearchTree); 11 | expect(bst.insert(80)).to.be.instanceof(BinarySearchTree); 12 | expect(bst.insert(30)).to.be.instanceof(BinarySearchTree); 13 | expect(bst.insert(90)).to.be.instanceof(BinarySearchTree); 14 | expect(bst.insert(60)).to.be.instanceof(BinarySearchTree); 15 | expect(bst.insert(40)).to.be.instanceof(BinarySearchTree); 16 | expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); 17 | 18 | // updates value of existing node 19 | expect(bst.insert(20)).to.be.instanceof(BinarySearchTree); 20 | expect(bst.find(20).getValue()).to.equal(20); 21 | }); 22 | }); 23 | 24 | describe('.root()', () => { 25 | it('should get the root node', () => { 26 | expect(bst.root().getValue()).to.equal(50); 27 | expect(bst.root().getRight().getValue()).to.equal(80); 28 | expect(bst.root().getLeft().getValue()).to.equal(30); 29 | }); 30 | }); 31 | 32 | describe('.count()', () => { 33 | it('get the count of nodes in the tree', () => { 34 | expect(bst.count()).to.be.equal(7); 35 | }); 36 | }); 37 | 38 | describe('.has(value)', () => { 39 | it('checks if a node exists by value', () => { 40 | expect(bst.has(50)).to.equal(true); 41 | expect(bst.has(80)).to.equal(true); 42 | expect(bst.has(30)).to.equal(true); 43 | expect(bst.has(90)).to.equal(true); 44 | expect(bst.has(50)).to.equal(true); 45 | expect(bst.has(40)).to.equal(true); 46 | expect(bst.has(20)).to.equal(true); 47 | expect(bst.has(100)).to.equal(false); 48 | }); 49 | }); 50 | 51 | describe('.has(key)', () => { 52 | it('checks if a node exists by key', () => { 53 | const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 54 | testTree.insert({ id: 1, name: 'a' }); 55 | testTree.insert({ id: 2, name: 'b' }); 56 | testTree.insert({ id: 3, name: 'c' }); 57 | expect(testTree.has({ id: 1 })).to.equal(true); 58 | expect(testTree.has({ id: 2 })).to.equal(true); 59 | expect(testTree.has({ id: 3 })).to.equal(true); 60 | expect(testTree.hasKey(1)).to.equal(true); 61 | expect(testTree.hasKey(2)).to.equal(true); 62 | expect(testTree.hasKey(3)).to.equal(true); 63 | }); 64 | }); 65 | 66 | describe('.find(value)', () => { 67 | it('should search a node by its value in the tree', () => { 68 | expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); 69 | expect(bst.find(80)).to.be.instanceof(BinarySearchTreeNode); 70 | expect(bst.find(30)).to.be.instanceof(BinarySearchTreeNode); 71 | expect(bst.find(90)).to.be.instanceof(BinarySearchTreeNode); 72 | expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); 73 | expect(bst.find(40)).to.be.instanceof(BinarySearchTreeNode); 74 | expect(bst.find(20)).to.be.instanceof(BinarySearchTreeNode); 75 | expect(bst.find(100)).to.equal(null); 76 | }); 77 | }); 78 | 79 | describe('.findKey(key)', () => { 80 | it('should search a node by its key in the tree', () => { 81 | const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 82 | testTree.insert({ id: 1, name: 'a' }); 83 | testTree.insert({ id: 2, name: 'b' }); 84 | testTree.insert({ id: 3, name: 'c' }); 85 | expect(testTree.find({ id: 1 }).getValue()).to.eql({ id: 1, name: 'a' }); 86 | expect(testTree.find({ id: 2 }).getValue()).to.eql({ id: 2, name: 'b' }); 87 | expect(testTree.find({ id: 3 }).getValue()).to.eql({ id: 3, name: 'c' }); 88 | expect(testTree.findKey(1).getValue()).to.eql({ id: 1, name: 'a' }); 89 | expect(testTree.findKey(2).getValue()).to.eql({ id: 2, name: 'b' }); 90 | expect(testTree.findKey(3).getValue()).to.eql({ id: 3, name: 'c' }); 91 | }); 92 | }); 93 | 94 | describe('.max()', () => { 95 | it('get the node with max key', () => { 96 | const max = bst.max(); 97 | expect(max.getValue()).to.equal(90); 98 | }); 99 | }); 100 | 101 | describe('.min()', () => { 102 | it('get the node with min key', () => { 103 | const min = bst.min(); 104 | expect(min.getValue()).to.equal(20); 105 | }); 106 | }); 107 | 108 | describe('.lowerBound(value)', () => { 109 | it('gets the node with biggest key less or equal k', () => { 110 | expect(bst.lowerBound(60).getValue()).to.equal(60); 111 | expect(bst.lowerBound(60, false).getValue()).to.equal(50); 112 | }); 113 | 114 | it('returns null when k is less than all tree keys', () => { 115 | expect(bst.lowerBound(10)).to.equal(null); 116 | }); 117 | 118 | it('returns the biggest lower bound of multiple lower bounds', () => { 119 | const lowerBst = new BinarySearchTree(); 120 | lowerBst.insert(20); 121 | lowerBst.insert(7); 122 | lowerBst.insert(15); 123 | lowerBst.insert(9); 124 | expect(lowerBst.floor(10).getValue()).to.equal(9); 125 | }); 126 | }); 127 | 128 | describe('.lowerBoundKey(key) / floorKey', () => { 129 | it('gets the node with biggest key less or equal k', () => { 130 | const lowerBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 131 | lowerBst.insert({ id: 20 }); 132 | lowerBst.insert({ id: 7 }); 133 | lowerBst.insert({ id: 15 }); 134 | lowerBst.insert({ id: 9 }); 135 | expect(lowerBst.lowerBoundKey(60).getValue()).to.eql({ id: 20 }); 136 | expect(lowerBst.floorKey(20, false).getValue()).to.eql({ id: 15 }); 137 | }); 138 | }); 139 | 140 | describe('.upperBound(k)', () => { 141 | it('gets the node with smallest key bigger than a key', () => { 142 | expect(bst.upperBound(75).getValue()).to.equal(80); 143 | expect(bst.upperBound(80).getValue()).to.equal(80); 144 | expect(bst.upperBound(80, false).getValue()).to.equal(90); 145 | }); 146 | 147 | it('returns null when k is bigger than all tree keys', () => { 148 | expect(bst.upperBound(110)).to.equal(null); 149 | }); 150 | 151 | it('returns the smallest upper bound of multiple upper bounds', () => { 152 | const upperBst = new BinarySearchTree(); 153 | upperBst.insert(-133195046); 154 | upperBst.insert(-49109668); 155 | upperBst.insert(115062875); 156 | upperBst.insert(-38206732); 157 | upperBst.insert(49311742); 158 | expect(upperBst.ceil(49303013).getValue()).to.equal(49311742); 159 | }); 160 | }); 161 | 162 | describe('.upperBoundKey(key) / ceilKey', () => { 163 | it('gets the node with smallest key bigger than a key', () => { 164 | const upperBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 165 | upperBst.insert({ id: 20 }); 166 | upperBst.insert({ id: 7 }); 167 | upperBst.insert({ id: 15 }); 168 | upperBst.insert({ id: 9 }); 169 | expect(upperBst.upperBoundKey(15).getValue()).to.eql({ id: 15 }); 170 | expect(upperBst.ceilKey(15, false).getValue()).to.eql({ id: 20 }); 171 | }); 172 | }); 173 | 174 | describe('.traverseInOrder(cb)', () => { 175 | it('traverse the tree in-order', () => { 176 | const keys = []; 177 | bst.traverseInOrder((node) => keys.push(node.getValue())); 178 | expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); 179 | }); 180 | 181 | it('traverse in order and allow aborting traversal', () => { 182 | const keys = []; 183 | let counter = 0; 184 | bst.traverseInOrder((node) => { 185 | keys.push(node.getValue()); 186 | counter += 1; 187 | }, () => counter > 2); 188 | expect(keys).to.deep.equal([20, 30, 40]); 189 | }); 190 | }); 191 | 192 | describe('.traversePreOrder(cb)', () => { 193 | it('traverse the tree pre-order', () => { 194 | const keys = []; 195 | bst.traversePreOrder((node) => keys.push(node.getValue())); 196 | expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); 197 | }); 198 | 199 | it('traverse pre order and allow aborting traversal', () => { 200 | const keys = []; 201 | let counter = 0; 202 | bst.traversePreOrder((node) => { 203 | keys.push(node.getValue()); 204 | counter += 1; 205 | }, () => counter > 2); 206 | expect(keys).to.deep.equal([50, 30, 20]); 207 | }); 208 | }); 209 | 210 | describe('.traversePostOrder(cb)', () => { 211 | it('traverse the tree post-order', () => { 212 | const keys = []; 213 | bst.traversePostOrder((node) => keys.push(node.getValue())); 214 | expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); 215 | }); 216 | 217 | it('traverse post order and allow aborting traversal', () => { 218 | const keys = []; 219 | let counter = 0; 220 | bst.traversePostOrder((node) => { 221 | keys.push(node.getValue()); 222 | counter += 1; 223 | }, () => counter > 2); 224 | expect(keys).to.deep.equal([20, 40, 30]); 225 | }); 226 | }); 227 | 228 | describe('.remove(value)', () => { 229 | it('should remove a leaf node', () => { 230 | bst.remove(20); 231 | expect(bst.has(20)).to.equal(false); 232 | expect(bst.find(30).getLeft()).to.equal(null); 233 | expect(bst.count()).to.equal(6); 234 | }); 235 | 236 | it('should remove a node with a right child only', () => { 237 | bst.remove(30); 238 | expect(bst.has(30)).to.equal(false); 239 | expect(bst.root().getLeft().getValue()).to.equal(40); 240 | expect(bst.count()).to.equal(5); 241 | }); 242 | 243 | it('should remove a node with a left child only', () => { 244 | bst.insert(30); 245 | bst.remove(40); 246 | expect(bst.has(40)).to.equal(false); 247 | expect(bst.root().getLeft().getValue()).to.equal(30); 248 | expect(bst.count()).to.equal(5); 249 | }); 250 | 251 | it('should remove a node with two children', () => { 252 | bst.remove(80); 253 | expect(bst.has(80)).to.equal(false); 254 | expect(bst.root().getRight().getValue()).to.equal(90); 255 | expect(bst.find(90).getRight()).to.equal(null); 256 | expect(bst.find(90).getLeft().getValue()).to.equal(60); 257 | expect(bst.count()).to.equal(4); 258 | }); 259 | 260 | it('should remove root node with right child', () => { 261 | bst.insert(100); 262 | bst.remove(60); 263 | bst.remove(90); 264 | bst.remove(30); 265 | bst.remove(50); 266 | expect(bst.root().getValue()).to.equal(100); 267 | }); 268 | 269 | it('should remove root node with left child', () => { 270 | bst.insert(20); 271 | bst.insert(30); 272 | bst.insert(25); 273 | bst.remove(30); 274 | bst.remove(25); 275 | bst.remove(100); 276 | expect(bst.root().getValue()).to.equal(20); 277 | }); 278 | 279 | it('should remove root node', () => { 280 | bst.remove(20); 281 | expect(bst.root()).to.equal(null); 282 | }); 283 | }); 284 | 285 | describe('.removeNode(node)', () => { 286 | const testRemoveTree = new BinarySearchTree(); 287 | testRemoveTree 288 | .insert(50) 289 | .insert(80) 290 | .insert(30) 291 | .insert(90) 292 | .insert(60) 293 | .insert(40) 294 | .insert(20); 295 | const n80 = testRemoveTree.find(80); 296 | testRemoveTree.removeNode(n80); 297 | expect(testRemoveTree.root().getRight().getValue()).to.equal(90); 298 | expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); 299 | expect(testRemoveTree.root().getRight().getRight()).to.equal(null); 300 | }); 301 | 302 | describe('.clear()', () => { 303 | bst.clear(); 304 | expect(bst.count()).to.equal(0); 305 | expect(bst.root()).to.equal(null); 306 | expect(bst.remove(10)).to.equal(false); 307 | }); 308 | }); 309 | -------------------------------------------------------------------------------- /test/binarySearchTree.iterative.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { BinarySearchTreeNode } = require('../src/binarySearchTreeNode'); 3 | const { BinarySearchTree } = require('../src/binarySearchTree'); 4 | 5 | describe('BinarySearchTree tests (iterative implementation)', () => { 6 | const bst = new BinarySearchTree(); 7 | 8 | describe('.insertIterative(value)', () => { 9 | it('should insert nodes to the tree', () => { 10 | expect(bst.insertIterative(50)).to.be.instanceof(BinarySearchTree); 11 | expect(bst.insertIterative(80)).to.be.instanceof(BinarySearchTree); 12 | expect(bst.insertIterative(30)).to.be.instanceof(BinarySearchTree); 13 | expect(bst.insertIterative(90)).to.be.instanceof(BinarySearchTree); 14 | expect(bst.insertIterative(60)).to.be.instanceof(BinarySearchTree); 15 | expect(bst.insertIterative(40)).to.be.instanceof(BinarySearchTree); 16 | expect(bst.insertIterative(20)).to.be.instanceof(BinarySearchTree); 17 | 18 | // updates value of existing node 19 | expect(bst.insertIterative(20)).to.be.instanceof(BinarySearchTree); 20 | expect(bst.findIterative(20).getValue()).to.equal(20); 21 | }); 22 | }); 23 | 24 | describe('.root()', () => { 25 | it('should get the root node', () => { 26 | expect(bst.root().getValue()).to.equal(50); 27 | expect(bst.root().getRight().getValue()).to.equal(80); 28 | expect(bst.root().getLeft().getValue()).to.equal(30); 29 | }); 30 | }); 31 | 32 | describe('.count()', () => { 33 | it('get the count of nodes in the tree', () => { 34 | expect(bst.count()).to.be.equal(7); 35 | }); 36 | }); 37 | 38 | describe('.hasIterative(value)', () => { 39 | it('checks if a node exists by value', () => { 40 | expect(bst.hasIterative(50)).to.equal(true); 41 | expect(bst.hasIterative(80)).to.equal(true); 42 | expect(bst.hasIterative(30)).to.equal(true); 43 | expect(bst.hasIterative(90)).to.equal(true); 44 | expect(bst.hasIterative(50)).to.equal(true); 45 | expect(bst.hasIterative(40)).to.equal(true); 46 | expect(bst.hasIterative(20)).to.equal(true); 47 | expect(bst.hasIterative(100)).to.equal(false); 48 | }); 49 | }); 50 | 51 | describe('.hasIterative(key)', () => { 52 | it('checks if a node exists by key', () => { 53 | const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 54 | testTree.insertIterative({ id: 1, name: 'a' }); 55 | testTree.insertIterative({ id: 2, name: 'b' }); 56 | testTree.insertIterative({ id: 3, name: 'c' }); 57 | expect(testTree.hasIterative({ id: 1 })).to.equal(true); 58 | expect(testTree.hasIterative({ id: 2 })).to.equal(true); 59 | expect(testTree.hasIterative({ id: 3 })).to.equal(true); 60 | expect(testTree.hasKey(1)).to.equal(true); 61 | expect(testTree.hasKey(2)).to.equal(true); 62 | expect(testTree.hasKey(3)).to.equal(true); 63 | }); 64 | }); 65 | 66 | describe('.findIterative(value)', () => { 67 | it('should search a node by its value in the tree', () => { 68 | expect(bst.findIterative(50)).to.be.instanceof(BinarySearchTreeNode); 69 | expect(bst.findIterative(80)).to.be.instanceof(BinarySearchTreeNode); 70 | expect(bst.findIterative(30)).to.be.instanceof(BinarySearchTreeNode); 71 | expect(bst.findIterative(90)).to.be.instanceof(BinarySearchTreeNode); 72 | expect(bst.findIterative(50)).to.be.instanceof(BinarySearchTreeNode); 73 | expect(bst.findIterative(40)).to.be.instanceof(BinarySearchTreeNode); 74 | expect(bst.findIterative(20)).to.be.instanceof(BinarySearchTreeNode); 75 | expect(bst.findIterative(100)).to.equal(null); 76 | }); 77 | }); 78 | 79 | describe('.findKeyIterative(key)', () => { 80 | it('should search a node by its key in the tree', () => { 81 | const testTree = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 82 | testTree.insertIterative({ id: 1, name: 'a' }); 83 | testTree.insertIterative({ id: 2, name: 'b' }); 84 | testTree.insertIterative({ id: 3, name: 'c' }); 85 | expect(testTree.findIterative({ id: 1 }).getValue()).to.eql({ id: 1, name: 'a' }); 86 | expect(testTree.findIterative({ id: 2 }).getValue()).to.eql({ id: 2, name: 'b' }); 87 | expect(testTree.findIterative({ id: 3 }).getValue()).to.eql({ id: 3, name: 'c' }); 88 | expect(testTree.findKey(1).getValue()).to.eql({ id: 1, name: 'a' }); 89 | expect(testTree.findKey(2).getValue()).to.eql({ id: 2, name: 'b' }); 90 | expect(testTree.findKey(3).getValue()).to.eql({ id: 3, name: 'c' }); 91 | }); 92 | }); 93 | 94 | describe('.maxIterative()', () => { 95 | it('get the node with max key', () => { 96 | const max = bst.maxIterative(); 97 | expect(max.getValue()).to.equal(90); 98 | }); 99 | }); 100 | 101 | describe('.minIterative()', () => { 102 | it('get the node with min key', () => { 103 | const min = bst.minIterative(); 104 | expect(min.getValue()).to.equal(20); 105 | }); 106 | }); 107 | 108 | describe('.lowerBoundIterative(value)', () => { 109 | it('gets the node with biggest key less or equal k', () => { 110 | expect(bst.lowerBoundIterative(60).getValue()).to.equal(60); 111 | expect(bst.lowerBoundIterative(60, false).getValue()).to.equal(50); 112 | }); 113 | 114 | it('returns null when k is less than all tree keys', () => { 115 | expect(bst.lowerBoundIterative(10)).to.equal(null); 116 | }); 117 | 118 | it('returns the biggest lower bound of multiple lower bounds', () => { 119 | const lowerBst = new BinarySearchTree(); 120 | lowerBst.insertIterative(20); 121 | lowerBst.insertIterative(7); 122 | lowerBst.insertIterative(15); 123 | lowerBst.insertIterative(9); 124 | expect(lowerBst.lowerBoundIterative(10).getValue()).to.equal(9); 125 | }); 126 | }); 127 | 128 | describe('.lowerBoundKeyIterative(key) / floorKeyIterative', () => { 129 | it('gets the node with biggest key less or equal k', () => { 130 | const lowerBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 131 | lowerBst.insertIterative({ id: 20 }); 132 | lowerBst.insertIterative({ id: 7 }); 133 | lowerBst.insertIterative({ id: 15 }); 134 | lowerBst.insertIterative({ id: 9 }); 135 | expect(lowerBst.lowerBoundKey(60).getValue()).to.eql({ id: 20 }); 136 | expect(lowerBst.floorKey(20, false).getValue()).to.eql({ id: 15 }); 137 | }); 138 | }); 139 | 140 | describe('.upperBoundIterative(k)', () => { 141 | it('gets the node with smallest key bigger than a key', () => { 142 | expect(bst.upperBoundIterative(75).getValue()).to.equal(80); 143 | expect(bst.upperBoundIterative(80).getValue()).to.equal(80); 144 | expect(bst.upperBoundIterative(80, false).getValue()).to.equal(90); 145 | }); 146 | 147 | it('returns null when k is bigger than all tree keys', () => { 148 | expect(bst.upperBoundIterative(110)).to.equal(null); 149 | }); 150 | 151 | it('returns the smallest upper bound of multiple upper bounds', () => { 152 | const upperBst = new BinarySearchTree(); 153 | upperBst.insertIterative(-133195046); 154 | upperBst.insertIterative(-49109668); 155 | upperBst.insertIterative(115062875); 156 | upperBst.insertIterative(-38206732); 157 | upperBst.insertIterative(49311742); 158 | expect(upperBst.upperBoundIterative(49303013).getValue()).to.equal(49311742); 159 | }); 160 | }); 161 | 162 | describe('.upperBoundKeyIterative(key) / ceilKeyIterative', () => { 163 | it('gets the node with smallest key bigger than a key', () => { 164 | const upperBst = new BinarySearchTree((a, b) => a.id - b.id, { key: 'id' }); 165 | upperBst.insertIterative({ id: 20 }); 166 | upperBst.insertIterative({ id: 7 }); 167 | upperBst.insertIterative({ id: 15 }); 168 | upperBst.insertIterative({ id: 9 }); 169 | expect(upperBst.upperBoundKey(15).getValue()).to.eql({ id: 15 }); 170 | expect(upperBst.ceilKey(15, false).getValue()).to.eql({ id: 20 }); 171 | }); 172 | }); 173 | 174 | describe('.traverseInOrderIterative(cb)', () => { 175 | it('traverse the tree in-order', () => { 176 | const keys = []; 177 | bst.traverseInOrderIterative((node) => keys.push(node.getValue())); 178 | expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); 179 | }); 180 | 181 | it('traverse in order and allow aborting traversal', () => { 182 | const keys = []; 183 | let counter = 0; 184 | bst.traverseInOrderIterative((node) => { 185 | keys.push(node.getValue()); 186 | counter += 1; 187 | }, () => counter > 2); 188 | expect(keys).to.deep.equal([20, 30, 40]); 189 | }); 190 | }); 191 | 192 | describe('.traversePreOrderIterative(cb)', () => { 193 | it('traverse the tree pre-order', () => { 194 | const keys = []; 195 | bst.traversePreOrderIterative((node) => keys.push(node.getValue())); 196 | expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); 197 | }); 198 | 199 | it('traverse pre order and allow aborting traversal', () => { 200 | const keys = []; 201 | let counter = 0; 202 | bst.traversePreOrderIterative((node) => { 203 | keys.push(node.getValue()); 204 | counter += 1; 205 | }, () => counter > 2); 206 | expect(keys).to.deep.equal([50, 30, 20]); 207 | }); 208 | }); 209 | 210 | describe('.traversePostOrderIterative(cb)', () => { 211 | it('traverse the tree post-order', () => { 212 | const keys = []; 213 | bst.traversePostOrderIterative((node) => keys.push(node.getValue())); 214 | expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); 215 | }); 216 | 217 | it('traverse post order and allow aborting traversal', () => { 218 | const keys = []; 219 | let counter = 0; 220 | bst.traversePostOrderIterative((node) => { 221 | keys.push(node.getValue()); 222 | counter += 1; 223 | }, () => counter > 2); 224 | expect(keys).to.deep.equal([20, 40, 30]); 225 | }); 226 | }); 227 | 228 | describe('.removeIterative(value)', () => { 229 | it('should remove a leaf node', () => { 230 | bst.removeIterative(20); 231 | expect(bst.hasIterative(20)).to.equal(false); 232 | expect(bst.findIterative(30).getLeft()).to.equal(null); 233 | expect(bst.count()).to.equal(6); 234 | }); 235 | 236 | it('should remove a node with a right child only', () => { 237 | bst.removeIterative(30); 238 | expect(bst.hasIterative(30)).to.equal(false); 239 | expect(bst.root().getLeft().getValue()).to.equal(40); 240 | expect(bst.count()).to.equal(5); 241 | }); 242 | 243 | it('should remove a node with a left child only', () => { 244 | bst.insertIterative(30); 245 | bst.removeIterative(40); 246 | expect(bst.hasIterative(40)).to.equal(false); 247 | expect(bst.root().getLeft().getValue()).to.equal(30); 248 | expect(bst.count()).to.equal(5); 249 | }); 250 | 251 | it('should remove a node with two children', () => { 252 | bst.removeIterative(80); 253 | expect(bst.hasIterative(80)).to.equal(false); 254 | expect(bst.root().getRight().getValue()).to.equal(90); 255 | expect(bst.findIterative(90).getRight()).to.equal(null); 256 | expect(bst.findIterative(90).getLeft().getValue()).to.equal(60); 257 | expect(bst.count()).to.equal(4); 258 | }); 259 | 260 | it('should remove root node with right child', () => { 261 | bst.insertIterative(100); 262 | bst.removeIterative(60); 263 | bst.removeIterative(90); 264 | bst.removeIterative(30); 265 | bst.removeIterative(50); 266 | expect(bst.root().getValue()).to.equal(100); 267 | }); 268 | 269 | it('should remove root node with left child', () => { 270 | bst.insertIterative(20); 271 | bst.insertIterative(30); 272 | bst.insertIterative(25); 273 | bst.removeIterative(30); 274 | bst.removeIterative(25); 275 | bst.removeIterative(100); 276 | expect(bst.root().getValue()).to.equal(20); 277 | }); 278 | 279 | it('should remove root node', () => { 280 | bst.removeIterative(20); 281 | expect(bst.root()).to.equal(null); 282 | }); 283 | }); 284 | 285 | describe('.removeNode(node)', () => { 286 | const testRemoveTree = new BinarySearchTree(); 287 | testRemoveTree 288 | .insertIterative(50) 289 | .insertIterative(80) 290 | .insertIterative(30) 291 | .insertIterative(90) 292 | .insertIterative(60) 293 | .insertIterative(40) 294 | .insertIterative(20); 295 | const n80 = testRemoveTree.findIterative(80); 296 | testRemoveTree.removeNode(n80); 297 | expect(testRemoveTree.root().getRight().getValue()).to.equal(90); 298 | expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); 299 | expect(testRemoveTree.root().getRight().getRight()).to.equal(null); 300 | }); 301 | 302 | describe('.clear()', () => { 303 | bst.clear(); 304 | expect(bst.count()).to.equal(0); 305 | expect(bst.root()).to.equal(null); 306 | expect(bst.removeIterative(10)).to.equal(false); 307 | }); 308 | }); 309 | -------------------------------------------------------------------------------- /src/binarySearchTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/binary-search-tree 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | const { BinarySearchTreeNode } = require('./binarySearchTreeNode'); 8 | 9 | const defaultCompare = (a, b) => { 10 | if (a === b) return 0; 11 | return a > b ? 1 : -1; 12 | }; 13 | 14 | /** 15 | * @class BinarySearchTree 16 | */ 17 | class BinarySearchTree { 18 | constructor(compare, options) { 19 | if (compare && typeof compare !== 'function') { 20 | throw new Error('BinarySearchTree constructor expects a compare function'); 21 | } 22 | 23 | this._compare = compare || defaultCompare; 24 | this._options = options || {}; 25 | this._root = null; 26 | this._count = 0; 27 | } 28 | 29 | /** 30 | * Inserts a node with a key/value into the tree (recursive implementation) 31 | * @public 32 | * @param {number|string|object} value 33 | * @return {BinarySearchTree} 34 | */ 35 | insert(value) { 36 | const newNode = new BinarySearchTreeNode(value); 37 | const insertRecursive = (current) => { 38 | const compare = this._compare(newNode.getValue(), current.getValue()); 39 | if (compare < 0) { 40 | if (current.hasLeft()) { 41 | insertRecursive(current.getLeft()); 42 | } else { 43 | current.setLeft(newNode.setParent(current)); 44 | this._count += 1; 45 | } 46 | } else if (compare > 0) { 47 | if (current.hasRight()) { 48 | insertRecursive(current.getRight()); 49 | } else { 50 | current.setRight(newNode.setParent(current)); 51 | this._count += 1; 52 | } 53 | } else { 54 | current.setValue(value); 55 | } 56 | }; 57 | 58 | if (this._root === null) { 59 | this._root = newNode; 60 | this._count += 1; 61 | } else { 62 | insertRecursive(this._root); 63 | } 64 | 65 | return this; 66 | } 67 | 68 | /** 69 | * Inserts a node with a key/value into the tree (iterative implementation) 70 | * @public 71 | * @param {number|string|object} value 72 | * @return {BinarySearchTree} 73 | */ 74 | insertIterative(value) { 75 | const newNode = new BinarySearchTreeNode(value); 76 | let node = this._root; 77 | if (!node) { 78 | this._root = newNode; 79 | this._count += 1; 80 | } else { 81 | let inserted = false; 82 | while (!inserted) { 83 | const compare = this._compare(newNode.getValue(), node.getValue()); 84 | 85 | if (compare < 0) { 86 | if (node.hasLeft()) { 87 | node = node.getLeft(); 88 | } else { 89 | node.setLeft(newNode.setParent(node)); 90 | this._count += 1; 91 | inserted = true; 92 | } 93 | } else if (compare > 0) { 94 | if (node.hasRight()) { 95 | node = node.getRight(); 96 | } else { 97 | node.setRight(newNode.setParent(node)); 98 | this._count += 1; 99 | inserted = true; 100 | } 101 | } else { 102 | node.setValue(value); 103 | inserted = true; 104 | } 105 | } 106 | } 107 | return this; 108 | } 109 | 110 | /** 111 | * Checks if a value exists in the tree by its value (recursive implementation) 112 | * @public 113 | * @param {number|string|object} value 114 | * @return {boolean} 115 | */ 116 | has(value) { 117 | const hasRecursive = (current) => { 118 | if (current === null) return false; 119 | 120 | const compare = this._compare(value, current.getValue()); 121 | if (compare === 0) return true; 122 | if (compare < 0) return hasRecursive(current.getLeft()); 123 | return hasRecursive(current.getRight()); 124 | }; 125 | 126 | return hasRecursive(this._root); 127 | } 128 | 129 | /** 130 | * Checks if a value exists in the tree by its value (iterative implementation) 131 | * @public 132 | * @param {number|string|object} value 133 | * @return {boolean} 134 | */ 135 | hasIterative(value) { 136 | let current = this._root; 137 | while (current !== null) { 138 | const compare = this._compare(value, current.getValue()); 139 | 140 | if (compare === 0) { 141 | return true; 142 | } 143 | if (compare < 0) { 144 | current = current.getLeft(); 145 | } else { 146 | current = current.getRight(); 147 | } 148 | } 149 | return false; 150 | } 151 | 152 | /** 153 | * Checks if a value exists in the tree by its key 154 | * @public 155 | * @param {number|string} key 156 | * @return {boolean} 157 | */ 158 | hasKey(key) { 159 | if (this._options.key === undefined || this._options.key === null) { 160 | throw new Error('Missing key prop name in constructor options'); 161 | } 162 | return this.has({ [this._options.key]: key }); 163 | } 164 | 165 | /** 166 | * Finds a node by its value (recursive implementation) 167 | * @public 168 | * @param {number|string|object} value 169 | * @return {BinarySearchTreeNode} 170 | */ 171 | find(value) { 172 | const findRecursive = (current) => { 173 | if (current === null) return null; 174 | 175 | const compare = this._compare(value, current.getValue()); 176 | if (compare === 0) return current; 177 | if (compare < 0) return findRecursive(current.getLeft()); 178 | return findRecursive(current.getRight()); 179 | }; 180 | 181 | return findRecursive(this._root); 182 | } 183 | 184 | /** 185 | * Finds a node by its value (iterative implementation) 186 | * @public 187 | * @param {number|string|object} value 188 | * @return {BinarySearchTreeNode} 189 | */ 190 | findIterative(value) { 191 | let current = this._root; 192 | 193 | while (current !== null) { 194 | const compare = this._compare(value, current.getValue()); 195 | 196 | if (compare === 0) { 197 | return current; 198 | } 199 | if (compare < 0) { 200 | current = current.getLeft(); 201 | } else { 202 | current = current.getRight(); 203 | } 204 | } 205 | 206 | return null; 207 | } 208 | 209 | /** 210 | * Finds a node by its object's key 211 | * @public 212 | * @param {number|string} key 213 | * @return {BinarySearchTreeNode} 214 | */ 215 | findKey(key) { 216 | if (this._options.key === undefined || this._options.key === null) { 217 | throw new Error('Missing key prop name in constructor options'); 218 | } 219 | return this.find({ [this._options.key]: key }); 220 | } 221 | 222 | /** 223 | * Finds the node with max key (most right) in the tree (recursive implementation) 224 | * @public 225 | * @param {BinarySearchTreeNode} [current] 226 | * @return {BinarySearchTreeNode} 227 | */ 228 | max(current = this._root) { 229 | if (current === null) return null; 230 | if (current.hasRight()) return this.max(current.getRight()); 231 | return current; 232 | } 233 | 234 | /** 235 | * Finds the node with max key (most right) in the tree (iterative implementation) 236 | * @public 237 | * @param {BinarySearchTreeNode} [current] 238 | * @return {BinarySearchTreeNode} 239 | */ 240 | maxIterative(current = this._root) { 241 | if (current === null) return null; 242 | let node = current; 243 | while (node.hasRight()) { 244 | node = node.getRight(); 245 | } 246 | return node; 247 | } 248 | 249 | /** 250 | * Finds the node with min key (most left) in the tree (recursive implementation) 251 | * @public 252 | * @param {BinarySearchTreeNode} [current] 253 | * @return {BinarySearchTreeNode} 254 | */ 255 | min(current = this._root) { 256 | if (current === null) return null; 257 | if (current.hasLeft()) return this.min(current.getLeft()); 258 | return current; 259 | } 260 | 261 | /** 262 | * Finds the node with min key (most left) in the tree (iterative implementation) 263 | * @public 264 | * @param {BinarySearchTreeNode} [current] 265 | * @return {BinarySearchTreeNode} 266 | */ 267 | minIterative(current = this._root) { 268 | if (current === null) return null; 269 | let node = current; 270 | while (node.hasLeft()) { 271 | node = node.getLeft(); 272 | } 273 | return node; 274 | } 275 | 276 | /** 277 | * Returns the node with the biggest value less or equal a given value (recursive implementation) 278 | * @public 279 | * @param {number|string|object} value 280 | * @param {boolean} includeEqual 281 | * @return {BinarySearchTreeNode|null} 282 | */ 283 | lowerBound(value, includeEqual = true) { 284 | let lowerBound = null; 285 | 286 | const lowerBoundRecursive = (current) => { 287 | if (current === null) return lowerBound; 288 | 289 | const compare = this._compare(value, current.getValue()); 290 | if (compare > 0 || (includeEqual && compare === 0)) { 291 | if (lowerBound === null || this._compare(lowerBound.getValue(), current.getValue()) <= 0) { 292 | lowerBound = current; 293 | } 294 | return lowerBoundRecursive(current.getRight()); 295 | } 296 | 297 | return lowerBoundRecursive(current.getLeft()); 298 | }; 299 | 300 | return lowerBoundRecursive(this._root); 301 | } 302 | 303 | /** 304 | * Returns the node with the biggest value less or equal a given value (iterative implementation) 305 | * @public 306 | * @param {number|string|object} value 307 | * @param {boolean} includeEqual 308 | * @return {BinarySearchTreeNode|null} 309 | */ 310 | lowerBoundIterative(value, includeEqual = true) { 311 | let lowerBound = null; 312 | let current = this._root; 313 | 314 | while (current !== null) { 315 | const compare = this._compare(value, current.getValue()); 316 | 317 | if (compare > 0 || (includeEqual && compare === 0)) { 318 | if (lowerBound === null || this._compare(lowerBound.getValue(), current.getValue()) < 0) { 319 | lowerBound = current; 320 | } 321 | current = current.getRight(); 322 | } else { 323 | current = current.getLeft(); 324 | } 325 | } 326 | 327 | return lowerBound; 328 | } 329 | 330 | /** 331 | * Returns the node with the biggest object's key less or equal a given key 332 | * @public 333 | * @param {number|string} key 334 | * @param {boolean} includeEqual 335 | * @return {BinarySearchTreeNode|null} 336 | */ 337 | lowerBoundKey(key, includeEqual = true) { 338 | if (this._options.key === undefined || this._options.key === null) { 339 | throw new Error('Missing key prop name in constructor options'); 340 | } 341 | 342 | return this.lowerBound({ [this._options.key]: key }, includeEqual); 343 | } 344 | 345 | /** 346 | * Returns the node with the biggest value less or equal a given value 347 | * @public 348 | * @param {number|string|object} value 349 | * @param {boolean} includeEqual 350 | * @return {BinarySearchTreeNode|null} 351 | */ 352 | floor(value, includeEqual = true) { 353 | return this.lowerBound(value, includeEqual); 354 | } 355 | 356 | /** 357 | * Returns the node with the biggest object's key less or equal a given value 358 | * @public 359 | * @param {number|string} value 360 | * @param {boolean} includeEqual 361 | * @return {BinarySearchTreeNode|null} 362 | */ 363 | floorKey(key, includeEqual = true) { 364 | return this.lowerBoundKey(key, includeEqual); 365 | } 366 | 367 | /** 368 | * Returns the node with the smallest value greater or equal a given value (recursive implementation) 369 | * @public 370 | * @param {number|string|object} value 371 | * @param {boolean} includeEqual 372 | * @return {BinarySearchTreeNode|null} 373 | */ 374 | upperBound(value, includeEqual = true) { 375 | let upperBound = null; 376 | 377 | const upperBoundRecursive = (current) => { 378 | if (current === null) return upperBound; 379 | 380 | const compare = this._compare(value, current.getValue()); 381 | if (compare < 0 || (includeEqual && compare === 0)) { 382 | if (upperBound === null || this._compare(upperBound.getValue(), current.getValue()) >= 0) { 383 | upperBound = current; 384 | } 385 | return upperBoundRecursive(current.getLeft()); 386 | } 387 | 388 | return upperBoundRecursive(current.getRight()); 389 | }; 390 | 391 | return upperBoundRecursive(this._root); 392 | } 393 | 394 | /** 395 | * Returns the node with the smallest value greater or equal a given value (iterative implementation) 396 | * @public 397 | * @param {number|string|object} value 398 | * @param {boolean} includeEqual 399 | * @return {BinarySearchTreeNode|null} 400 | */ 401 | upperBoundIterative(value, includeEqual = true) { 402 | let upperBound = null; 403 | let current = this._root; 404 | 405 | while (current !== null) { 406 | const compare = this._compare(value, current.getValue()); 407 | 408 | if (compare < 0 || (includeEqual && compare === 0)) { 409 | if (upperBound === null || this._compare(upperBound.getValue(), current.getValue()) > 0) { 410 | upperBound = current; 411 | } 412 | current = current.getLeft(); 413 | } else { 414 | current = current.getRight(); 415 | } 416 | } 417 | 418 | return upperBound; 419 | } 420 | 421 | /** 422 | * Returns the node with the smallest object's key greater or equal a given key 423 | * @public 424 | * @param {number|string} key 425 | * @param {boolean} includeEqual 426 | * @return {BinarySearchTreeNode|null} 427 | */ 428 | upperBoundKey(key, includeEqual = true) { 429 | if (this._options.key === undefined || this._options.key === null) { 430 | throw new Error('Missing key prop name in constructor options'); 431 | } 432 | 433 | return this.upperBound({ [this._options.key]: key }, includeEqual); 434 | } 435 | 436 | /** 437 | * Returns the node with the smallest value greater or equal a given value 438 | * @public 439 | * @param {number|string|object} value 440 | * @param {boolean} includeEqual 441 | * @return {BinarySearchTreeNode|null} 442 | */ 443 | ceil(value, includeEqual = true) { 444 | return this.upperBound(value, includeEqual); 445 | } 446 | 447 | /** 448 | * Returns the node with the smallest object's key greater or equal a given key 449 | * @public 450 | * @param {number|string} key 451 | * @param {boolean} includeEqual 452 | * @return {BinarySearchTreeNode|null} 453 | */ 454 | ceilKey(key, includeEqual = true) { 455 | return this.upperBoundKey(key, includeEqual); 456 | } 457 | 458 | /** 459 | * Returns the root node 460 | * @public 461 | * @return {BinarySearchTreeNode} 462 | */ 463 | root() { 464 | return this._root; 465 | } 466 | 467 | /** 468 | * Returns the nodes count 469 | * @public 470 | * @return {number} 471 | */ 472 | count() { 473 | return this._count; 474 | } 475 | 476 | /** 477 | * Removes a node by its value (recursive implementation) 478 | * @public 479 | * @param {number|string|object} value 480 | * @return {boolean} 481 | */ 482 | remove(value) { 483 | const removeRecursively = (val, current) => { 484 | if (current === null) return false; 485 | 486 | const compare = this._compare(val, current.getValue()); 487 | if (compare < 0) return removeRecursively(val, current.getLeft()); 488 | if (compare > 0) return removeRecursively(val, current.getRight()); 489 | 490 | return this.removeNode(current); 491 | }; 492 | 493 | return removeRecursively(value, this._root); 494 | } 495 | 496 | /** 497 | * Removes a node by its value (iterative implementation) 498 | * @public 499 | * @param {number|string|object} value 500 | * @return {boolean} 501 | */ 502 | removeIterative(value) { 503 | let current = this._root; 504 | 505 | while (current !== null) { 506 | const compare = this._compare(value, current.getValue()); 507 | 508 | if (compare === 0) { 509 | this.removeNode(current); 510 | return true; 511 | } 512 | if (compare < 0) { 513 | current = current.getLeft(); 514 | } else { 515 | current = current.getRight(); 516 | } 517 | } 518 | 519 | return false; 520 | } 521 | 522 | /** 523 | * Removes a node from the tree 524 | * @public 525 | * @param {BinarySearchTreeNode} node 526 | * @return {boolean} 527 | */ 528 | removeNode(node) { 529 | if (node === null || !(node instanceof BinarySearchTreeNode)) { 530 | return false; 531 | } 532 | 533 | // case 1: node has no children 534 | if (node.isLeaf()) { 535 | if (node.isRoot()) { 536 | this._root = null; 537 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 538 | node.getParent().setLeft(null); 539 | } else { 540 | node.getParent().setRight(null); 541 | } 542 | this._count -= 1; 543 | return true; 544 | } 545 | 546 | // case 2: node has a left child and no right child 547 | if (!node.hasRight()) { 548 | if (node.isRoot()) { 549 | this._root = node.getLeft(); 550 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 551 | node.getParent().setLeft(node.getLeft()); 552 | } else { 553 | node.getParent().setRight(node.getLeft()); 554 | } 555 | node.getLeft().setParent(node.getParent()); 556 | this._count -= 1; 557 | return true; 558 | } 559 | 560 | // case 3: node has a right child and no left child 561 | if (!node.hasLeft()) { 562 | if (node.isRoot()) { 563 | this._root = node.getRight(); 564 | } else if (this._compare(node.getValue(), node.getParent().getValue()) < 0) { 565 | node.getParent().setLeft(node.getRight()); 566 | } else { 567 | node.getParent().setRight(node.getRight()); 568 | } 569 | node.getRight().setParent(node.getParent()); 570 | this._count -= 1; 571 | return true; 572 | } 573 | 574 | // case 4: node has left and right children 575 | const minRight = this.min(node.getRight()); 576 | node.setValue(minRight.getValue()); 577 | return this.removeNode(minRight); 578 | } 579 | 580 | /** 581 | * Traverses the tree in-order (left-node-right) (recursive implementation) 582 | * @public 583 | * @param {function} cb 584 | * @param {function} [abortCb] 585 | */ 586 | traverseInOrder(cb, abortCb) { 587 | if (typeof cb !== 'function') { 588 | throw new Error('.traverseInOrder expects a callback function'); 589 | } 590 | 591 | const traverseRecursive = (current) => { 592 | if (current === null || (abortCb && abortCb())) return; 593 | traverseRecursive(current.getLeft()); 594 | if (abortCb && abortCb()) return; 595 | cb(current); 596 | traverseRecursive(current.getRight()); 597 | }; 598 | 599 | traverseRecursive(this._root); 600 | } 601 | 602 | /** 603 | * Traverses the tree in-order (left-node-right) (iterative implementation) 604 | * @public 605 | * @param {function} cb 606 | * @param {function} [abortCb] 607 | */ 608 | traverseInOrderIterative(cb, abortCb) { 609 | if (typeof cb !== 'function') { 610 | throw new Error('.traverseInOrderIterative expects a callback function'); 611 | } 612 | 613 | let current = this._root; 614 | const stack = []; 615 | 616 | while (current !== null || stack.length > 0) { 617 | while (current !== null) { 618 | stack.push(current); 619 | current = current.getLeft(); 620 | } 621 | current = stack.pop(); 622 | 623 | if (abortCb && abortCb()) { 624 | return; 625 | } 626 | 627 | cb(current); 628 | 629 | current = current.getRight(); 630 | } 631 | } 632 | 633 | /** 634 | * Traverses the tree pre-order (node-left-right) (recursive implementation) 635 | * @public 636 | * @param {function} cb 637 | * @param {function} [abortCb] 638 | */ 639 | traversePreOrder(cb, abortCb) { 640 | if (typeof cb !== 'function') { 641 | throw new Error('.traversePreOrder expects a callback function'); 642 | } 643 | 644 | const traverseRecursive = (current) => { 645 | if (current === null || (abortCb && abortCb())) return; 646 | cb(current); 647 | traverseRecursive(current.getLeft()); 648 | traverseRecursive(current.getRight()); 649 | }; 650 | 651 | traverseRecursive(this._root); 652 | } 653 | 654 | /** 655 | * Traverses the tree pre-order (node-left-right) (iterative implementation) 656 | * @public 657 | * @param {function} cb 658 | * @param {function} [abortCb] 659 | */ 660 | traversePreOrderIterative(cb, abortCb) { 661 | if (typeof cb !== 'function') { 662 | throw new Error('.traversePreOrderIterative expects a callback function'); 663 | } 664 | 665 | const stack = [this._root]; 666 | 667 | while (stack.length) { 668 | const current = stack.pop(); 669 | 670 | if (abortCb && abortCb()) break; 671 | if (current) { 672 | cb(current); 673 | stack.push(current.getRight()); 674 | stack.push(current.getLeft()); 675 | } 676 | } 677 | } 678 | 679 | /** 680 | * Traverses the tree post-order (left-right-node) (recursive implementation) 681 | * @public 682 | * @param {function} cb 683 | * @param {function} [abortCb] 684 | */ 685 | traversePostOrder(cb, abortCb) { 686 | if (typeof cb !== 'function') { 687 | throw new Error('.traversePostOrder expects a callback function'); 688 | } 689 | 690 | const traverseRecursive = (current) => { 691 | if (current === null || (abortCb && abortCb())) return; 692 | traverseRecursive(current.getLeft()); 693 | traverseRecursive(current.getRight()); 694 | if (abortCb && abortCb()) return; 695 | cb(current); 696 | }; 697 | 698 | traverseRecursive(this._root); 699 | } 700 | 701 | /** 702 | * Traverses the tree post-order (left-right-node) (iterative implementation) 703 | * @public 704 | * @param {function} cb 705 | * @param {function} [abortCb] 706 | */ 707 | traversePostOrderIterative(cb, abortCb) { 708 | if (typeof cb !== 'function') { 709 | throw new Error('.traversePostOrderIterative expects a callback function'); 710 | } 711 | 712 | const s1 = []; 713 | const s2 = []; 714 | s1.push(this._root); 715 | 716 | while (s1.length) { 717 | const current = s1.pop(); 718 | 719 | s2.push(current); 720 | 721 | if (abortCb && abortCb()) break; 722 | if (current) { 723 | s1.push(current.getLeft()); 724 | s1.push(current.getRight()); 725 | } 726 | } 727 | 728 | while (s2.length) { 729 | const current = s2.pop(); 730 | if (abortCb && abortCb()) break; 731 | if (current) { 732 | cb(current); 733 | } 734 | } 735 | } 736 | 737 | /** 738 | * Clears the tree 739 | * @public 740 | */ 741 | clear() { 742 | this._root = null; 743 | this._count = 0; 744 | } 745 | } 746 | 747 | exports.BinarySearchTree = BinarySearchTree; 748 | -------------------------------------------------------------------------------- /test/avlTree.recursive.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { AvlTree } = require('../src/avlTree'); 3 | 4 | describe('AvlTree tests (recursive implementation)', () => { 5 | const avlTree = new AvlTree(); 6 | 7 | describe('.insert(value)', () => { 8 | it('left rotation balancing', () => { 9 | avlTree.insert(50); 10 | avlTree.insert(80); 11 | avlTree.insert(90); 12 | /* 13 | 50 (balance = -2) 14 | \ 15 | 80 16 | \ 17 | 90 18 | 19 | lef-rotation of 50 to ==> 20 | 21 | 80 22 | / \ 23 | 50 90 24 | */ 25 | const root = avlTree.root(); 26 | expect(root.getValue()).to.equal(80); 27 | 28 | expect(root.getRight().getValue()).to.equal(90); 29 | expect(root.getRight().getParent().getValue()).to.equal(80); 30 | 31 | expect(root.getLeft().getValue()).to.equal(50); 32 | expect(root.getLeft().getParent().getValue()).to.equal(80); 33 | }); 34 | 35 | it('right rotation balancing', () => { 36 | avlTree.insert(40); 37 | avlTree.insert(30); 38 | 39 | /* 40 | 80 41 | / \ 42 | (balance = 2) 50 90 43 | / 44 | 40 45 | / 46 | 30 47 | 48 | right-rotation of 50 to ==> 49 | 50 | 80 51 | / \ 52 | 40 90 53 | / \ 54 | 30 50 55 | */ 56 | const root = avlTree.root(); 57 | expect(root.getValue()).to.equal(80); 58 | 59 | expect(root.getRight().getValue()).to.equal(90); 60 | expect(root.getRight().getParent().getValue()).to.equal(80); 61 | 62 | expect(root.getLeft().getValue()).to.equal(40); 63 | expect(root.getLeft().getParent().getValue()).to.equal(80); 64 | 65 | expect(root.getLeft().getRight().getValue()).to.equal(50); 66 | expect(root.getLeft().getRight().getParent().getValue()).to.equal(40); 67 | 68 | expect(root.getLeft().getLeft().getValue()).to.equal(30); 69 | expect(root.getLeft().getLeft().getParent().getValue()).to.equal(40); 70 | 71 | avlTree.insert(20); 72 | /* 73 | 80 (balance = 2) 74 | / \ 75 | 40 90 76 | / \ 77 | 30 50 78 | / 79 | 20 80 | 81 | right-rotation of 80 ==> 82 | 83 | 40 84 | / \ 85 | 30 80 86 | / / \ 87 | 20 50 90 88 | */ 89 | expect(avlTree.root().getValue()).to.equal(40); 90 | 91 | expect(avlTree.root().getLeft().getValue()).to.equal(30); 92 | expect(avlTree.root().getLeft().getLeft().getValue()).to.equal(20); 93 | 94 | expect(avlTree.root().getRight().getValue()).to.equal(80); 95 | expect(avlTree.root().getRight().getRight().getValue()).to.equal(90); 96 | expect(avlTree.root().getRight().getLeft().getValue()).to.equal(50); 97 | }); 98 | 99 | it('left-right rotation balancing', () => { 100 | avlTree.insert(35); 101 | avlTree.insert(10); 102 | avlTree.insert(15); 103 | /* 104 | verify left-right rotation 105 | 40 106 | / \ 107 | 30 80 108 | / \ / \ 109 | 20 35 50 90 110 | / 111 | 10 112 | \ 113 | 15 114 | 115 | left-right rotation of 20 ==> 116 | 40 117 | / \ 118 | 30 80 119 | / \ / \ 120 | 15 35 50 90 121 | / \ 122 | 10 20 123 | */ 124 | const root = avlTree.root(); 125 | expect(root.getValue()).to.equal(40); 126 | 127 | expect(root.getRight().getValue()).to.equal(80); 128 | expect(root.getRight().getRight().getValue()).to.equal(90); 129 | expect(root.getRight().getLeft().getValue()).to.equal(50); 130 | 131 | expect(root.getLeft().getValue()).to.equal(30); 132 | expect(root.getLeft().getRight().getValue()).to.equal(35); 133 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 134 | expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); 135 | expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); 136 | }); 137 | 138 | it('right-left rotation balancing', () => { 139 | avlTree.insert(100); 140 | avlTree.insert(95); 141 | /* 142 | verify right-left rotation 143 | 40 144 | / \ 145 | 30 80 146 | / \ / \ 147 | 15 35 50 90 148 | / \ \ 149 | 10 20 100 150 | / 151 | 95 152 | 153 | right-left rotation of 90 ==> 154 | 155 | 40 156 | / \ 157 | 30 80 158 | / \ / \ 159 | 15 35 50 95 160 | / \ / \ 161 | 10 20 90 100 162 | */ 163 | const root = avlTree.root(); 164 | expect(root.getValue()).to.equal(40); 165 | 166 | expect(root.getRight().getValue()).to.equal(80); 167 | expect(root.getRight().getRight().getValue()).to.equal(95); 168 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 169 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 170 | expect(root.getRight().getLeft().getValue()).to.equal(50); 171 | 172 | expect(root.getLeft().getValue()).to.equal(30); 173 | expect(root.getLeft().getRight().getValue()).to.equal(35); 174 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 175 | expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); 176 | expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); 177 | }); 178 | 179 | it('keep balance when inserting so many elements', () => { 180 | const elements = [ 181 | 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, 182 | 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, 183 | 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, 184 | 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, 185 | 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, 186 | 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, 187 | 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, 188 | 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, 189 | 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, 190 | 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, 191 | 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, 192 | 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, 193 | 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, 194 | 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, 195 | 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, 196 | 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, 197 | 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, 198 | 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, 199 | 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, 200 | 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, 201 | 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, 202 | 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, 203 | 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, 204 | 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, 205 | 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, 206 | 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, 207 | 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, 208 | 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, 209 | 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, 210 | 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, 211 | 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, 212 | 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, 213 | 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, 214 | 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, 215 | 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, 216 | 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, 217 | 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, 218 | 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, 219 | 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, 220 | 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, 221 | 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, 222 | 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, 223 | 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, 224 | 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, 225 | 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, 226 | 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, 227 | 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, 228 | 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, 229 | 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, 230 | 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, 231 | 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, 232 | 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, 233 | 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, 234 | 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, 235 | 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, 236 | 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, 237 | 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, 238 | 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, 239 | 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, 240 | 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, 241 | 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, 242 | 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 243 | ]; 244 | 245 | const tree = new AvlTree(); 246 | elements.forEach((n) => tree.insert(n)); 247 | tree.traversePreOrder((node) => { 248 | const balance = node.getBalance(); 249 | if (balance > 1 || balance < -1) { 250 | throw new Error(`not balance , balance is ${balance}`); 251 | } 252 | }); 253 | }); 254 | }); 255 | 256 | describe('.min()', () => { 257 | it('get the node with min value', () => { 258 | expect(avlTree.min().getValue(15)); 259 | }); 260 | }); 261 | 262 | describe('.max()', () => { 263 | it('get the node with min value', () => { 264 | expect(avlTree.max().getValue(100)); 265 | }); 266 | }); 267 | 268 | describe('.root()', () => { 269 | it('should get root node', () => { 270 | expect(avlTree.root().getValue(40)); 271 | }); 272 | }); 273 | 274 | describe('.find(value)', () => { 275 | it('find a node by its value', () => { 276 | expect(avlTree.find(35).getValue()).to.equal(35); 277 | expect(avlTree.find(1000)).to.equal(null); 278 | }); 279 | }); 280 | 281 | describe('.traverseInOrder(cb)', () => { 282 | it('traverse the tree in order', () => { 283 | const values = []; 284 | avlTree.traverseInOrder((node) => values.push(node.getValue())); 285 | expect(values).to.deep.equal([ 286 | 10, 15, 20, 30, 35, 40, 50, 80, 90, 95, 100 287 | ]); 288 | }); 289 | }); 290 | 291 | describe('.traversePreOrder(cb)', () => { 292 | it('traverse the tree in order', () => { 293 | const values = []; 294 | avlTree.traversePreOrder((node) => values.push(node.getValue())); 295 | expect(values).to.deep.equal([ 296 | 40, 30, 15, 10, 20, 35, 80, 50, 95, 90, 100 297 | ]); 298 | }); 299 | }); 300 | 301 | describe('.traversePostOrder(cb)', () => { 302 | it('traverse the tree post order', () => { 303 | const values = []; 304 | avlTree.traversePostOrder((node) => values.push(node.getValue())); 305 | expect(values).to.deep.equal([ 306 | 10, 20, 15, 35, 30, 50, 90, 100, 95, 80, 40 307 | ]); 308 | }); 309 | }); 310 | 311 | describe('.remove(value)', () => { 312 | it('right rotation balancing', () => { 313 | /* 314 | 40 315 | / \ 316 | 30 80 317 | / \ / \ 318 | 15 35 50 95 319 | / \ / \ 320 | 10 20 90 100 321 | */ 322 | 323 | avlTree.remove(35); 324 | 325 | /* 326 | 40 327 | / \ 328 | (balance = 2) 30 80 329 | / / \ 330 | 15 50 95 331 | / \ / \ 332 | 10 20 90 100 333 | 334 | right rotation of 30 ==> 335 | 336 | 40 337 | / \ 338 | 15 80 339 | / \ / \ 340 | 10 30 50 95 341 | / / \ 342 | 20 90 100 343 | */ 344 | const root = avlTree.root(); 345 | expect(root.getValue()).to.equal(40); 346 | expect(root.getRight().getValue()).to.equal(80); 347 | expect(root.getRight().getRight().getValue()).to.equal(95); 348 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 349 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 350 | expect(root.getRight().getLeft().getValue()).to.equal(50); 351 | expect(root.getLeft().getValue()).to.equal(15); 352 | expect(root.getLeft().getRight().getValue()).to.equal(30); 353 | expect(root.getLeft().getRight().getLeft().getValue()).to.equal(20); 354 | expect(root.getLeft().getLeft().getValue()).to.equal(10); 355 | }); 356 | 357 | it('right-left rotation balancing', () => { 358 | /* 359 | 40 360 | / \ 361 | 15 80 362 | / \ / \ 363 | 10 30 50 95 364 | / / \ 365 | 20 90 100 366 | */ 367 | 368 | avlTree.remove(10); 369 | 370 | /* 371 | 40 372 | / \ 373 | (balance = -2) 15 80 374 | \ / \ 375 | 30 50 95 376 | / / \ 377 | 20 90 100 378 | 379 | right-left rotation of 15 ==> 380 | 381 | 40 382 | / \ 383 | 20 80 384 | / \ / \ 385 | 15 30 50 95 386 | / \ 387 | 90 100 388 | */ 389 | const root = avlTree.root(); 390 | expect(root.getValue()).to.equal(40); 391 | expect(root.getRight().getValue()).to.equal(80); 392 | expect(root.getRight().getRight().getValue()).to.equal(95); 393 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 394 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 395 | expect(root.getRight().getLeft().getValue()).to.equal(50); 396 | expect(root.getLeft().getValue()).to.equal(20); 397 | expect(root.getLeft().getRight().getValue()).to.equal(30); 398 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 399 | }); 400 | 401 | it('left rotation balancing', () => { 402 | /* 403 | 404 | 40 405 | / \ 406 | 20 80 407 | / \ / \ 408 | 15 30 50 95 409 | / \ 410 | 90 100 411 | */ 412 | 413 | avlTree.remove(90); 414 | avlTree.remove(50); 415 | 416 | /* 417 | 40 418 | / \ 419 | 20 80 (balance = -2) 420 | / \ \ 421 | 15 30 95 422 | \ 423 | 100 424 | 425 | left rotation of 15 ==> 426 | 427 | 40 428 | / \ 429 | 20 95 430 | / \ / \ 431 | 15 30 80 100 432 | */ 433 | expect(avlTree.root().getValue()).to.equal(40); 434 | expect(avlTree.root().getRight().getValue()).to.equal(95); 435 | expect(avlTree.root().getRight().getRight().getValue()).to.equal(100); 436 | expect(avlTree.root().getRight().getLeft().getValue()).to.equal(80); 437 | expect(avlTree.root().getLeft().getValue()).to.equal(20); 438 | expect(avlTree.root().getLeft().getRight().getValue()).to.equal(30); 439 | expect(avlTree.root().getLeft().getLeft().getValue()).to.equal(15); 440 | }); 441 | 442 | it('left-right rotation balancing', () => { 443 | avlTree.insert(85); 444 | /* 445 | 40 446 | / \ 447 | 20 95 448 | / \ / \ 449 | 15 30 80 100 450 | \ 451 | 85 452 | */ 453 | 454 | avlTree.remove(100); 455 | /* 456 | 40 457 | / \ 458 | 20 95 (balance = 2) 459 | / \ / 460 | 15 30 80 461 | \ 462 | 85 463 | 464 | left-right rotation of 95 ==> 465 | 466 | 40 467 | / \ 468 | 20 85 469 | / \ / \ 470 | 15 30 80 95 471 | */ 472 | const root = avlTree.root(); 473 | expect(root.getValue()).to.equal(40); 474 | expect(root.getLeft().getValue()).to.equal(20); 475 | expect(root.getLeft().getRight().getValue()).to.equal(30); 476 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 477 | expect(root.getRight().getValue()).to.equal(85); 478 | expect(root.getRight().getLeft().getValue()).to.equal(80); 479 | expect(root.getRight().getRight().getValue()).to.equal(95); 480 | }); 481 | 482 | it('removes the rest of nodes properly', () => { 483 | /* 484 | 40 485 | / \ 486 | 20 85 487 | / \ / \ 488 | 15 30 80 95 489 | */ 490 | avlTree.remove(30); 491 | avlTree.remove(80); 492 | avlTree.remove(95); 493 | avlTree.remove(85); 494 | /* 495 | 40 (balance = 2) 496 | / 497 | 20 498 | / 499 | 15 500 | 501 | right rotation of 40 ==> 502 | 20 503 | / \ 504 | 15 40 505 | */ 506 | 507 | expect(avlTree.root().getValue()).to.equal(20); 508 | expect(avlTree.root().getLeft().getValue()).to.equal(15); 509 | expect(avlTree.root().getRight().getValue()).to.equal(40); 510 | 511 | avlTree.remove(20); 512 | expect(avlTree.root().getValue()).to.equal(40); 513 | expect(avlTree.root().getLeft().getValue()).to.equal(15); 514 | 515 | avlTree.remove(40); 516 | expect(avlTree.root().getValue()).to.equal(15); 517 | expect(avlTree.count()).to.equal(1); 518 | 519 | avlTree.insert(20); 520 | avlTree.remove(15); 521 | expect(avlTree.root().getValue()).to.equal(20); 522 | expect(avlTree.count()).to.equal(1); 523 | avlTree.remove(20); 524 | expect(avlTree.root()).to.equal(null); 525 | expect(avlTree.count()).to.equal(0); 526 | }); 527 | 528 | it('correctly removes a node with one child', () => { 529 | function getAll(tree) { 530 | const arr = []; 531 | tree.traverseInOrder((n) => arr.push(n.getValue())); 532 | return arr; 533 | } 534 | 535 | const tree = new AvlTree(); 536 | tree.insert(3); 537 | tree.insert(1); 538 | tree.insert(5); 539 | tree.insert(6); 540 | 541 | tree.remove(5); 542 | 543 | expect(getAll(tree)).to.deep.equal([1, 3, 6]); 544 | }); 545 | 546 | it('keeps the tree balanced when removing only nodes with two children', () => { 547 | const insertOrder = [7, 3, 11, 1, 5, 9, 13, 0, 2, 4, 6, 8, 10, 12, 14]; 548 | const deleteOrder = [1, 5, 3, 4, 9, 13, 11, 12, 7, 8, 10]; 549 | 550 | const tree = new AvlTree(); 551 | insertOrder.forEach((n) => tree.insert(n)); 552 | deleteOrder.forEach((n) => tree.remove(n)); 553 | expect(tree.root().getBalance()).to.be.oneOf([-1, 0, 1]); 554 | const elements = []; 555 | tree.traverseInOrder((n) => elements.push(n.getValue())); 556 | expect(elements).to.deep.equal([0, 2, 6, 14]); 557 | }); 558 | 559 | it('correctly removes all nodes from a large tree', () => { 560 | const elements = [ 561 | 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, 562 | 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, 563 | 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, 564 | 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, 565 | 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, 566 | 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, 567 | 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, 568 | 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, 569 | 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, 570 | 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, 571 | 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, 572 | 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, 573 | 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, 574 | 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, 575 | 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, 576 | 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, 577 | 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, 578 | 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, 579 | 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, 580 | 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, 581 | 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, 582 | 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, 583 | 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, 584 | 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, 585 | 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, 586 | 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, 587 | 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, 588 | 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, 589 | 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, 590 | 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, 591 | 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, 592 | 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, 593 | 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, 594 | 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, 595 | 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, 596 | 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, 597 | 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, 598 | 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, 599 | 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, 600 | 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, 601 | 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, 602 | 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, 603 | 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, 604 | 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, 605 | 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, 606 | 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, 607 | 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, 608 | 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, 609 | 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, 610 | 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, 611 | 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, 612 | 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, 613 | 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, 614 | 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, 615 | 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, 616 | 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, 617 | 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, 618 | 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, 619 | 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, 620 | 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, 621 | 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, 622 | 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 623 | ]; 624 | 625 | const tree = new AvlTree(); 626 | elements.forEach((n) => tree.insert(n)); 627 | elements.forEach((n) => tree.remove(n)); 628 | }); 629 | }); 630 | 631 | describe('.removeNode(node)', () => { 632 | const testRemoveTree = new AvlTree(); 633 | testRemoveTree 634 | .insert(50) 635 | .insert(80) 636 | .insert(30) 637 | .insert(90) 638 | .insert(60) 639 | .insert(40) 640 | .insert(20); 641 | const n80 = testRemoveTree.find(80); 642 | testRemoveTree.removeNode(n80); 643 | expect(testRemoveTree.root().getRight().getValue()).to.equal(90); 644 | expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); 645 | expect(testRemoveTree.root().getRight().getRight()).to.equal(null); 646 | }); 647 | }); 648 | -------------------------------------------------------------------------------- /test/avlTree.iterative.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { AvlTree } = require('../src/avlTree'); 3 | 4 | describe('AvlTree tests (iterative implementation)', () => { 5 | const avlTree = new AvlTree(); 6 | 7 | describe('.insertIterative(value)', () => { 8 | it('left rotation balancing', () => { 9 | avlTree.insertIterative(50); 10 | avlTree.insertIterative(80); 11 | avlTree.insertIterative(90); 12 | /* 13 | 50 (balance = -2) 14 | \ 15 | 80 16 | \ 17 | 90 18 | 19 | lef-rotation of 50 to ==> 20 | 21 | 80 22 | / \ 23 | 50 90 24 | */ 25 | const root = avlTree.root(); 26 | expect(root.getValue()).to.equal(80); 27 | 28 | expect(root.getRight().getValue()).to.equal(90); 29 | expect(root.getRight().getParent().getValue()).to.equal(80); 30 | 31 | expect(root.getLeft().getValue()).to.equal(50); 32 | expect(root.getLeft().getParent().getValue()).to.equal(80); 33 | }); 34 | 35 | it('right rotation balancing', () => { 36 | avlTree.insertIterative(40); 37 | avlTree.insertIterative(30); 38 | 39 | /* 40 | 80 41 | / \ 42 | (balance = 2) 50 90 43 | / 44 | 40 45 | / 46 | 30 47 | 48 | right-rotation of 50 to ==> 49 | 50 | 80 51 | / \ 52 | 40 90 53 | / \ 54 | 30 50 55 | */ 56 | const root = avlTree.root(); 57 | expect(root.getValue()).to.equal(80); 58 | 59 | expect(root.getRight().getValue()).to.equal(90); 60 | expect(root.getRight().getParent().getValue()).to.equal(80); 61 | 62 | expect(root.getLeft().getValue()).to.equal(40); 63 | expect(root.getLeft().getParent().getValue()).to.equal(80); 64 | 65 | expect(root.getLeft().getRight().getValue()).to.equal(50); 66 | expect(root.getLeft().getRight().getParent().getValue()).to.equal(40); 67 | 68 | expect(root.getLeft().getLeft().getValue()).to.equal(30); 69 | expect(root.getLeft().getLeft().getParent().getValue()).to.equal(40); 70 | 71 | avlTree.insertIterative(20); 72 | /* 73 | 80 (balance = 2) 74 | / \ 75 | 40 90 76 | / \ 77 | 30 50 78 | / 79 | 20 80 | 81 | right-rotation of 80 ==> 82 | 83 | 40 84 | / \ 85 | 30 80 86 | / / \ 87 | 20 50 90 88 | */ 89 | expect(avlTree.root().getValue()).to.equal(40); 90 | 91 | expect(avlTree.root().getLeft().getValue()).to.equal(30); 92 | expect(avlTree.root().getLeft().getLeft().getValue()).to.equal(20); 93 | 94 | expect(avlTree.root().getRight().getValue()).to.equal(80); 95 | expect(avlTree.root().getRight().getRight().getValue()).to.equal(90); 96 | expect(avlTree.root().getRight().getLeft().getValue()).to.equal(50); 97 | }); 98 | 99 | it('left-right rotation balancing', () => { 100 | avlTree.insertIterative(35); 101 | avlTree.insertIterative(10); 102 | avlTree.insertIterative(15); 103 | /* 104 | verify left-right rotation 105 | 40 106 | / \ 107 | 30 80 108 | / \ / \ 109 | 20 35 50 90 110 | / 111 | 10 112 | \ 113 | 15 114 | 115 | left-right rotation of 20 ==> 116 | 40 117 | / \ 118 | 30 80 119 | / \ / \ 120 | 15 35 50 90 121 | / \ 122 | 10 20 123 | */ 124 | const root = avlTree.root(); 125 | expect(root.getValue()).to.equal(40); 126 | 127 | expect(root.getRight().getValue()).to.equal(80); 128 | expect(root.getRight().getRight().getValue()).to.equal(90); 129 | expect(root.getRight().getLeft().getValue()).to.equal(50); 130 | 131 | expect(root.getLeft().getValue()).to.equal(30); 132 | expect(root.getLeft().getRight().getValue()).to.equal(35); 133 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 134 | expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); 135 | expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); 136 | }); 137 | 138 | it('right-left rotation balancing', () => { 139 | avlTree.insertIterative(100); 140 | avlTree.insertIterative(95); 141 | /* 142 | verify right-left rotation 143 | 40 144 | / \ 145 | 30 80 146 | / \ / \ 147 | 15 35 50 90 148 | / \ \ 149 | 10 20 100 150 | / 151 | 95 152 | 153 | right-left rotation of 90 ==> 154 | 155 | 40 156 | / \ 157 | 30 80 158 | / \ / \ 159 | 15 35 50 95 160 | / \ / \ 161 | 10 20 90 100 162 | */ 163 | const root = avlTree.root(); 164 | expect(root.getValue()).to.equal(40); 165 | 166 | expect(root.getRight().getValue()).to.equal(80); 167 | expect(root.getRight().getRight().getValue()).to.equal(95); 168 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 169 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 170 | expect(root.getRight().getLeft().getValue()).to.equal(50); 171 | 172 | expect(root.getLeft().getValue()).to.equal(30); 173 | expect(root.getLeft().getRight().getValue()).to.equal(35); 174 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 175 | expect(root.getLeft().getLeft().getRight().getValue()).to.equal(20); 176 | expect(root.getLeft().getLeft().getLeft().getValue()).to.equal(10); 177 | }); 178 | 179 | it('keep balance when inserting so many elements', () => { 180 | const elements = [ 181 | 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, 182 | 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, 183 | 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, 184 | 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, 185 | 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, 186 | 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, 187 | 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, 188 | 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, 189 | 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, 190 | 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, 191 | 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, 192 | 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, 193 | 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, 194 | 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, 195 | 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, 196 | 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, 197 | 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, 198 | 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, 199 | 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, 200 | 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, 201 | 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, 202 | 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, 203 | 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, 204 | 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, 205 | 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, 206 | 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, 207 | 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, 208 | 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, 209 | 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, 210 | 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, 211 | 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, 212 | 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, 213 | 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, 214 | 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, 215 | 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, 216 | 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, 217 | 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, 218 | 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, 219 | 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, 220 | 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, 221 | 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, 222 | 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, 223 | 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, 224 | 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, 225 | 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, 226 | 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, 227 | 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, 228 | 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, 229 | 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, 230 | 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, 231 | 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, 232 | 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, 233 | 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, 234 | 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, 235 | 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, 236 | 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, 237 | 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, 238 | 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, 239 | 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, 240 | 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, 241 | 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, 242 | 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 243 | ]; 244 | 245 | const tree = new AvlTree(); 246 | elements.forEach((n) => tree.insertIterative(n)); 247 | tree.traversePreOrder((node) => { 248 | const balance = node.getBalance(); 249 | if (balance > 1 || balance < -1) { 250 | throw new Error(`not balance , balance is ${balance}`); 251 | } 252 | }); 253 | }); 254 | }); 255 | 256 | describe('.min()', () => { 257 | it('get the node with min value', () => { 258 | expect(avlTree.min().getValue(15)); 259 | }); 260 | }); 261 | 262 | describe('.max()', () => { 263 | it('get the node with min value', () => { 264 | expect(avlTree.max().getValue(100)); 265 | }); 266 | }); 267 | 268 | describe('.root()', () => { 269 | it('should get root node', () => { 270 | expect(avlTree.root().getValue(40)); 271 | }); 272 | }); 273 | 274 | describe('.find(value)', () => { 275 | it('find a node by its value', () => { 276 | expect(avlTree.find(35).getValue()).to.equal(35); 277 | expect(avlTree.find(1000)).to.equal(null); 278 | }); 279 | }); 280 | 281 | describe('.traverseInOrder(cb)', () => { 282 | it('traverse the tree in order', () => { 283 | const values = []; 284 | avlTree.traverseInOrder((node) => values.push(node.getValue())); 285 | expect(values).to.deep.equal([ 286 | 10, 15, 20, 30, 35, 40, 50, 80, 90, 95, 100 287 | ]); 288 | }); 289 | }); 290 | 291 | describe('.traversePreOrder(cb)', () => { 292 | it('traverse the tree in order', () => { 293 | const values = []; 294 | avlTree.traversePreOrder((node) => values.push(node.getValue())); 295 | expect(values).to.deep.equal([ 296 | 40, 30, 15, 10, 20, 35, 80, 50, 95, 90, 100 297 | ]); 298 | }); 299 | }); 300 | 301 | describe('.traversePostOrder(cb)', () => { 302 | it('traverse the tree post order', () => { 303 | const values = []; 304 | avlTree.traversePostOrder((node) => values.push(node.getValue())); 305 | expect(values).to.deep.equal([ 306 | 10, 20, 15, 35, 30, 50, 90, 100, 95, 80, 40 307 | ]); 308 | }); 309 | }); 310 | 311 | describe('.remove(value)', () => { 312 | it('right rotation balancing', () => { 313 | /* 314 | 40 315 | / \ 316 | 30 80 317 | / \ / \ 318 | 15 35 50 95 319 | / \ / \ 320 | 10 20 90 100 321 | */ 322 | 323 | avlTree.removeIterative(35); 324 | 325 | /* 326 | 40 327 | / \ 328 | (balance = 2) 30 80 329 | / / \ 330 | 15 50 95 331 | / \ / \ 332 | 10 20 90 100 333 | 334 | right rotation of 30 ==> 335 | 336 | 40 337 | / \ 338 | 15 80 339 | / \ / \ 340 | 10 30 50 95 341 | / / \ 342 | 20 90 100 343 | */ 344 | const root = avlTree.root(); 345 | expect(root.getValue()).to.equal(40); 346 | expect(root.getRight().getValue()).to.equal(80); 347 | expect(root.getRight().getRight().getValue()).to.equal(95); 348 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 349 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 350 | expect(root.getRight().getLeft().getValue()).to.equal(50); 351 | expect(root.getLeft().getValue()).to.equal(15); 352 | expect(root.getLeft().getRight().getValue()).to.equal(30); 353 | expect(root.getLeft().getRight().getLeft().getValue()).to.equal(20); 354 | expect(root.getLeft().getLeft().getValue()).to.equal(10); 355 | }); 356 | 357 | it('right-left rotation balancing', () => { 358 | /* 359 | 40 360 | / \ 361 | 15 80 362 | / \ / \ 363 | 10 30 50 95 364 | / / \ 365 | 20 90 100 366 | */ 367 | 368 | avlTree.removeIterative(10); 369 | 370 | /* 371 | 40 372 | / \ 373 | (balance = -2) 15 80 374 | \ / \ 375 | 30 50 95 376 | / / \ 377 | 20 90 100 378 | 379 | right-left rotation of 15 ==> 380 | 381 | 40 382 | / \ 383 | 20 80 384 | / \ / \ 385 | 15 30 50 95 386 | / \ 387 | 90 100 388 | */ 389 | const root = avlTree.root(); 390 | expect(root.getValue()).to.equal(40); 391 | expect(root.getRight().getValue()).to.equal(80); 392 | expect(root.getRight().getRight().getValue()).to.equal(95); 393 | expect(root.getRight().getRight().getRight().getValue()).to.equal(100); 394 | expect(root.getRight().getRight().getLeft().getValue()).to.equal(90); 395 | expect(root.getRight().getLeft().getValue()).to.equal(50); 396 | expect(root.getLeft().getValue()).to.equal(20); 397 | expect(root.getLeft().getRight().getValue()).to.equal(30); 398 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 399 | }); 400 | 401 | it('left rotation balancing', () => { 402 | /* 403 | 404 | 40 405 | / \ 406 | 20 80 407 | / \ / \ 408 | 15 30 50 95 409 | / \ 410 | 90 100 411 | */ 412 | 413 | avlTree.removeIterative(90); 414 | avlTree.removeIterative(50); 415 | 416 | /* 417 | 40 418 | / \ 419 | 20 80 (balance = -2) 420 | / \ \ 421 | 15 30 95 422 | \ 423 | 100 424 | 425 | left rotation of 15 ==> 426 | 427 | 40 428 | / \ 429 | 20 95 430 | / \ / \ 431 | 15 30 80 100 432 | */ 433 | expect(avlTree.root().getValue()).to.equal(40); 434 | expect(avlTree.root().getRight().getValue()).to.equal(95); 435 | expect(avlTree.root().getRight().getRight().getValue()).to.equal(100); 436 | expect(avlTree.root().getRight().getLeft().getValue()).to.equal(80); 437 | expect(avlTree.root().getLeft().getValue()).to.equal(20); 438 | expect(avlTree.root().getLeft().getRight().getValue()).to.equal(30); 439 | expect(avlTree.root().getLeft().getLeft().getValue()).to.equal(15); 440 | }); 441 | 442 | it('left-right rotation balancing', () => { 443 | avlTree.insertIterative(85); 444 | /* 445 | 40 446 | / \ 447 | 20 95 448 | / \ / \ 449 | 15 30 80 100 450 | \ 451 | 85 452 | */ 453 | 454 | avlTree.removeIterative(100); 455 | /* 456 | 40 457 | / \ 458 | 20 95 (balance = 2) 459 | / \ / 460 | 15 30 80 461 | \ 462 | 85 463 | 464 | left-right rotation of 95 ==> 465 | 466 | 40 467 | / \ 468 | 20 85 469 | / \ / \ 470 | 15 30 80 95 471 | */ 472 | const root = avlTree.root(); 473 | expect(root.getValue()).to.equal(40); 474 | expect(root.getLeft().getValue()).to.equal(20); 475 | expect(root.getLeft().getRight().getValue()).to.equal(30); 476 | expect(root.getLeft().getLeft().getValue()).to.equal(15); 477 | expect(root.getRight().getValue()).to.equal(85); 478 | expect(root.getRight().getLeft().getValue()).to.equal(80); 479 | expect(root.getRight().getRight().getValue()).to.equal(95); 480 | }); 481 | 482 | it('removes the rest of nodes properly', () => { 483 | /* 484 | 40 485 | / \ 486 | 20 85 487 | / \ / \ 488 | 15 30 80 95 489 | */ 490 | avlTree.removeIterative(30); 491 | avlTree.removeIterative(80); 492 | avlTree.removeIterative(95); 493 | avlTree.removeIterative(85); 494 | /* 495 | 40 (balance = 2) 496 | / 497 | 20 498 | / 499 | 15 500 | 501 | right rotation of 40 ==> 502 | 20 503 | / \ 504 | 15 40 505 | */ 506 | 507 | expect(avlTree.root().getValue()).to.equal(20); 508 | expect(avlTree.root().getLeft().getValue()).to.equal(15); 509 | expect(avlTree.root().getRight().getValue()).to.equal(40); 510 | 511 | avlTree.removeIterative(20); 512 | expect(avlTree.root().getValue()).to.equal(40); 513 | expect(avlTree.root().getLeft().getValue()).to.equal(15); 514 | 515 | avlTree.removeIterative(40); 516 | expect(avlTree.root().getValue()).to.equal(15); 517 | expect(avlTree.count()).to.equal(1); 518 | 519 | avlTree.insertIterative(20); 520 | avlTree.removeIterative(15); 521 | expect(avlTree.root().getValue()).to.equal(20); 522 | expect(avlTree.count()).to.equal(1); 523 | avlTree.removeIterative(20); 524 | expect(avlTree.root()).to.equal(null); 525 | expect(avlTree.count()).to.equal(0); 526 | }); 527 | 528 | it('correctly removes a node with one child', () => { 529 | function getAll(tree) { 530 | const arr = []; 531 | tree.traverseInOrder((n) => arr.push(n.getValue())); 532 | return arr; 533 | } 534 | 535 | const tree = new AvlTree(); 536 | tree.insertIterative(3); 537 | tree.insertIterative(1); 538 | tree.insertIterative(5); 539 | tree.insertIterative(6); 540 | 541 | tree.removeIterative(5); 542 | 543 | expect(getAll(tree)).to.deep.equal([1, 3, 6]); 544 | }); 545 | 546 | it('keeps the tree balanced when removing only nodes with two children', () => { 547 | const insertOrder = [7, 3, 11, 1, 5, 9, 13, 0, 2, 4, 6, 8, 10, 12, 14]; 548 | const deleteOrder = [1, 5, 3, 4, 9, 13, 11, 12, 7, 8, 10]; 549 | 550 | const tree = new AvlTree(); 551 | insertOrder.forEach((n) => tree.insertIterative(n)); 552 | deleteOrder.forEach((n) => tree.removeIterative(n)); 553 | expect(tree.root().getBalance()).to.be.oneOf([-1, 0, 1]); 554 | const elements = []; 555 | tree.traverseInOrder((n) => elements.push(n.getValue())); 556 | expect(elements).to.deep.equal([0, 2, 6, 14]); 557 | }); 558 | 559 | it('correctly removes all nodes from a large tree', () => { 560 | const elements = [ 561 | 130, 345, 826, 571, 795, 366, 648, 418, 353, 267, 450, 404, 456, 310, 562 | 137, 888, 497, 378, 651, 148, 552, 632, 886, 532, 273, 802, 590, 187, 563 | 487, 781, 24, 775, 746, 664, 459, 180, 76, 252, 44, 439, 426, 444, 91, 564 | 817, 152, 5, 643, 381, 470, 864, 307, 83, 753, 792, 216, 650, 780, 863, 565 | 396, 750, 494, 855, 684, 508, 837, 849, 654, 812, 561, 86, 731, 845, 566 | 176, 851, 110, 673, 222, 874, 512, 856, 609, 403, 872, 141, 488, 150, 567 | 70, 357, 705, 260, 805, 233, 172, 890, 519, 230, 824, 335, 447, 563, 568 | 613, 62, 597, 659, 166, 185, 93, 666, 634, 195, 767, 729, 803, 520, 626, 569 | 284, 569, 704, 225, 228, 440, 234, 526, 105, 202, 751, 809, 762, 341, 570 | 631, 857, 169, 43, 115, 147, 513, 773, 299, 674, 865, 259, 493, 629, 571 | 164, 555, 437, 101, 866, 287, 821, 380, 711, 337, 212, 458, 45, 733, 572 | 576, 60, 755, 415, 384, 311, 51, 301, 540, 74, 756, 742, 675, 776, 181, 573 | 862, 847, 490, 516, 423, 760, 114, 876, 306, 405, 116, 385, 541, 624, 574 | 501, 873, 867, 16, 698, 617, 323, 354, 359, 700, 644, 179, 502, 397, 575 | 304, 0, 635, 881, 171, 671, 454, 87, 266, 551, 207, 695, 592, 743, 268, 576 | 198, 72, 550, 283, 758, 292, 189, 340, 194, 278, 507, 689, 145, 472, 577 | 269, 199, 346, 870, 431, 333, 363, 433, 578, 464, 779, 668, 827, 577, 578 | 715, 162, 167, 136, 210, 399, 89, 248, 184, 35, 524, 330, 640, 157, 350, 579 | 182, 622, 846, 586, 99, 549, 358, 58, 410, 4, 686, 139, 28, 258, 491, 580 | 575, 842, 825, 138, 618, 174, 660, 395, 871, 801, 749, 504, 383, 589, 581 | 103, 17, 241, 281, 797, 796, 499, 361, 104, 264, 247, 478, 804, 682, 582 | 294, 481, 732, 126, 683, 800, 669, 356, 81, 432, 71, 701, 288, 777, 221, 583 | 759, 892, 370, 736, 257, 477, 783, 134, 515, 771, 612, 411, 467, 838, 584 | 272, 238, 829, 160, 132, 409, 55, 376, 823, 446, 7, 791, 155, 730, 500, 585 | 788, 317, 460, 108, 430, 680, 453, 547, 442, 158, 316, 250, 601, 390, 586 | 14, 201, 39, 9, 884, 244, 583, 559, 251, 681, 702, 420, 371, 78, 763, 587 | 73, 531, 794, 232, 389, 498, 620, 554, 533, 391, 436, 754, 355, 882, 588 | 744, 522, 836, 196, 806, 125, 293, 107, 455, 297, 120, 891, 18, 435, 589 | 652, 206, 122, 853, 203, 564, 394, 412, 320, 56, 766, 832, 633, 6, 835, 590 | 124, 153, 312, 748, 47, 840, 82, 364, 839, 761, 97, 672, 457, 786, 553, 591 | 61, 170, 22, 703, 351, 112, 95, 517, 200, 774, 485, 121, 163, 204, 523, 592 | 25, 720, 558, 348, 466, 41, 29, 237, 177, 119, 741, 253, 336, 37, 331, 593 | 401, 42, 173, 614, 19, 608, 770, 858, 143, 217, 52, 570, 117, 128, 276, 594 | 539, 113, 308, 785, 496, 298, 852, 480, 670, 518, 2, 419, 159, 118, 814, 595 | 724, 588, 461, 714, 226, 628, 286, 186, 468, 98, 208, 712, 630, 627, 596 | 861, 213, 413, 734, 448, 400, 798, 271, 373, 854, 495, 573, 765, 96, 597 | 382, 707, 893, 285, 710, 313, 525, 154, 621, 521, 725, 591, 641, 300, 598 | 696, 790, 645, 146, 535, 282, 565, 691, 572, 639, 12, 657, 256, 23, 85, 599 | 690, 38, 3, 584, 64, 605, 543, 144, 607, 449, 649, 360, 557, 492, 372, 600 | 548, 161, 342, 615, 407, 223, 625, 850, 655, 568, 10, 205, 427, 386, 601 | 820, 197, 123, 249, 131, 735, 667, 129, 319, 100, 465, 567, 616, 191, 602 | 277, 619, 48, 594, 723, 819, 462, 242, 245, 463, 193, 79, 843, 647, 231, 603 | 566, 606, 402, 739, 322, 810, 542, 328, 127, 813, 40, 209, 537, 329, 604 | 510, 476, 102, 860, 374, 602, 429, 69, 220, 676, 599, 527, 424, 362, 605 | 534, 822, 11, 658, 663, 818, 236, 54, 63, 808, 637, 109, 393, 406, 57, 606 | 32, 142, 556, 708, 506, 772, 869, 67, 688, 596, 31, 885, 1, 365, 219, 607 | 503, 709, 344, 326, 367, 332, 229, 263, 600, 706, 560, 604, 175, 868, 608 | 408, 452, 26, 587, 377, 243, 697, 279, 740, 505, 638, 595, 581, 718, 21, 609 | 324, 347, 227, 562, 421, 218, 646, 752, 59, 156, 489, 84, 302, 88, 699, 610 | 50, 135, 653, 875, 388, 685, 434, 77, 745, 445, 878, 290, 677, 255, 275, 611 | 484, 178, 880, 585, 192, 149, 387, 441, 90, 679, 289, 545, 66, 49, 694, 612 | 623, 536, 769, 469, 111, 728, 443, 33, 240, 580, 375, 80, 486, 471, 687, 613 | 859, 830, 94, 530, 879, 270, 30, 833, 816, 722, 579, 352, 574, 528, 887, 614 | 509, 831, 318, 417, 327, 727, 261, 665, 799, 325, 719, 598, 214, 544, 615 | 188, 811, 254, 190, 721, 379, 656, 782, 13, 274, 793, 309, 482, 392, 616 | 889, 768, 338, 713, 841, 451, 661, 717, 349, 848, 538, 726, 133, 483, 617 | 807, 343, 747, 883, 165, 738, 593, 92, 369, 877, 315, 787, 716, 321, 618 | 339, 235, 778, 438, 20, 27, 183, 34, 844, 757, 737, 636, 834, 479, 692, 619 | 603, 303, 678, 828, 239, 789, 46, 425, 414, 474, 246, 514, 815, 662, 620 | 546, 582, 610, 140, 151, 473, 168, 65, 75, 475, 211, 224, 529, 368, 36, 621 | 280, 215, 262, 334, 511, 296, 8, 53, 15, 693, 295, 68, 428, 784, 398, 622 | 106, 291, 764, 305, 422, 416, 611, 314, 642, 265 623 | ]; 624 | 625 | const tree = new AvlTree(); 626 | elements.forEach((n) => tree.insertIterative(n)); 627 | elements.forEach((n) => tree.removeIterative(n)); 628 | }); 629 | }); 630 | 631 | describe('.removeNode(node)', () => { 632 | const testRemoveTree = new AvlTree(); 633 | testRemoveTree 634 | .insertIterative(50) 635 | .insertIterative(80) 636 | .insertIterative(30) 637 | .insertIterative(90) 638 | .insertIterative(60) 639 | .insertIterative(40) 640 | .insertIterative(20); 641 | const n80 = testRemoveTree.findIterative(80); 642 | testRemoveTree.removeNode(n80); 643 | expect(testRemoveTree.root().getRight().getValue()).to.equal(90); 644 | expect(testRemoveTree.root().getRight().getLeft().getValue()).to.equal(60); 645 | expect(testRemoveTree.root().getRight().getRight()).to.equal(null); 646 | }); 647 | }); 648 | --------------------------------------------------------------------------------