Tree: red black tree (RBT) C + + implementation

preface

Red black tree is widely used. It is essentially a special AVL tree, but it is better than AVL tree. The efficiency of query, insertion and deletion of red black tree is O(logN). The underlying implementation of set and map in C + + and event manager in Epoll is red black tree.


Characteristics of red black tree

In addition to the characteristics of AVL tree, red black tree also includes the following characteristics:

  1. The node is black or red
  2. The root is black
  3. Leaf nodes are black
  4. Each red node must have two black child nodes
  5. The simple path from any node to each leaf node contains the same number of black nodes

be careful:
In feature 3, the leaf node represents the empty node, that is, the empty node is black;
Feature 4 represents that there can be no continuous red nodes;
In feature 5, a simple path means that there can be no duplicate nodes. This sentence means that no path can be twice as long as other paths


Introduction to red black tree node

  • node
struct TreeNode
{
    int val;
    bool color;
    struct TreeNode *left;
    struct TreeNode *right;
    struct TreeNode *parent;
    TreeNode(int val) : val(val), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};
  • Left and right rotation
void RBT::left_rotate(TreeNode* x)
{
    TreeNode* y = x->right;
    x->right = y->left;
    if (y->left)
        y->left->parent = x;

    y->parent = x->parent;
    if (_root == x)
        _root = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else
        x->parent->right = y;

    y->left = x;
    x->parent = y;
}


void RBT::right_rotate(TreeNode* x)
{
    TreeNode* y = x->left;
    x->left = y->right;
    if (y->right)
        y->right->parent = x;
    
    y->parent = x->parent;
    if (_root == x)
        _root = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else 
        x->parent->right = y;
    
    y->right = x;
    x->parent = y;
}


Red black tree insertion

Through the nature of red black tree, we know that the tree has strict requirements for black nodes, so we choose to insert red nodes when inserting.

Several situations of insertion:

  1. After inserting a node, the parent node is empty, which means that the tree is originally an empty tree. You only need to dye the node color black;
  2. After inserting a node, the parent node is black, which will not destroy the nature of the red black tree, so you don't have to do anything;
  3. After inserting a node, if the parent node is red and the tertiary node exists and is also red, flip the colors of the grandfather node, parent node and tertiary node, and check whether the grandfather node is the root node;
  4. After inserting a node, the parent node is red and the tertiary node does not exist. This situation is more complicated:
    1) If the inserted node is on the left of the parent node, rotate the grandfather node to the right and flip the colors of the parent node and the grandfather node;
    2) If the inserted node is on the right side of the parent node, rotate to the left of the parent node first, and then perform 1) operation.

Wikipedia and many blogs have also discussed the fifth case: the parent node is red and the tertiary node is black, but I personally don't think it is necessary, because the red black tree itself under this premise has violated its nature (Article 5).

  • Illustration

Case 1 and case 2 are relatively simple and need not be elaborated too much.
Case 3:

Case 4:
1) Situation

2) Situation

  • code
void RBT::insert_node(TreeNode* node)
{
    TreeNode* curr = _root, *pre = nullptr;
    while (curr)
    {
        pre = curr;
        if (node->val < curr->val)
            curr = curr->left;
        else if (node->val > curr->val)
            curr = curr->right;
        else //You already have this value, so you don't need to add it
            return;
    }
    node->parent = pre;
    if (!pre)
        _root = node;
    else{
        if (node->val < pre->val)
            pre->left = node;
        else
            pre->right = node;
    }
    insert_fixup(node);
}

