Red–Black Tree
Data Structure · Stretch
Step through it: Binary Tree Traversal pattern
Step 1 of 5
A tree has no single first-to-last order, so we pick one. The three common orders differ only in when a node is visited compared with its children.
Step 2 of 5
Pre-order: the node first, then left, then right. Good for copying a tree.
Step 3 of 5
In-order: left, then the node, then right. On a binary search tree this gives the values in sorted order.
Step 4 of 5
Post-order: left, right, then the node. Children come before their parent, so it is the order for deleting a tree or adding up sizes.
Step 5 of 5
Your turn.
A red–black tree is a kind of self-balancing binary search tree in computer science. Each node of the binary tree has an extra bit, and that bit is often interpreted as the color (red or black) of the node. These color bits are used to ensure the tree remains approximately balanced during insertions and deletions.
Balance is preserved by painting each node of the tree with one of two colors in a way that satisfies certain properties, which collectively constrain how unbalanced the tree can become in the worst case. When the tree is modified, the new tree is subsequently rearranged and repainted to restore the coloring properties. The properties are designed in such a way that this rearranging and recoloring can be performed efficiently.
The balancing of the tree is not perfect, but it is good
enough to allow it to guarantee searching in O(log n) time,
where n is the total number of elements in the tree.
The insertion and deletion operations, along with the tree
rearrangement and recoloring, are also performed
in O(log n) time.
An example of a red–black tree:
Properties
In addition to the requirements imposed on a binary search tree the following must be satisfied by a red–black tree:
- Each node is either red or black.
- The root is black. This rule is sometimes omitted. Since the root can always be changed from red to black, but not necessarily vice versa, this rule has little effect on analysis.
- All leaves (NIL) are black.
- If a node is red, then both its children are black.
- Every path from a given node to any of its descendant NIL nodes contains the same number of black nodes.
Some definitions: the number of black nodes from the root to a node is the node's black depth; the uniform number of black nodes in all paths from root to the leaves is called the black-height of the red–black tree.
These constraints enforce a critical property of red–black trees: the path from the root to the farthest leaf is no more than twice as long as the path from the root to the nearest leaf. The result is that the tree is roughly height-balanced. Since operations such as inserting, deleting, and finding values require worst-case time proportional to the height of the tree, this theoretical upper bound on the height allows red–black trees to be efficient in the worst case, unlike ordinary binary search trees.
Balancing during insertion
If uncle is RED
If uncle is BLACK
- Left Left Case (
pis left child ofgandxis left child ofp) - Left Right Case (
pis left child ofgandxis right child ofp) - Right Right Case (
pis right child ofgandxis right child ofp) - Right Left Case (
pis right child ofgandxis left child ofp)
Left Left Case (See g, p and x)
Left Right Case (See g, p and x)
Right Right Case (See g, p and x)
Right Left Case (See g, p and x)
Code
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);
}
}References
Practice
Binary Tree Traversal
- Binary Tree Inorder TraversalEasy
- Binary Tree Preorder TraversalEasy
- Binary Tree Postorder TraversalEasy
- Same TreeEasy
- Symmetric TreeEasy
- Invert Binary TreeEasy
- Subtree of Another TreeEasy
- Merge Two Binary TreesEasy
- Construct Binary Tree from Preorder and Inorder TraversalMedium
- Construct Binary Tree from Inorder and Postorder TraversalMedium
- Validate Binary Search TreeMedium
- Kth Smallest Element in a BSTMedium
- Flatten Binary Tree to Linked ListMedium
- Lowest Common Ancestor of a Binary TreeMedium
- Binary Search Tree IteratorMedium
- Recover Binary Search TreeMedium
- Serialize and Deserialize Binary TreeHard
- Binary Tree Maximum Path SumHard
Spotted a mistake or something unclear? Report it on GitHub.