UNPKG

buckets-js

Version:

Buckets is a complete, fully tested and documented data structure library written in pure JavaScript.

422 lines (378 loc) 12.7 kB
/** * Creates an empty binary search tree. * @class <p> Binary search trees keep their elements in sorted order, so that * lookup and other operations can use the principle of binary search. In a BST * the element in any node is larger than the elements in the node's * left sub-tree and smaller than the elements in the node's right sub-tree.</p> * <p>If the inserted elements are custom objects, a compare function must * be provided at construction time, otherwise the <=, === and >= operators are * used to compare elements.</p> * <p>Example:</p> * <pre> * function compare(a, b) { * if (a is less than b by some ordering criterion) { * return -1; * } if (a is greater than b by the ordering criterion) { * return 1; * } * // a must be equal to b * return 0; * } * </pre> * @constructor * @param {function(Object,Object):number=} compareFunction Optional * function used to compare two elements. Must return a negative integer, * zero, or a positive integer as the first argument is less than, equal to, * or greater than the second. */ buckets.BSTree = function (compareFunction) { /** * @exports tree as buckets.BSTree * @private */ var tree = {}, // Function to compare elements. compare = compareFunction || buckets.defaultCompare, // Number of elements in the tree. nElements = 0, // The root node of the tree. root; // Returns the sub-node containing the specified element or undefined. function searchNode(root, element) { var node = root, cmp; while (node !== undefined && cmp !== 0) { cmp = compare(element, node.element); if (cmp < 0) { node = node.leftCh; } else if (cmp > 0) { node = node.rightCh; } } return node; } // Returns the sub-node containing the minimum element or undefined. function minimumAux(root) { var node = root; while (node.leftCh !== undefined) { node = node.leftCh; } return node; } /** * Inserts the specified element into the tree if it's not already present. * @param {Object} element The element to insert. * @return {boolean} True if the tree didn't already contain the element. */ tree.add = function (element) { if (buckets.isUndefined(element)) { return false; } /** * @private */ function insertNode(node) { var position = root, parent, cmp; while (position !== undefined) { cmp = compare(node.element, position.element); if (cmp === 0) { return undefined; } if (cmp < 0) { parent = position; position = position.leftCh; } else { parent = position; position = position.rightCh; } } node.parent = parent; if (parent === undefined) { // tree is empty root = node; } else if (compare(node.element, parent.element) < 0) { parent.leftCh = node; } else { parent.rightCh = node; } return node; } var node = { element: element, leftCh: undefined, rightCh: undefined, parent: undefined }; if (insertNode(node) !== undefined) { nElements += 1; return true; } return false; }; /** * Removes all the elements from the tree. */ tree.clear = function () { root = undefined; nElements = 0; }; /** * Returns true if the tree contains no elements. * @return {boolean} True if the tree contains no elements. */ tree.isEmpty = function () { return nElements === 0; }; /** * Returns the number of elements in the tree. * @return {number} The number of elements in the tree. */ tree.size = function () { return nElements; }; /** * Returns true if the tree contains the specified element. * @param {Object} element Element to search for. * @return {boolean} True if the tree contains the element, * false otherwise. */ tree.contains = function (element) { if (buckets.isUndefined(element)) { return false; } return searchNode(root, element) !== undefined; }; /** * Removes the specified element from the tree. * @return {boolean} True if the tree contained the specified element. */ tree.remove = function (element) { var node; function transplant(n1, n2) { if (n1.parent === undefined) { root = n2; } else if (n1 === n1.parent.leftCh) { n1.parent.leftCh = n2; } else { n1.parent.rightCh = n2; } if (n2 !== undefined) { n2.parent = n1.parent; } } function removeNode(node) { if (node.leftCh === undefined) { transplant(node, node.rightCh); } else if (node.rightCh === undefined) { transplant(node, node.leftCh); } else { var y = minimumAux(node.rightCh); if (y.parent !== node) { transplant(y, y.rightCh); y.rightCh = node.rightCh; y.rightCh.parent = y; } transplant(node, y); y.leftCh = node.leftCh; y.leftCh.parent = y; } } node = searchNode(root, element); if (node === undefined) { return false; } removeNode(node); nElements -= 1; return true; }; /** * Executes the provided function once per element present in the tree in in-order. * @param {function(Object):*} callback Function to execute, invoked with an element as * argument. To break the iteration you can optionally return false in the callback. */ tree.inorderTraversal = function (callback) { function inorderRecursive(node, callback, signal) { if (node === undefined || signal.stop) { return; } inorderRecursive(node.leftCh, callback, signal); if (signal.stop) { return; } signal.stop = callback(node.element) === false; if (signal.stop) { return; } inorderRecursive(node.rightCh, callback, signal); } inorderRecursive(root, callback, { stop: false }); }; /** * Executes the provided function once per element present in the tree in pre-order. * @param {function(Object):*} callback Function to execute, invoked with an element as * argument. To break the iteration you can optionally return false in the callback. */ tree.preorderTraversal = function (callback) { function preorderRecursive(node, callback, signal) { if (node === undefined || signal.stop) { return; } signal.stop = callback(node.element) === false; if (signal.stop) { return; } preorderRecursive(node.leftCh, callback, signal); if (signal.stop) { return; } preorderRecursive(node.rightCh, callback, signal); } preorderRecursive(root, callback, { stop: false }); }; /** * Executes the provided function once per element present in the tree in post-order. * @param {function(Object):*} callback Function to execute, invoked with an element as * argument. To break the iteration you can optionally return false in the callback. */ tree.postorderTraversal = function (callback) { function postorderRecursive(node, callback, signal) { if (node === undefined || signal.stop) { return; } postorderRecursive(node.leftCh, callback, signal); if (signal.stop) { return; } postorderRecursive(node.rightCh, callback, signal); if (signal.stop) { return; } signal.stop = callback(node.element) === false; } postorderRecursive(root, callback, { stop: false }); }; /** * Executes the provided function once per element present in the tree in level-order. * @param {function(Object):*} callback Function to execute, invoked with an element as * argument. To break the iteration you can optionally return false in the callback. */ tree.levelTraversal = function (callback) { function levelAux(node, callback) { var queue = buckets.Queue(); if (node !== undefined) { queue.enqueue(node); } while (!queue.isEmpty()) { node = queue.dequeue(); if (callback(node.element) === false) { return; } if (node.leftCh !== undefined) { queue.enqueue(node.leftCh); } if (node.rightCh !== undefined) { queue.enqueue(node.rightCh); } } } levelAux(root, callback); }; /** * Returns the minimum element of the tree. * @return {*} The minimum element of the tree or undefined if the tree * is empty. */ tree.minimum = function () { if (tree.isEmpty()) { return undefined; } return minimumAux(root).element; }; /** * Returns the maximum element of the tree. * @return {*} The maximum element of the tree or undefined if the tree * is empty. */ tree.maximum = function () { function maximumAux(node) { while (node.rightCh !== undefined) { node = node.rightCh; } return node; } if (tree.isEmpty()) { return undefined; } return maximumAux(root).element; }; /** * Executes the provided function once per element present in the tree in in-order. * Equivalent to inorderTraversal. * @param {function(Object):*} callback Function to execute, it's * invoked with an element argument. To break the iteration you can * optionally return false in the callback. */ tree.forEach = function (callback) { tree.inorderTraversal(callback); }; /** * Returns an array containing all the elements in the tree in in-order. * @return {Array} An array containing all the elements in the tree in in-order. */ tree.toArray = function () { var array = []; tree.inorderTraversal(function (element) { array.push(element); }); return array; }; /** * Returns the height of the tree. * @return {number} The height of the tree or -1 if it's empty. */ tree.height = function () { function heightAux(node) { if (node === undefined) { return -1; } return Math.max(heightAux(node.leftCh), heightAux(node.rightCh)) + 1; } function heightRecursive(node) { if (node === undefined) { return -1; } return Math.max(heightAux(node.leftCh), heightAux(node.rightCh)) + 1; } return heightRecursive(root); }; /** * Returns true if the tree is equal to another tree. * Two trees are equal if they have the same elements. * @param {buckets.BSTree} other The other tree. * @return {boolean} True if the tree is equal to the given tree. */ tree.equals = function (other) { var isEqual; if (buckets.isUndefined(other) || typeof other.levelTraversal !== 'function') { return false; } if (tree.size() !== other.size()) { return false; } isEqual = true; other.forEach(function (element) { isEqual = tree.contains(element); return isEqual; }); return isEqual; }; return tree; };