Open In App

Red Black Tree (RB-Tree) Using C++

Last Updated : 17 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

A Red Black Tree is a self-balancing binary search tree where each node has an extra bit for denoting the color of the node, either red or black. This color coding is used to ensure that the tree remains balanced during insertions and deletions. In this article, we will learn how to implement a Red-Black Tree in C++ along with its basic operations.

What is a Red Black Tree?

A Red Black Tree is a type of self-balancing binary search tree. It maintains a near-perfect balance by coloring each node-red or black and enforcing a set of properties that guarantee that the deepest path in the tree is no more than twice as long as the shallowest path.

Properties of Red-Black Trees

  • Every node is either red or black.
  • The root node is always black.
  • Every leaf (NIL or null node) is black.
  • If a node is red, then both its children are black.
  • For each node, all simple paths from the node to descendant leaves contain the same number of black nodes.

Implementation of Red Black Tree in C++

A Red Black Tree can be implemented as an extension of a binary search tree. Each node in the tree will have an extra attribute to store its color.

Representation of Red Black Tree in C++

To represent a Red Black Tree in C++, we'll use a struct Node to represent each node of the red-black tree and a class RedBlackTree that will contain all of the member functions. We have used the template to keep the red-black tree generic so that it can support multiple data types.

enum Color { RED, BLACK };

template <typename T>
class RedBlackTree {
private:
struct Node {
T data;
Color color;
Node *left;
Node*right;
Node*parent;
};

Here,

  • data: stores the value in the node.
  • color: an enum that stores the color of the node (RED or BLACK).
  • left, right, and parent: store pointers to the left child, right child, and parent nodes respectively.

The following diagram represents the structure of red black tree:

Red-black-tree
Red-Black-Tree Diagram

Basic Operations of Red Black Tree in C++

Following are some of the basic operations of a Red Black Tree that are required to manipulate its elements:

Operation

Description

Time Complexity

Space Complexity

Insert

Inserts a new element into the tree.

O(log n)

O(1)

Delete node

Removes an element from the tree.

O(log n)

O(1)

Search

Searches for an element in the tree.

O(log n)

O(1)

Left Rotate

Performs a left rotation on a given node.

O(1)

O(1)

Right Rotate

Performs a right rotation on a given node.

O(1)

O(1)

Here, n represents the number of nodes in the red black tree.

Implementation of Insert Function

  • Perform standard BST insertion and color the newly inserted node red.
  • If the newly inserted node is the root, color it black and return.
  • Do the following while the parent of the newly inserted node is red:
    • If the parent is the left child of the grandparent:
      • If the right child of the grandparent is red, recolor and move up the tree.
      • Else, perform rotations and recolor.
    • If the parent is the right child of the grandparent:
      • If the left child of the grandparent is red, recolor and move up the tree.
      • Else, perform rotations and recolor.
  • Color the root black.

Implementation of Delete Node Function

  • Perform standard BST deletion.
  • If the deleted node was red, we're done.
  • If the deleted node was black, we need to fix the double black violation:
    • If the sibling is red, perform rotations and recolor.
    • If the sibling is black with both black children, recolor and move up the tree.
    • If the sibling is black with at least one red child, perform rotations and recolor.

Implementation of Search Function

  • Start from the root node.
  • Compare the value to be searched with the value of the current node.
  • If the value is less than the current node, move to the left child.
  • If the value is greater than the current node, move to the right child.
  • If the value is equal to the current node, return true.
  • If a leaf node is reached and the value is not found, return false.

Implementation of Rotation Functions