void RBT::insert_fixup(TreeNode* node)
{
    TreeNode* parent, *gparent, *uncle;
    //Only case 3 and case 4 need to be adjusted
    while ((parent=node->parent)!=nullptr && parent->color==RED)
    {
        gparent = parent->parent;
        if (parent == gparent->left){//The parent node is on the left and symmetrical. The other case below is the same
            //Situation 3
            if ((uncle=gparent->right)!=nullptr && uncle->color==RED){
                parent->color = BLACK;
                uncle->color = BLACK;
                gparent->color = RED;
                node = gparent;
                continue; //Adjust the grandfather as a newly inserted node
            }
            //Situation 4
            if (parent->right == node){//It may be case 2) in case 4, then make a rotation first
                left_rotate(parent);
                TreeNode* t = parent;
                parent = node;
                node = t;
            }
            //Case 1 of case 4
            parent->color = BLACK;
            gparent->color = RED;
            right_rotate(gparent);

        }else //Symmetrical operation
        {
            //Situation 3
            if ((uncle=gparent->left)!=nullptr && uncle->color==RED){
                parent->color = BLACK;
                uncle->color = BLACK;
                gparent->color = RED;
                node = gparent;
                continue; //Adjust the grandfather as a newly inserted node
            }
            //Situation 4
            if (parent->left == node){//It may be case 2) in case 4, then make a rotation first
                right_rotate(parent);
                TreeNode* t = parent;
                parent = node;
                node = t;
            }
            //Case 1 of case 4
            parent->color = BLACK;
            gparent->color = RED;
            left_rotate(gparent);
        }
    }
    _root->color = BLACK;
}

Red black tree deletion

I Deleting a node has two child nodes, which can be transformed into deleting a single child node or leaf node;

II If the deleted node has a single child node, the node to be deleted must be black and the child node must be red. Top the child node and dye it black;

III Delete a leaf node. If the leaf node is red, you can delete it directly;

IV Delete a leaf node. The leaf node is black. The leaf node must have siblings. This situation is complex and needs to be discussed by situation: (at this time, only the leaf node on the left is discussed, and the right is symmetrical)

  1. If the sibling node is red, dye the sibling node black, dye the left son of the sibling node red, and then turn left.

  1. If the sibling node is black and is a leaf node, dye the sibling node red. Because the number of black nodes is uneven at this time, it is necessary to adjust the parent node again;

  2. If the sibling node is black and not a leaf node, the child of the sibling node must be red. There are three cases:

1) The sibling node has a red right child, which is transformed as follows:
Assign the color of the parent node to the brother node, then dye the right paper color of the parent node and brother node into black, and finally turn left

2) The sibling node has a red left child, which is transformed as follows:
Dye the brother node red, the left child of the brother node black, and finally turn right to the brother node, which returns to the situation of 1)

3) The sibling node has two red children, which are transformed as follows:
This case is essentially the same as case 1). Assign the color of the parent node to the brother node, then dye the right child paper color of the parent node and brother node into black, and finally turn left

  • code
void RBT::delete_node(TreeNode* node)
{
    //replace represents the node replaced after deletion
    //Parent is the parent node of the replace node
    TreeNode* replace = nullptr, *parent = nullptr;
    //If the deleted node has left and right children, find the largest child in the left subtree or the smallest child in the right subtree for replacement
    if (node->left && node->right){
        TreeNode* succ = nullptr;
        for (succ = node->left; succ->right; succ = succ->right);
        node->val = succ->val;
        delete_node(succ);
        return;
    }else{//Delete a node as a leaf node or have only one child
        //If you delete the root
        if (!node->parent){
            _root = (node->left ? node->left : node->right);
            replace = _root;
            if (_root)
                _root->parent = nullptr;
        }else{//Deleted is not a root
            TreeNode* child = (node->left ? node->left : node->right);
            if  (node->parent->left == node)
                node->parent->left = child;
            else
                node->parent->right = child;
            
            if (child)
                child->parent = node->parent;
            replace = child;
            parent = node->parent;
        }
    }

    //If the deleted node is red, it ends directly
    if (node->color == BLACK)
        delete_fixup(replace, parent);
}


