369 lines
8.5 KiB
JavaScript
369 lines
8.5 KiB
JavaScript
|
'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);
|