'use strict'; /** * Size balanced tree is a data structure which is * a type of self-balancing binary search tree that use * the tree size attribute for re-balancing the tree. * * @example * * var SBTree = require('../src/data-structures/size-balanced-tree').SBTree; * var sbTree = new SBTree(); * * var treeNode = sbTree.push({ * name: 'John', * surname: 'Smith' * }); * sbTree.insert(0, { * name: 'Pavlo', * surname: 'Popov' * }); * sbTree.insert(1, { * name: 'Garry', * surname: 'Fisher' * }); * sbTree.insert(0, { * name: 'Derek', * surname: 'Anderson' * }); * * console.log(sbTree.get(0)); // { name: 'Derek', surname: 'Anderson' } * * @module data-structures/size-balanced-tree */ function CreateSBTreeClass (Node, Nil, updateChild) { function LeftRotate(node, childNode) { /* Before rotate: node / \ NL childNode / \ CL CR After rotate: childNode / \ node CR / \ NL CL */ node.right = childNode.left; if (node.right !== Nil) { node.right.parent = node; } childNode.left = node; // setting childNode's parent to node's parent updateChild(node, childNode); return childNode; } function RightRotate(node, childNode) { /* Before rotate: node / \ childNode NR / \ CL CR After rotate: childNode / \ CL node / \ CR NR */ node.left = childNode.right; if (node.left !== Nil) { node.left.parent = node; } childNode.right = node; // setting childNode's parent to node's parent updateChild(node, childNode); return childNode; } function maintain(node, leftChild) { if (node === Nil) { return node; } var savedNode = node; if (leftChild) { if (node.left.left.size > node.right.size) { node = RightRotate(node, node.left); } else if (node.left.right.size > node.right.size) { LeftRotate(node.left, node.left.right); node = RightRotate(node, node.left); } } else { if (node.right.right.size > node.left.size) { node = LeftRotate(node, node.right); } else if (node.right.left.size > node.left.size) { RightRotate(node.right, node.right.left); node = LeftRotate(node, node.right); } } if (node === savedNode) { return node; } maintain(node.left, false); maintain(node.right, true); node = maintain(node, true); node = maintain(node, false); return node; } function maintainSizeBalancedTree(node) { while (node.parent !== Nil) { var childNode = node; node = node.parent; if (node.left === childNode) { node = maintain(node, true); } else { node = maintain(node, false); } } return node; } function findNodeAtPos(node, pos) { while (pos !== node.left.size) { if (pos < node.left.size) { node = node.left; } else { pos -= node.left.size; pos -= 1; //The node element should be decrement by 1 node = node.right; } } return node; } /** * Size Balanced Tree. * * @public * @constructor */ var SBTree = function () {}; SBTree.prototype = { _root: Nil, get size() { return this._root.size; }, get root() { return this._root; }, binarySearch: function (cmp, value) { var left = -1; var right = this.size; while (left + 1 < right) { var middle = (left + right) >> 1; // jshint ignore:line var result = cmp(this.get(middle).value, value); if (result <= 0) { left = middle; } else { right = middle; } } return left + 1; }, }; SBTree.prototype.get = function (pos) { if (pos >= this.size) { return Nil; } return findNodeAtPos(this._root, pos); }; SBTree.prototype.getIndex = function (node) { var index = node.left.size; while (node !== this._root) { var parent = node.parent; if (parent.right === node) { index += parent.left.size + 1; } node = parent; } return index; }; SBTree.prototype.shiftDown = function (node) { var direction = 0; while (true) { if (node.left !== Nil && node.right !== Nil) { switch (direction) { case 0: RightRotate(node, node.left); break; case 1: LeftRotate(node, node.right); break; } direction = 1 - direction; } else if (node.left !== Nil) { RightRotate(node, node.left); } else if (node.right !== Nil) { LeftRotate(node, node.right); } else { break; // The node could be able to removed } } }; SBTree.prototype.insertLeafNode = function (node) { var parent = node.parent; while (parent !== Nil) { parent.size = parent.size + 1; parent = parent.parent; } }; SBTree.prototype.removeLeafNode = function (node) { var parent = node.parent; while (parent !== Nil) { parent.size = parent.size - 1; parent = parent.parent; } }; SBTree.prototype.insert = function (pos, value) { var node = Nil; var newNode = new Node(value, Nil, Nil, Nil, 1); if (pos === this.size) { if (pos > 0) { node = findNodeAtPos(this._root, pos - 1); node.right = newNode; } } else { node = findNodeAtPos(this._root, pos); if (node.left !== Nil) { this.shiftDown(node); } node.left = newNode; } newNode.parent = node; this.insertLeafNode(newNode); this._root = maintainSizeBalancedTree(newNode); return newNode; }; /** * Push a value to the end of tree. * Complexity: O(log N). * * @public * @method * @param {Object} value Value. */ SBTree.prototype.push = function (value) { this.insert(this.size, value); }; SBTree.prototype.removeNode = function (node) { this.shiftDown(node); var maintainNode = node.parent; if (maintainNode.left === node) { maintainNode.left = Nil; } else if (maintainNode.right === node) { maintainNode.right = Nil; } this.removeLeafNode(node); this._root = maintainSizeBalancedTree(maintainNode); return node; }; SBTree.prototype.remove = function (pos) { if (pos >= this._root.size) { return Nil; // There is no element to remove } var node = findNodeAtPos(this._root, pos); return this.removeNode(node); }; return SBTree; } (function (exports) { /** * Node constructor of the Size-Balanced tree. * * @private * @constructor * @param {Object} value Value assigned to the node. * @param {Node} parent Parent node. * @param {Node} left Left node. * @param {Node} right Right node. * @param {Number} size Node's, means the Node count of this . */ var NodeConstructor = function (value, parent, left, right, size) { this.value = value; this.parent = parent; this.left = left; this.right = right; this.size = size; }; var createNil = function (Node, value) { var Nil = new Node(value, null, null, null, 0); Nil.parent = Nil; Nil.left = Nil; Nil.right = Nil; return Nil; }; /** * Update node's size. * * @private * @method */ var updateSize = function () { this.size = this.left.size + this.right.size + 1; }; // node, childNode must not be Nil, // if the childNode turn out to be the root, the parent should be Nil var updateChild = function (node, childNode) { var parent = node.parent; node.parent = childNode; childNode.parent = parent; node.updateSize(); childNode.updateSize(); if (parent.right === node) { parent.right = childNode; parent.updateSize(); } else if (parent.left === node) { parent.left = childNode; parent.updateSize(); } // otherwise parent is Nil }; var Node = function () { NodeConstructor.apply(this, arguments); }; Node.prototype.updateSize = updateSize; var Nil = createNil(Node, null); exports.NodeConstructor = NodeConstructor; exports.createNil = createNil; exports.updateSize = updateSize; exports.updateChild = updateChild; exports.CreateSBTreeClass = CreateSBTreeClass; exports.Node = Node; exports.Nil = Nil; exports.SBTree = CreateSBTreeClass(Node, Nil, updateChild); })(typeof module === 'undefined' ? window : module.exports);