void RBT::delete_fixup(TreeNode* replace,  TreeNode* parent)
{
    TreeNode* brother = nullptr;
    // If the replacement node is black and not the root node.
    //After the above deleteNode method, the parent must not be null
    while ((replace == nullptr || replace->color == BLACK) && replace != this->_root){
        //Everything about the left child's position,
        if (parent->left == replace) {
            brother = parent->right;
            // case1 brother is black, parent is red, and parent is left-handed. The brother of replace has changed into the situation of brother black
            if (brother->color == RED) {
                brother->color = BLACK;
                parent->color = RED;
                left_rotate(parent);
                brother = parent->right;
            }
            // Through the top, whether entering or not, the brothers became black
            // Case 2 brother black, and both of his children are black
            if ((brother->left == nullptr || brother->left->color == BLACK) && (brother->right == nullptr || brother->right->color == BLACK)) {
            // If the parent is red at this time, transfer the brother's black to the parent
                if (parent->color == RED) {
                parent->color = BLACK;
                brother->color = RED;
                break;
            } else {// If the parent is black at this time, that is, it is all black at this time, paint the brother red, resulting in one less black in the brother branch and one less black in the whole branch. It is necessary to adjust the parent again
                brother->color = RED;
                replace = parent;
                parent = replace->parent;
            }
        } else {
            // case3 brother black, the brother's left child is red
            if (brother->left != nullptr && brother->left->color == RED) {
                brother->left->color = parent->color;
                parent->color = BLACK;
                right_rotate(brother);
                left_rotate(parent);
            // case4 brother black, the brother's right child is red
            } else if (brother->right != nullptr && brother->right->color == RED) {
                brother->color = parent->color;
                parent->color = BLACK;
                brother->right->color = BLACK;
                left_rotate(parent);
            }
            break;
        }
    } else {//In the case of symmetrical position, reverse the direction of rotation
        brother = parent->left;
        // case1 brother is black, parent is red, and parent is left-handed. The brother of replace has changed into the situation of brother black
        if (brother->color == RED) {
            brother->color = BLACK;
            parent->color = RED;
            right_rotate(parent);
            brother = parent->left;
        }
        // Through the top, whether entering or not, the brothers became black
        // Case 2 brother black, and both of his children are black
        if ((brother->left == nullptr || brother->left->color == BLACK)
            && (brother->right == nullptr || brother->right->color == BLACK)) {
        // If the parent is red at this time, transfer the brother's black to the parent
            if (parent->color == RED) {
                parent->color = BLACK;
                brother->color = RED;
                break;
            } else {// If the parent is black at this time, that is, it is all black at this time, paint the brother red, resulting in one less black in the brother branch and one less black in the whole branch. It is necessary to adjust the parent again
                brother->color = RED;
                replace = parent;
                parent = replace->parent;
                }
            } else {
                // case3 brother black, the left child of the brother is red, and the right child is free
                if (brother->right != nullptr && brother->right->color == RED) {
                    brother->right->color = parent->color;
                    parent->color = BLACK;
                    left_rotate(brother);
                    right_rotate(parent);
                // case4 brother black, the right child of the brother is red, and the left child is optional
                } else if (brother->left != nullptr && brother->left->color == RED) {
                    brother->color = parent->color;
                    parent->color = BLACK;
                    brother->left->color = BLACK;
                    right_rotate(parent);
                }
                break;
            }
        }
    }
    //Here we can deal with the case where the deleted node has only one child node. If it is a root, it will also be blackened.
    if (replace != nullptr)
        replace->color = BLACK;
}


Red black tree complete program

#include <iostream>
#include <algorithm>
#include <map>
#include <unordered_map>
#include <queue>
#include <set>
#include <vector>
#include <fstream>
#include <sstream>
#include <string.h>
#include <memory>
#include <limits>
#include <list>
#include <regex>
#include <functional>
#include <math.h>
#include <unordered_set>
#define RED 0
#define BLACK 1
using namespace std;

struct TreeNode
{
    int val;
    bool color;
    struct TreeNode *left;
    struct TreeNode *right;
    struct TreeNode *parent;
    TreeNode(int val) : val(val), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};

class RBT
{
    TreeNode* _root;
public:
    RBT();
    ~RBT();
    void left_rotate(TreeNode* n);
    void right_rotate(TreeNode* n);
    void insert_node(TreeNode*);
    void insert_fixup(TreeNode*);
    void delete_node(TreeNode*);
    void delete_fixup(TreeNode*, TreeNode*);
    void preOrder(TreeNode*) const;
    void inOrder(TreeNode*) const;
    TreeNode* getRoot()const { return _root; }
};

