mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-12-08 19:06:00 +00:00
324 lines
9.6 KiB
JavaScript
324 lines
9.6 KiB
JavaScript
import BinarySearchTree from '../binary-search-tree/BinarySearchTree';
|
|
|
|
// Possible colors of red-black tree nodes.
|
|
const RED_BLACK_TREE_COLORS = {
|
|
red: 'red',
|
|
black: 'black',
|
|
};
|
|
|
|
// Color property name in meta information of the nodes.
|
|
const COLOR_PROP_NAME = 'color';
|
|
|
|
export default class RedBlackTree extends BinarySearchTree {
|
|
/**
|
|
* @param {*} value
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
insert(value) {
|
|
const insertedNode = super.insert(value);
|
|
|
|
// if (!this.root.left && !this.root.right) {
|
|
if (this.nodeComparator.equal(insertedNode, this.root)) {
|
|
// Make root to always be black.
|
|
this.makeNodeBlack(insertedNode);
|
|
} else {
|
|
// Make all newly inserted nodes to be red.
|
|
this.makeNodeRed(insertedNode);
|
|
}
|
|
|
|
// Check all conditions and balance the node.
|
|
this.balance(insertedNode);
|
|
|
|
return insertedNode;
|
|
}
|
|
|
|
/**
|
|
* @param {*} value
|
|
* @return {boolean}
|
|
*/
|
|
remove(value) {
|
|
throw new Error(`Can't remove ${value}. Remove method is not implemented yet`);
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode} node
|
|
*/
|
|
balance(node) {
|
|
// If it is a root node then nothing to balance here.
|
|
if (this.nodeComparator.equal(node, this.root)) {
|
|
return;
|
|
}
|
|
|
|
// If the parent is black then done. Nothing to balance here.
|
|
if (this.isNodeBlack(node.parent)) {
|
|
return;
|
|
}
|
|
|
|
const grandParent = node.parent.parent;
|
|
|
|
if (node.uncle && this.isNodeRed(node.uncle)) {
|
|
// If node has red uncle then we need to do RECOLORING.
|
|
|
|
// Recolor parent and uncle to black.
|
|
this.makeNodeBlack(node.uncle);
|
|
this.makeNodeBlack(node.parent);
|
|
|
|
if (!this.nodeComparator.equal(grandParent, this.root)) {
|
|
// Recolor grand-parent to red if it is not root.
|
|
this.makeNodeRed(grandParent);
|
|
} else {
|
|
// If grand-parent is black root don't do anything.
|
|
// Since root already has two black sibling that we've just recolored.
|
|
return;
|
|
}
|
|
|
|
// Now do further checking for recolored grand-parent.
|
|
this.balance(grandParent);
|
|
} else if (!node.uncle || this.isNodeBlack(node.uncle)) {
|
|
// If node uncle is black or absent then we need to do ROTATIONS.
|
|
|
|
if (grandParent) {
|
|
// Grand parent that we will receive after rotations.
|
|
let newGrandParent;
|
|
|
|
if (this.nodeComparator.equal(grandParent.left, node.parent)) {
|
|
// Left case.
|
|
if (this.nodeComparator.equal(node.parent.left, node)) {
|
|
// Left-left case.
|
|
newGrandParent = this.leftLeftRotation(grandParent);
|
|
} else {
|
|
// Left-right case.
|
|
newGrandParent = this.leftRightRotation(grandParent);
|
|
}
|
|
} else {
|
|
// Right case.
|
|
if (this.nodeComparator.equal(node.parent.right, node)) {
|
|
// Right-right case.
|
|
newGrandParent = this.rightRightRotation(grandParent);
|
|
} else {
|
|
// Right-left case.
|
|
newGrandParent = this.rightLeftRotation(grandParent);
|
|
}
|
|
}
|
|
|
|
// Set newGrandParent as a root if it doesn't have parent.
|
|
if (newGrandParent && newGrandParent.parent === null) {
|
|
this.root = newGrandParent;
|
|
|
|
// Recolor root into black.
|
|
this.makeNodeBlack(this.root);
|
|
}
|
|
|
|
// Check if new grand parent don't violate red-black-tree rules.
|
|
this.balance(newGrandParent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Left Left Case (p is left child of g and x is left child of p)
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} grandParentNode
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
leftLeftRotation(grandParentNode) {
|
|
// Memorize the parent of grand-parent node.
|
|
const grandGrandParent = grandParentNode.parent;
|
|
|
|
// Check what type of sibling is our grandParentNode is (left or right).
|
|
let grandParentNodeIsLeft;
|
|
if (grandGrandParent) {
|
|
grandParentNodeIsLeft = this.nodeComparator.equal(grandGrandParent.left, grandParentNode);
|
|
}
|
|
|
|
// Memorize grandParentNode's left node.
|
|
const parentNode = grandParentNode.left;
|
|
|
|
// Memorize parent's right node since we're going to transfer it to
|
|
// grand parent's left subtree.
|
|
const parentRightNode = parentNode.right;
|
|
|
|
// Make grandParentNode to be right child of parentNode.
|
|
parentNode.setRight(grandParentNode);
|
|
|
|
// Move child's right subtree to grandParentNode's left subtree.
|
|
grandParentNode.setLeft(parentRightNode);
|
|
|
|
// Put parentNode node in place of grandParentNode.
|
|
if (grandGrandParent) {
|
|
if (grandParentNodeIsLeft) {
|
|
grandGrandParent.setLeft(parentNode);
|
|
} else {
|
|
grandGrandParent.setRight(parentNode);
|
|
}
|
|
} else {
|
|
// Make parent node a root
|
|
parentNode.parent = null;
|
|
}
|
|
|
|
// Swap colors of grandParentNode and parentNode.
|
|
this.swapNodeColors(parentNode, grandParentNode);
|
|
|
|
// Return new root node.
|
|
return parentNode;
|
|
}
|
|
|
|
/**
|
|
* Left Right Case (p is left child of g and x is right child of p)
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} grandParentNode
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
leftRightRotation(grandParentNode) {
|
|
// Memorize left and left-right nodes.
|
|
const parentNode = grandParentNode.left;
|
|
const childNode = parentNode.right;
|
|
|
|
// We need to memorize child left node to prevent losing
|
|
// left child subtree. Later it will be re-assigned to
|
|
// parent's right sub-tree.
|
|
const childLeftNode = childNode.left;
|
|
|
|
// Make parentNode to be a left child of childNode node.
|
|
childNode.setLeft(parentNode);
|
|
|
|
// Move child's left subtree to parent's right subtree.
|
|
parentNode.setRight(childLeftNode);
|
|
|
|
// Put left-right node in place of left node.
|
|
grandParentNode.setLeft(childNode);
|
|
|
|
// Now we're ready to do left-left rotation.
|
|
return this.leftLeftRotation(grandParentNode);
|
|
}
|
|
|
|
/**
|
|
* Right Right Case (p is right child of g and x is right child of p)
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} grandParentNode
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
rightRightRotation(grandParentNode) {
|
|
// Memorize the parent of grand-parent node.
|
|
const grandGrandParent = grandParentNode.parent;
|
|
|
|
// Check what type of sibling is our grandParentNode is (left or right).
|
|
let grandParentNodeIsLeft;
|
|
if (grandGrandParent) {
|
|
grandParentNodeIsLeft = this.nodeComparator.equal(grandGrandParent.left, grandParentNode);
|
|
}
|
|
|
|
// Memorize grandParentNode's right node.
|
|
const parentNode = grandParentNode.right;
|
|
|
|
// Memorize parent's left node since we're going to transfer it to
|
|
// grand parent's right subtree.
|
|
const parentLeftNode = parentNode.left;
|
|
|
|
// Make grandParentNode to be left child of parentNode.
|
|
parentNode.setLeft(grandParentNode);
|
|
|
|
// Transfer all left nodes from parent to right sub-tree of grandparent.
|
|
grandParentNode.setRight(parentLeftNode);
|
|
|
|
// Put parentNode node in place of grandParentNode.
|
|
if (grandGrandParent) {
|
|
if (grandParentNodeIsLeft) {
|
|
grandGrandParent.setLeft(parentNode);
|
|
} else {
|
|
grandGrandParent.setRight(parentNode);
|
|
}
|
|
} else {
|
|
// Make parent node a root.
|
|
parentNode.parent = null;
|
|
}
|
|
|
|
// Swap colors of granParent and parent nodes.
|
|
this.swapNodeColors(parentNode, grandParentNode);
|
|
|
|
// Return new root node.
|
|
return parentNode;
|
|
}
|
|
|
|
/**
|
|
* Right Left Case (p is right child of g and x is left child of p)
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} grandParentNode
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
rightLeftRotation(grandParentNode) {
|
|
// Memorize right and right-left nodes.
|
|
const parentNode = grandParentNode.right;
|
|
const childNode = parentNode.left;
|
|
|
|
// We need to memorize child right node to prevent losing
|
|
// right child subtree. Later it will be re-assigned to
|
|
// parent's left sub-tree.
|
|
const childRightNode = childNode.right;
|
|
|
|
// Make parentNode to be a right child of childNode.
|
|
childNode.setRight(parentNode);
|
|
|
|
// Move child's right subtree to parent's left subtree.
|
|
parentNode.setLeft(childRightNode);
|
|
|
|
// Put childNode node in place of parentNode.
|
|
grandParentNode.setRight(childNode);
|
|
|
|
// Now we're ready to do right-right rotation.
|
|
return this.rightRightRotation(grandParentNode);
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} node
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
makeNodeRed(node) {
|
|
node.meta.set(COLOR_PROP_NAME, RED_BLACK_TREE_COLORS.red);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} node
|
|
* @return {BinarySearchTreeNode}
|
|
*/
|
|
makeNodeBlack(node) {
|
|
node.meta.set(COLOR_PROP_NAME, RED_BLACK_TREE_COLORS.black);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} node
|
|
* @return {boolean}
|
|
*/
|
|
isNodeRed(node) {
|
|
return node.meta.get(COLOR_PROP_NAME) === RED_BLACK_TREE_COLORS.red;
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} node
|
|
* @return {boolean}
|
|
*/
|
|
isNodeBlack(node) {
|
|
return node.meta.get(COLOR_PROP_NAME) === RED_BLACK_TREE_COLORS.black;
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} node
|
|
* @return {boolean}
|
|
*/
|
|
isNodeColored(node) {
|
|
return this.isNodeRed(node) || this.isNodeBlack(node);
|
|
}
|
|
|
|
/**
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} firstNode
|
|
* @param {BinarySearchTreeNode|BinaryTreeNode} secondNode
|
|
*/
|
|
swapNodeColors(firstNode, secondNode) {
|
|
const firstColor = firstNode.meta.get(COLOR_PROP_NAME);
|
|
const secondColor = secondNode.meta.get(COLOR_PROP_NAME);
|
|
|
|
firstNode.meta.set(COLOR_PROP_NAME, secondColor);
|
|
secondNode.meta.set(COLOR_PROP_NAME, firstColor);
|
|
}
|
|
}
|