You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
6.0 KiB
JavaScript

/**
* Red-Black tree is a data structure which is
* a type of self-balancing binary search tree.
*
* @example
*
* var RBTree = require('../src/data-structures/red-black-tree').RBTree;
* var rbTree = new RBTree();
*
* rbTree.put(1981, {
* name: 'John',
* surname: 'Smith'
* });
* rbTree.put(2000, {
* name: 'Pavlo',
* surname: 'Popov'
* });
* rbTree.put(1989, {
* name: 'Garry',
* surname: 'Fisher'
* });
* rbTree.put(1990, {
* name: 'Derek',
* surname: 'Anderson'
* });
*
* console.log(rbTree.get(1989)); // { name: 'Garry', surname: 'Fisher' }
*
* @module data-structures/red-black-tree
*/
(function (exports) {
'use strict';
/**
* Enum for the different colors
*/
var Colors = {
RED: 0,
BLACK: 1
};
exports.Colors = Colors;
/**
* Node of the Red-Black tree.
*
* @private
* @constructor
* @param {Number} key Key of the node.
* @param {Object} value Value assigned to the node.
* @param {Node} left Left node.
* @param {Node} right Right node.
* @param {Number} color Node color.
*/
function Node(key, value, left, right, color) {
this._key = key;
this._left = left;
this._right = right;
this._value = value;
this._color = color;
}
/**
* Check or node is red.
*
* @private
* @method
* @return {Boolean} Returns true if node is red.
*/
Node.prototype.isRed = function () {
return this._color === Colors.RED;
};
/**
* Changes node color.
*
* @private
* @method
*/
Node.prototype.flipColor = function () {
if (this._color === Colors.RED) {
this._color = Colors.BLACK;
} else {
this._color = Colors.RED;
}
};
/**
* Creates getters and setters for the properties:
* key, value, left, right and color.
*/
'key value left right color'
.split(' ')
.forEach(function (key) {
var valueName = key.substr(0, 1).toUpperCase() + key.substr(1, key.length);
Node.prototype['get' + valueName] = function () {
return this['_' + key];
};
Node.prototype['set' + valueName] = function (val) {
this['_' + key] = val;
};
});
exports.Node = Node;
/**
* Red-Black Tree.
*
* @public
* @constructor
*/
exports.RBTree = function () {
this._root = null;
};
/**
* Add value associated with a given key.<br><br>
* Complexity: O(log N).
*
* @public
* @method
* @param {Number} key Key.
* @param {Object} value Value.
*/
exports.RBTree.prototype.put = function (key, value) {
this._root = this._put(key, value, this._root);
this._root.setColor(Colors.BLACK);
};
/**
* Returns true or false depending on whether
* given node is red.
*
* @private
* @method
* @param {Node} node Node which sould be checked.
* @return Returns true if node is red.
*/
exports.RBTree.prototype.isRed = function (node) {
if (!node) {
return false;
}
return node.isRed();
};
/**
* Helper function for insertion of given key,
* value pair into the Red-Black tree.
*
* @private
* @method
* @param {Number} key Key.
* @param {Object} value Value.
* @param {Node} node Node.
*/
exports.RBTree.prototype._put = function (key, value, node) {
var newRoot = node;
if (node === null) {
return new Node(key, value, null, null, Colors.RED);
}
if (node.getKey() > key) {
node.setLeft(this._put(key, value, node.getLeft()));
} else if (node.getKey() < key) {
node.setRight(this._put(key, value, node.getRight()));
} else {
node.setValue(value);
}
if (this.isRed(node.getRight()) && !this.isRed(node.getLeft())) {
newRoot = this._rotateLeft(node);
}
if (this.isRed(node.getLeft()) && this.isRed(node.getLeft().getLeft())) {
newRoot = this._rotateRight(node);
}
if (this.isRed(node.getLeft()) && this.isRed(node.getRight())) {
this._flipColors(node);
}
return newRoot;
};
/**
* Flip the colors of the both neighbours of given node.<br><br>
* Complexity: O(1).
*
* @private
* @method
* @param {Node} node Node.
*/
exports.RBTree.prototype._flipColors = function (node) {
node.getLeft().flipColor();
node.getRight().flipColor();
};
/*
* Rotates given node to the left.<br><br>
* Complexity: O(1).
*
* @private
* @method
* @param {Node} node Node.
* @return {Node} Right node.
*/
exports.RBTree.prototype._rotateLeft = function (node) {
var x = node.getRight();
if (x !== null) {
var temp = x.getLeft();
node.setRight(temp);
x.setLeft(node);
x.setColor(node.getColor());
node.setColor(Colors.RED);
}
return x;
};
/*
* Rotates given node to the right.<br><br>
* Complexity: O(1).
*
* @private
* @method
* @param {Node} node Node.
* @return {Node} Left node.
*/
exports.RBTree.prototype._rotateRight = function (node) {
var x = node.getLeft();
if (x !== null) {
var temp = x.getRight();
node.setLeft(temp);
x.setRight(node);
x.setColor(node.getColor());
node.setColor(Colors.RED);
}
return x;
};
/**
* Get value by the given key.<br><br>
* Complexity: O(log N).
*
* @public
* @param {Number} key A key to be searched for.
* @return {Object} A value which will be returned based on the key.
*/
exports.RBTree.prototype.get = function (key) {
return this._get(this._root, key);
};
/**
* Get value by the given key.<br><br>
*
* @private
* @param {Node} node Node to start with.
* @param {Number} key A key to be searched for.
* @return {Object} A value which will be returned based on the key.
*/
exports.RBTree.prototype._get = function (node, key) {
if (node === null) {
return undefined;
}
if (node.getKey() === key) {
return node.getValue();
}
if (node.getKey() > key) {
return this._get(node.getLeft(), key);
} else {
return this._get(node.getRight(), key);
}
};
})(typeof window === 'undefined' ? module.exports : window);