RBT::RBT()
:_root(nullptr)
{
    cout << "RBT()" << endl;
}

RBT::~RBT()
{
    cout << "~RBT()" << endl;
}

void RBT::preOrder(TreeNode* root) const
{
    if (root)
    {
        cout << root->val << " ";
        preOrder(root->left);
        preOrder(root->right);
    }
}

void RBT::inOrder(TreeNode* root) const
{
    if (root)
    {
        inOrder(root->left);
        cout << root->val << " ";
        inOrder(root->right);
    }
}


void RBT::left_rotate(TreeNode* x)
{
    TreeNode* y = x->right;
    x->right = y->left;
    if (y->left)
        y->left->parent = x;

    y->parent = x->parent;
    if (_root == x)
        _root = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else
        x->parent->right = y;

    y->left = x;
    x->parent = y;
}


void RBT::right_rotate(TreeNode* x)
{
    TreeNode* y = x->left;
    x->left = y->right;
    if (y->right)
        y->right->parent = x;
    
    y->parent = x->parent;
    if (_root == x)
        _root = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else 
        x->parent->right = y;
    
    y->right = x;
    x->parent = y;
}

void RBT::insert_node(TreeNode* node)
{
    TreeNode* curr = _root, *pre = nullptr;
    while (curr)
    {
        pre = curr;
        if (node->val < curr->val)
            curr = curr->left;
        else if (node->val > curr->val)
            curr = curr->right;
        else //You already have this value, so you don't need to add it
            return;
    }
    node->parent = pre;
    if (!pre)
        _root = node;
    else{
        if (node->val < pre->val)
            pre->left = node;
        else
            pre->right = node;
    }
    insert_fixup(node);
}


/*
Through the nature of red black tree, we know that the tree has strict requirements for black nodes, so we choose to insert red nodes when inserting.
Several situations of insertion:
1.After inserting a node, the parent node is empty, which means that the tree is originally an empty tree. You only need to dye the node color black;
2.After inserting a node, the parent node is black, which will not destroy the nature of the red black tree, so you don't have to do anything;
3.After inserting a node, if the parent node is red and the tertiary node exists and is also red, flip the colors of the grandfather node, parent node and tertiary node, and check whether the grandfather node is the root node;
4.After inserting a node, the parent node is red and the tertiary node does not exist. This situation is more complicated:
1)If the inserted node is on the left of the parent node, rotate the grandfather node to the right and flip the colors of the parent node and the grandfather node;
2)If the inserted node is on the right side of the parent node, rotate to the left of the parent node first, and then perform 1) operation.

Wikipedia and many blogs have also discussed the fifth case: the parent node is red and the tertiary node is black, but I personally don't think it is necessary, because the red black tree itself under this premise has violated its nature (Article 5).

*/

void RBT::insert_fixup(TreeNode* node)
{
    TreeNode* parent, *gparent, *uncle;
    //Only case 3 and case 4 need to be adjusted
    while ((parent=node->parent)!=nullptr && parent->color==RED)
    {
        gparent = parent->parent;
        if (parent == gparent->left){//The parent node is on the left and symmetrical. The other case below is the same
            //Situation 3
            if ((uncle=gparent->right)!=nullptr && uncle->color==RED){
                parent->color = BLACK;
                uncle->color = BLACK;
                gparent->color = RED;
                node = gparent;
                continue; //Adjust the grandfather as a newly inserted node
            }
            //Situation 4
            if (parent->right == node){//It may be case 2) in case 4, then make a rotation first
                left_rotate(parent);
                TreeNode* t = parent;
                parent = node;
                node = t;
            }
            //Case 1 of case 4
            parent->color = BLACK;
            gparent->color = RED;
            right_rotate(gparent);

        }else //Symmetrical operation
        {
            //Situation 3
            if ((uncle=gparent->left)!=nullptr && uncle->color==RED){
                parent->color = BLACK;
                uncle->color = BLACK;
                gparent->color = RED;
                node = gparent;
                continue; //Adjust the grandfather as a newly inserted node
            }
            //Situation 4
            if (parent->left == node){//It may be case 2) in case 4, then make a rotation first
                right_rotate(parent);
                TreeNode* t = parent;
                parent = node;
                node = t;
            }
            //Case 1 of case 4
            parent->color = BLACK;
            gparent->color = RED;
            left_rotate(gparent);
        }
    }
    _root->color = BLACK;
}