  • Left Rotation:
    • Make the right child of the current node the new root of the subtree.
    • Make the old root the left child of the new root.
    • Update the parent pointers accordingly.
  • Right Rotation:
    • Make the left child of the current node the new root of the subtree.
    • Make the old root the right child of the new root.
    • Update the parent pointers accordingly.

C++ Program to Implement Red Black Tree

The following program demonstrates the implementation of a Red Black Tree in C++:

C++
// C++ Program to Implement Red Black Tree

#include <iostream>
using namespace std;

// Enumeration for colors of nodes in Red-Black Tree
enum Color { RED, BLACK };

// Class template for Red-Black Tree
template <typename T> class RedBlackTree {
private:
    // Structure for a node in Red-Black Tree
    struct Node {
        T data;
        Color color;
        Node* parent;
        Node* left;
        Node* right;

        // Constructor to initialize node with data and
        // color
        Node(T value)
            : data(value)
            , color(RED)
            , parent(nullptr)
            , left(nullptr)
            , right(nullptr)
        {
        }
    };

    Node* root; // Root of the Red-Black Tree

    // Utility function: Left Rotation
    void rotateLeft(Node*& node)
    {
        Node* child = node->right;
        node->right = child->left;
        if (node->right != nullptr)
            node->right->parent = node;
        child->parent = node->parent;
        if (node->parent == nullptr)
            root = child;
        else if (node == node->parent->left)
            node->parent->left = child;
        else
            node->parent->right = child;
        child->left = node;
        node->parent = child;
    }

    // Utility function: Right Rotation
    void rotateRight(Node*& node)
    {
        Node* child = node->left;
        node->left = child->right;
        if (node->left != nullptr)
            node->left->parent = node;
        child->parent = node->parent;
        if (node->parent == nullptr)
            root = child;
        else if (node == node->parent->left)
            node->parent->left = child;
        else
            node->parent->right = child;
        child->right = node;
        node->parent = child;
    }

    // Utility function: Fixing Insertion Violation
    void fixInsert(Node*& node)
    {
        Node* parent = nullptr;
        Node* grandparent = nullptr;
        while (node != root && node->color == RED
               && node->parent->color == RED) {
            parent = node->parent;
            grandparent = parent->parent;
            if (parent == grandparent->left) {
                Node* uncle = grandparent->right;
                if (uncle != nullptr
                    && uncle->color == RED) {
                    grandparent->color = RED;
                    parent->color = BLACK;
                    uncle->color = BLACK;
                    node = grandparent;
                }
                else {
                    if (node == parent->right) {
                        rotateLeft(parent);
                        node = parent;
                        parent = node->parent;
                    }
                    rotateRight(grandparent);
                    swap(parent->color, grandparent->color);
                    node = parent;
                }
            }
            else {
                Node* uncle = grandparent->left;
                if (uncle != nullptr
                    && uncle->color == RED) {
                    grandparent->color = RED;
                    parent->color = BLACK;
                    uncle->color = BLACK;
                    node = grandparent;
                }
                else {
                    if (node == parent->left) {
                        rotateRight(parent);
                        node = parent;
                        parent = node->parent;
                    }
                    rotateLeft(grandparent);
                    swap(parent->color, grandparent->color);
                    node = parent;
                }
            }
        }
        root->color = BLACK;
    }

    // Utility function: Fixing Deletion Violation
    void fixDelete(Node*& node)
    {
        while (node != root && node->color == BLACK) {
            if (node == node->parent->left) {
                Node* sibling = node->parent->right;
                if (sibling->color == RED) {
                    sibling->color = BLACK;
                    node->parent->color = RED;
                    rotateLeft(node->parent);
                    sibling = node->parent->right;
                }
                if ((sibling->left == nullptr
                     || sibling->left->color == BLACK)
                    && (sibling->right == nullptr
                        || sibling->right->color
                               == BLACK)) {
                    sibling->color = RED;
                    node = node->parent;
                }
                else {
                    if (sibling->right == nullptr
                        || sibling->right->color == BLACK) {
                        if (sibling->left != nullptr)
                            sibling->left->color = BLACK;
                        sibling->color = RED;
                        rotateRight(sibling);
                        sibling = node->parent->right;
                    }
                    sibling->color = node->parent->color;
                    node->parent->color = BLACK;
                    if (sibling->right != nullptr)
                        sibling->right->color = BLACK;
                    rotateLeft(node->parent);
                    node = root;
                }
            }
            else {
                Node* sibling = node->parent->left;
                if (sibling->color == RED) {
                    sibling->color = BLACK;
                    node->parent->color = RED;
                    rotateRight(node->parent);
                    sibling = node->parent->left;
                }
                if ((sibling->left == nullptr
                     || sibling->left->color == BLACK)
                    && (sibling->right == nullptr
                        || sibling->right->color
                               == BLACK)) {
                    sibling->color = RED;
                    node = node->parent;
                }
                else {
                    if (sibling->left == nullptr
                        || sibling->left->color == BLACK) {
                        if (sibling->right != nullptr)
                            sibling->right->color = BLACK;
                        sibling->color = RED;
                        rotateLeft(sibling);
                        sibling = node->parent->left;
                    }
                    sibling->color = node->parent->color;
                    node->parent->color = BLACK;
                    if (sibling->left != nullptr)
                        sibling->left->color = BLACK;
                    rotateRight(node->parent);
                    node = root;
                }
            }
        }
        node->color = BLACK;
    }

    // Utility function: Find Node with Minimum Value
    Node* minValueNode(Node*& node)
    {
        Node* current = node;
        while (current->left != nullptr)
            current = current->left;
        return current;
    }

    // Utility function: Transplant nodes in Red-Black Tree
    void transplant(Node*& root, Node*& u, Node*& v)
    {
        if (u->parent == nullptr)
            root = v;
        else if (u == u->parent->left)
            u->parent->left = v;
        else
            u->parent->right = v;
        if (v != nullptr)
            v->parent = u->parent;
    }

    // Utility function: Helper to print Red-Black Tree
    void printHelper(Node* root, string indent, bool last)
    {
        if (root != nullptr) {
            cout << indent;
            if (last) {
                cout << "R----";
                indent += "   ";
            }
            else {
                cout << "L----";
                indent += "|  ";
            }
            string sColor
                = (root->color == RED) ? "RED" : "BLACK";
            cout << root->data << "(" << sColor << ")"
                 << endl;
            printHelper(root->left, indent, false);
            printHelper(root->right, indent, true);
        }
    }

    // Utility function: Delete all nodes in the Red-Black
    // Tree
    void deleteTree(Node* node)
    {
        if (node != nullptr) {
            deleteTree(node->left);
            deleteTree(node->right);
            delete node;
        }
    }

public:
    // Constructor: Initialize Red-Black Tree
    RedBlackTree()
        : root(nullptr)
    {
    }

    // Destructor: Delete Red-Black Tree
    ~RedBlackTree() { deleteTree(root); }

    // Public function: Insert a value into Red-Black Tree
    void insert(T key)
    {
        Node* node = new Node(key);
        Node* parent = nullptr;
        Node* current = root;
        while (current != nullptr) {
            parent = current;
            if (node->data < current->data)
                current = current->left;
            else
                current = current->right;
        }
        node->parent = parent;
        if (parent == nullptr)
            root = node;
        else if (node->data < parent->data)
            parent->left = node;
        else
            parent->right = node;
        fixInsert(node);
    }

    // Public function: Remove a value from Red-Black Tree
    void remove(T key)
    {
        Node* node = root;
        Node* z = nullptr;
        Node* x = nullptr;
        Node* y = nullptr;
        while (node != nullptr) {
            if (node->data == key) {
                z = node;
            }

            if (node->data <= key) {
                node = node->right;
            }
            else {
                node = node->left;
            }
        }

        if (z == nullptr) {
            cout << "Key not found in the tree" << endl;
            return;
        }

        y = z;
        Color yOriginalColor = y->color;
        if (z->left == nullptr) {
            x = z->right;
            transplant(root, z, z->right);
        }
        else if (z->right == nullptr) {
            x = z->left;
            transplant(root, z, z->left);
        }
        else {
            y = minValueNode(z->right);
            yOriginalColor = y->color;
            x = y->right;
            if (y->parent == z) {
                if (x != nullptr)
                    x->parent = y;
            }
            else {
                transplant(root, y, y->right);
                y->right = z->right;
                y->right->parent = y;
            }
            transplant(root, z, y);
            y->left = z->left;
            y->left->parent = y;
            y->color = z->color;
        }
        delete z;
        if (yOriginalColor == BLACK) {
            fixDelete(x);
        }
    }

    // Public function: Print the Red-Black Tree
    void printTree()
    {
        if (root == nullptr)
            cout << "Tree is empty." << endl;
        else {
            cout << "Red-Black Tree:" << endl;
            printHelper(root, "", true);
        }
    }
};

// Driver program to test Red-Black Tree
int main()
{
    RedBlackTree<int> rbtree;

    // Inserting values into Red-Black Tree
    rbtree.insert(7);
    rbtree.insert(3);
    rbtree.insert(18);
    rbtree.insert(10);
    rbtree.insert(22);
    rbtree.insert(8);
    rbtree.insert(11);
    rbtree.insert(26);
    rbtree.insert(2);
    rbtree.insert(6);

    // Printing Red-Black Tree
    rbtree.printTree();

    // Deleting nodes from Red-Black Tree
    cout << "After deleting 18:" << endl;
    rbtree.remove(18);
    rbtree.printTree();

    cout << "After deleting 11:" << endl;
    rbtree.remove(11);
    rbtree.printTree();

    cout << "After deleting 3:" << endl;
    rbtree.remove(3);
    rbtree.printTree();

    return 0;
}


Output

Red-Black Tree:
R----7(BLACK)
L----3(BLACK)
| L----2(RED)
| R----6(RED)
R----18(RED)
L----10(BLACK)
| L----8(RED)
| R----11(RED)
R----22(BLACK)
R----26(RED)

After deleting 18:
Red-Black Tree:
R----7(BLACK)
L----3(BLACK)
| L----2(RED)
| R----6(RED)
R----22(RED)
L----10(BLACK)
| L----8(RED)
| R----11(RED)
R----26(BLACK)

After deleting 11:
Red-Black Tree:
R----7(BLACK)
L----3(BLACK)
| L----2(RED)
| R----6(RED)
R----22(RED)
L----10(BLACK)
| L----8(RED)
R----26(BLACK)

After deleting 3:
Red-Black Tree:
R----7(BLACK)
L----6(BLACK)
| L----2(RED)
R----22(RED)
L----10(BLACK)
| L----8(RED)
R----26(BLACK)

Applications of Red Black Tree

Following are some of the common applications of red black tree:

  • Red-black trees are used to implement associative arrays and set data structures in many programming languages, such as Java's TreeMap and TreeSet, and C++'s std::map and std::set.
  • They are used in memory management within operating systems, particularly in the Linux kernel for efficiently organizing and searching ranges of free memory.
  • Red-black trees are employed in process scheduling algorithms in operating systems, where tasks need to be efficiently inserted, removed, and searched based on priority.
  • They are utilized in database management systems for implementing efficient indexing structures, allowing for fast insertion, deletion, and lookup operations.
  • Red-black trees are used to implement symbol tables in compilers and interpreters, enabling efficient storage and retrieval of variable and function names.

Next Article
Article Tags :
Practice Tags :

Similar Reads