/*
Delete node:
I Deleting a node has two child nodes, which can be transformed into deleting a single child node or leaf node;

II If the deleted node has a single child node, the node to be deleted must be black and the child node must be red. Top the child node and dye it black;

III Delete a leaf node. If the leaf node is red, you can delete it directly;

IV Delete a leaf node. The leaf node is black. The leaf node must have siblings. This situation is complex and needs to be discussed by situation: (at this time, only the leaf node on the left is discussed, and the right is symmetrical)

1. If the sibling node is red, dye the sibling node black, dye the left son of the sibling node red, and then turn left.

2. If the sibling node is black and is a leaf node, dye the sibling node red. Because the number of black nodes is uneven at this time, it is necessary to adjust the parent node again;

3. If the sibling node is black and not a leaf node, the child of the sibling node must be red. There are three cases:
1)The sibling node has a red right child, which is transformed as follows:
Assign the color of the parent node to the brother node, then dye the right paper color of the parent node and brother node into black, and finally turn left

2)The sibling node has a red left child, which is transformed as follows:
Dye the brother node red, the left child of the brother node black, and finally turn right to the brother node, which returns to the situation of 1)

3)The sibling node has two red children, which are transformed as follows:
This case is essentially the same as case 1). Assign the color of the parent node to the brother node, then dye the right child paper color of the parent node and brother node into black, and finally turn left

*/

void RBT::delete_node(TreeNode* node)
{
    //replace represents the node replaced after deletion
    //Parent is the parent node of the replace node
    TreeNode* replace = nullptr, *parent = nullptr;
    //If the deleted node has left and right children, find the largest child in the left subtree or the smallest child in the right subtree for replacement
    if (node->left && node->right){
        TreeNode* succ = nullptr;
        for (succ = node->left; succ->right; succ = succ->right);
        node->val = succ->val;
        delete_node(succ);
        return;
    }else{//Delete a node as a leaf node or have only one child
        //If you delete the root
        if (!node->parent){
            _root = (node->left ? node->left : node->right);
            replace = _root;
            if (_root)
                _root->parent = nullptr;
        }else{//Deleted is not a root
            TreeNode* child = (node->left ? node->left : node->right);
            if  (node->parent->left == node)
                node->parent->left = child;
            else
                node->parent->right = child;
            
            if (child)
                child->parent = node->parent;
            replace = child;
            parent = node->parent;
        }
    }

    //If the deleted node is red, it ends directly
    if (node->color == BLACK)
        delete_fixup(replace, parent);
}


void RBT::delete_fixup(TreeNode* replace,  TreeNode* parent)
{
    TreeNode* brother = nullptr;
    // If the replacement node is black and not the root node.
    //After the above deleteNode method, the parent must not be null
    while ((replace == nullptr || replace->color == BLACK) && replace != this->_root){
        //Everything about the left child's position,
        if (parent->left == replace) {
            brother = parent->right;
            // case1 brother is black, parent is red, and parent is left-handed. The brother of replace has changed into the situation of brother black
            if (brother->color == RED) {
                brother->color = BLACK;
                parent->color = RED;
                left_rotate(parent);
                brother = parent->right;
            }
            // Through the top, whether entering or not, the brothers became black
            // Case 2 brother black, and both of his children are black
            if ((brother->left == nullptr || brother->left->color == BLACK) && (brother->right == nullptr || brother->right->color == BLACK)) {
            // If the parent is red at this time, transfer the brother's black to the parent
                if (parent->color == RED) {
                parent->color = BLACK;
                brother->color = RED;
                break;
            } else {// If the parent is black at this time, that is, it is all black at this time, paint the brother red, resulting in one less black in the brother branch and one less black in the whole branch. It is necessary to adjust the parent again
                brother->color = RED;
                replace = parent;
                parent = replace->parent;
            }
        } else {
            // case3 brother black, the brother's left child is red
            if (brother->left != nullptr && brother->left->color == RED) {
                brother->left->color = parent->color;
                parent->color = BLACK;
                right_rotate(brother);
                left_rotate(parent);
            // case4 brother black, the brother's right child is red
            } else if (brother->right != nullptr && brother->right->color == RED) {
                brother->color = parent->color;
                parent->color = BLACK;
                brother->right->color = BLACK;
                left_rotate(parent);
            }
            break;
        }
    } else {//In the case of symmetrical position, reverse the direction of rotation
        brother = parent->left;
        // case1 brother is black, parent is red, and parent is left-handed. The brother of replace has changed into the situation of brother black
        if (brother->color == RED) {
            brother->color = BLACK;
            parent->color = RED;
            right_rotate(parent);
            brother = parent->left;
        }
        // Through the top, whether entering or not, the brothers became black
        // Case 2 brother black, and both of his children are black
        if ((brother->left == nullptr || brother->left->color == BLACK)
            && (brother->right == nullptr || brother->right->color == BLACK)) {
        // If the parent is red at this time, transfer the brother's black to the parent
            if (parent->color == RED) {
                parent->color = BLACK;
                brother->color = RED;
                break;
            } else {// If the parent is black at this time, that is, it is all black at this time, paint the brother red, resulting in one less black in the brother branch and one less black in the whole branch. It is necessary to adjust the parent again
                brother->color = RED;
                replace = parent;
                parent = replace->parent;
                }
            } else {
                // case3 brother black, the left child of the brother is red, and the right child is free
                if (brother->right != nullptr && brother->right->color == RED) {
                    brother->right->color = parent->color;
                    parent->color = BLACK;
                    left_rotate(brother);
                    right_rotate(parent);
                // case4 brother black, the right child of the brother is red, and the left child is optional
                } else if (brother->left != nullptr && brother->left->color == RED) {
                    brother->color = parent->color;
                    parent->color = BLACK;
                    brother->left->color = BLACK;
                    right_rotate(parent);
                }
                break;
            }
        }
    }
    //Here we can deal with the case where the deleted node has only one child node. If it is a root, it will also be blackened.
    if (replace != nullptr)
        replace->color = BLACK;
}

/*******************************Test function********************************************/
void test1()
{
    RBT tree;
    tree.insert_node(new TreeNode(7));
    tree.insert_node(new TreeNode(2));
    tree.insert_node(new TreeNode(3));
    tree.insert_node(new TreeNode(1));
    tree.insert_node(new TreeNode(5));
    tree.insert_node(new TreeNode(6));
    tree.insert_node(new TreeNode(4));
    tree.insert_node(new TreeNode(8));
    cout << "Preorder traversal:" << endl;
    tree.preOrder(tree.getRoot());
    cout << "Middle order traversal:" << endl;
    tree.inOrder(tree.getRoot());
}

void test2()
{
    RBT tree;
    tree.insert_node(new TreeNode(7));
    tree.insert_node(new TreeNode(2));
    tree.insert_node(new TreeNode(3));
    tree.insert_node(new TreeNode(1));
    tree.insert_node(new TreeNode(5));
    tree.insert_node(new TreeNode(6));
    tree.insert_node(new TreeNode(4));
    tree.insert_node(new TreeNode(8));

    tree.delete_node(tree.getRoot());
    cout << "Preorder traversal:" << endl;
    tree.preOrder(tree.getRoot());
    cout << "Middle order traversal:" << endl;
    tree.inOrder(tree.getRoot());
}

int main()
{
    test2();
    return 0;
}


test

The red black tree generated according to test1 is as follows:

The red black tree after deleting the root node according to test2 is as follows:


reference resources

Wikipedia - red black tree
CSDN: red black tree
CSDN: red black tree (II)
Red black tree online generator

Tags: data structure

Posted by Calcartman on Sat, 16 Apr 2022 22:17:49 +0930