Detailed explanation of red-black tree (C/C++ implementation)

The red-black tree has a wide range of uses, such as map\epoll\timer\Nginx\CFS\memory management, the red-black tree is used to manage nodes

The red-black tree is a nearly balanced binary search tree. It does not have the concept of balance factor of the AVL tree. It only maintains a nearly balanced structure by satisfying five properties, ensuring that the longest path does not exceed twice the shortest path.

Applicable to scenarios that require sorting , the red-black tree does not need to form a linked list like a binary search tree in extreme cases, resulting in O(n) time complexity, and does not need to maintain the balance of the tree like a balanced search tree. performance consumption

insert image description here

nature

  • Nodes are red or black

  • root is black

  • The leaf nodes are all black (leaf nodes refer to the bottommost empty nodes, null nodes)

  • The child nodes of the red node are all black

    • The parent node of the red node is black

    • There will not be two consecutive red nodes on the path from the root node to the leaf node

  • All paths from any node to each of its leaf nodes contain the same number of black nodes

left-handed right-handed

insert image description here

Left hand:

  • Connect the original left subtree of y to the right subtree of x

  • Point the parent node of y to the parent node of x, and the parent node of x points to x instead of pointing to y

  • Connect x to the left subtree node of y

Right rotation:

  • Connect the original right subtree of x to the left subtree of y
  • Point the parent node of x to the parent node of y, and the parent node of y points to y instead of pointing to x
  • Connect y to the right subtree node of x

Code

// 左旋
void leftRotate(RbTree *T, RbTreeNode *x) {
    RbTreeNode *y = x->right;

    // 1.将y原来的左子树接到x的右子树下
    x->right = y->left;
    if (y->left != T->nil) {
        y->left->parent = x;
    }

    // 2.将y的父节点指向x的父节点,x的父节点原来指向x改为指向y
    y->parent = x->parent;
    if (x->parent == T->nil) {
        // 此时Y是根节点
        T->root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }

    // 3.将x接到y的左子树下
    y->left = x;
    x->parent = y;
}

// 右旋
void rightRotate (RbTree *T, RbTreeNode *y) {
    RbTreeNode* x = y->left;

    // 1.将x原来的右子树接到y的左子树下
    y->left = x->right;
    if (x->right != T->nil) {
        x->right->parent = y;
    }

    // 2.将x的父节点指向y的父节点,y的父节点原来指向y改为指向x
    x->parent = y->parent;
    if (y->parent == T->nil) {
        T->root = x;
    } else if (y->parent->left == y) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }

    // 3.将y接到x的右子树下
    x->right = y;
    y->parent = x;
}

In the left-handed right-handed code, only left and right are exchanged, x and y are exchanged, and everything else is the same

increase node

All are added to the leaf nodes, and finally adjusted

The added nodes are red by default and will not affect the number of black nodes on the path

Reason: To avoid the need to rotate every time you join

void rbTreeInsert(RbTree *T, RbTreeNode *newNode) {
    RbTreeNode* node = T->root;
    RbTreeNode* parent = T->nil;

    // 寻找可以插入的位置 找到叶子节点
    while (node != T->nil) {
        parent = node;
        if (newNode->key < node->key) {
            node = node->left
        } else if (newNode->key > node->key) {
            node = node->right;
        } else {
            return; // 具体相同的值要不要改动看需求
        }
    }

    newNode->parent = parent;
    if (parent == T->nil) {
        T->root = newNode;
    } else if (parent->key > newNode->key) {
        parent->left = newNode;
    } else {
        parent->right = newNode;
    }
    newNode->left = T->nil;
    newNode->right = T->nil;
    newNode->color = RED;

    rbTreeInsertFixup(T,newNode);
}

tree tweaks

When the parent node is a red node, it needs to be adjusted, because it violates rule 4

There are three situations in the adjustment process:

  • Uncle nodes are red
  • The uncle node is black, and the parent node of the current node is the left subtree
  • The uncle node is black, and the parent node of the current node is the right subtree

If the uncle node is red, there is no need to distinguish between left and right, there is no difference. At that time, the grandfather node will be changed to red, and the parent node and uncle node will be changed to black to continue to adjust upwards

But if it is black, it needs to be distinguished, because the color of the parent node and the grandparent node will cause the height of the black path to change

Current known situation:

  • The current node is red
  • parent node is red
  • Grandfather nodes are black

Uncle nodes are red

insert image description here

Change both the father and uncle to black. At this time, the length of the black path changes, and the grandfather needs to be changed to red.

Continue upwards from the grandfather

father is grandfather's left child

Self is the left child of the father:

insert image description here

Change the father to black. At this time, the black path on the left changes, and the grandfather needs to be changed to red

At this time, the black path of the right subtree is reduced, and the grandfather needs to be rotated to the right, and the parent node becomes the new grandfather to restore the black height of the right subtree

Self is the right child of the father:

insert image description here

Changing the father to the left to change the father to be his own left child, it becomes the above situation

father is grandfather's right child

Self is the right child of the father:

insert image description here

Turn the father into black, at this time the black path on the right becomes longer, and turn the grandfather into red

At this time, the black path on the left becomes shorter, and the height of the left subtree is recovered by performing left rotation.

Self is the left child of the father:

insert image description here

Performing a right rotation on the father makes the father his own right child and becomes the above situation

// 调整树
void rbTreeInsertFixup(RbTree *T, RbTreeNode *node) {
    // 如果父节点是黑色的则不需要调整
    while (node->parent->color == RED) {
        // 父节点是祖父节点的左子树
        if (node->parent == node->parent->parent->left) {
            // 叔父节点
            RbTreeNode *uncle = node->parent->parent->right;
            if (uncle->color == RED) {
                // 将父节点改为黑色和叔父节点改为黑色,祖父节点改为红色继续向上调整就可以了
                // 会影响到黑色路径的长度
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                // 继续向上调整
                node = node->parent->parent;
            } else {
                if (node == node->parent->right) {
                    // 左旋
                    node = node->parent;
                    leftRotate(T,node);
                }
                // 让父节点变为黑色,祖父节点变为红色(右子树的黑色高度变低了)
                // 对祖父右旋,让父节点成为新祖父,恢复右子树的高度
                // 这种情况只需要两次旋转就可以完成调整了
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                rightRotate(T,node->parent->parent);
            }
        } else {
            RbTreeNode *uncle = node->parent->parent->left;
            if (uncle->color == RED) {
                node->parent->color = BLACK;
                uncle->color = BLACK;
                node->parent->parent->color = RED;
                node = node->parent->parent;
            } else {
                if (node == node->parent->left) {
                    node = node->parent;
                    rightRotate(T,node);
                }
                // 左子树的高度减小了
                node->parent->color = BLACK;
                node->parent->parent->color = RED;
                leftRotate(T,node->parent->parent);
            }
        }
    }

    T->root->color = BLACK;
}

delete node

Define three nodes:

  • Overlay node Z: The node designated to be deleted is actually realized by being overwritten
  • Delete node Y: the node that is actually deleted, usually the successor node
  • Axis node X: the node that maintains the red-black tree

There are three possible situations for deleted nodes:

  • No left and right subtrees: delete directly
  • There is only one subtree: hang the only subtree of the node on the parent node, and then delete the node
  • There are both left and right subtrees: Find a successor node Y to cover the specified node Z, and then delete node Y, Y is one of the cases 1 or 2

If the current node is the left subtree of the parent node, four situations can be summarized. Similarly, if the current node is the right subtree of the parent node, we can also summarize four situations. But the difference between these four cases is the difference in the direction of rotation (mirror). There are a total of 8 situations, but they are actually 4 kinds when summed up.

The current node is the left subtree of the parent node

Case 1: The sibling node of the current node is red

Change the brother to black and the father to red. At this time, the height of the left side is reduced, and the height of the left subtree is restored by turning left to the parent node. At this time, the left child of the brother becomes a new brother, which becomes the situation 2, 3, and 4

(Because the brother of x is to be black, so it can only be obtained from the left and right nephews)

insert image description here

Situation 2: The brother is black, and the left and right nephews are also black

At this time, the brother and itself are black, and the parent node is red or black

Change the brother to red, and the axis node to become the parent node (the path where the original axis node is located lacks a black node, so turn the brother to red, and the black path of the parent node is the same at this time, but the path between the parent node The path above is missing a black node, so change the axis node to the parent node and continue to adjust)

insert image description here

Case 3: brother is black, right nephew is black, left nephew is red

Turn the left nephew into black, and the brother into red. At this time, the black height of the right subtree decreases, and the brother node continues to turn right to restore the height. At this time, the left nephew becomes the new right brother

At this time, the right son of the brother is red, which meets the situation 4. Continue to adjust according to the method of situation 4

insert image description here

Situation 4: brother is black, right nephew is red, left nephew is red or black

Change the brother color to be the same as the parent node, the right nephew and the parent node are both black

In order to ensure that the height of the parent node remains unchanged after it becomes black, the parent node needs to be rotated to the left

x points to the root node, the loop ends

Why change the right nephew to black: Because the right nephew becomes a new brother after the left rotation, replacing the original brother, so it needs to be the same color as the brother, that is, black

Why change the brother to be the same as the parent node: because the brother replaces the parent node after left rotation

Why change the parent node to black: because the parent node is left-handed to make up for the deleted black node

At this point the path is restored

insert image description here

The current node is the right subtree of the parent node

As in the case above, mirrored

Summarize

There are two situations that cause the loop to exit without adjustment:

In the second case, the axis node becomes the parent node. If the parent node is red, then you can exit and change the parent node to black to restore the height.

Situation 4: Use the parent node to make up for the height of the deleted node, and finally set the axis node as the root node to exit the loop

Each situation is as close as possible to the fourth situation, realizing the situation that the brother is black and the right nephew is red

the code

// 寻找x节点的最左节点
RbTreeNode* rbTree_mini(RbTree *T, RbTreeNode *x) {
    while (x->left != T->nil) {
        x = x->left;
    }
    return x;
}

// 寻找覆盖节点的右子树的最左节点
RbTreeNode* rbTree_successor(RbTree *T, RbTreeNode* x) {
    RbTreeNode *y = x->parent;

    // 寻找x节点的右子树的最左节点
    if (x->right != T->nil) {
        return rbTree_mini(T,x->right);
    }
}

void rbTree_delete_fixup(RbTree *T, RbTreeNode* x) {
    while ((x != T->root) && (x->color == BLACK)) {
        if (x == x->parent->left) {
            // w为兄弟节点
            RbTreeNode *w = x->parent->right;
            if (w->color == RED) {
                w->color = BLACK;
                x->parent->color = RED;
                leftRotate(T,x->parent);
                w = x->parent->right;
            }
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                w->color = RED;
                x = x->parent;
            } else {
                if (w->right->color == BLACK) {
                    w->left->color = BLACK;
                    w->color = RED;
                    rightRotate(T,w);
                    w = x->parent->right;
                }
                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->right->color = BLACK;
                leftRotate(T,x->parent);
                x = T->root;
            }
        } else {
            RbTreeNode *w = x->parent->left;
            if (w->color == RED) {
                w->color = BLACK;
                x->parent->color = RED;
                rightRotate(T,x->parent);
                w = x->parent->left;
            }
            if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
                w->color = RED;
                x = x->parent;
            } else {
                if (w->left->color == BLACK) {
                    w->right->color = BLACK;
                    w->color = RED;
                    leftRotate(T,w);
                    w = x->parent->left;
                }

                w->color = x->parent->color;
                x->parent->color = BLACK;
                w->left->color = BLACK;
                rightRotate(T,x->parent);

                x = T->root;
            }
        }
    }
    x->color = BLACK;
}

// 删除节点
RbTreeNode* rbTreeDelete(RbTree *T, RbTreeNode *z) {
    // z是被覆盖的节点 y是删除节点 x是轴心节点负责旋转
    RbTreeNode *y = T->nil;
    RbTreeNode *x = T->nil;

    // 此时如果没有子树或只有一颗子树 那么就是要被删除的节点
    if ((z->left == T->nil) || (z->right == T->nil)) {
        y = z;
    } else {
        // 寻找删除节点 也就是覆盖节点的右子树的最左节点
        y = rbTree_successor(T,z);
    }

    // 找轴心节点 一般是被删除节点的左子节点
    if (y->left != T->nil) {
        x = y->left;
    } else if (y->right != T->nil) {
        x = y->right;
    }

    x->parent = y->parent;
    if (y->parent == T->nil) {
        // 如果y是根节点 那么x就成为新的根节点
        T->root = x;
    } else if (y == y->parent->left) {
        y->parent->left = x;
    } else {
        y->parent->right = x;
    }

    // 覆盖节点
    if (y != z) {
        z->key = y->key;
        z->value = y->value;
    }

    // 如果删除节点是黑色节点则需要调整
    if (y->color == BLACK) {
        rbTree_delete_fixup(T,x);
    }

    return y;
}

time complexity

The time complexity of red-black tree:

  • Query element O(logN)
  • Inserting elements: at most two rotations are required, at least one rotation can be completed, and sometimes no adjustment is required
  • Delete elements: at most three rotations are required, at least one rotation can be completed, and sometimes no adjustment is required

Time complexity of AVL tree:

  • Query element O(logn)
  • Inserting elements: up to two rotations to preserve balance
  • Delete elements: AVL tree needs to maintain the balance of all nodes on the path from the deleted node to the root node, which requires a lot of rotation

As far as the insertion of nodes causes the tree to be unbalanced, both AVL and RB-Tree have at most two tree rotations to achieve rebalance, and the magnitude of the rotation is O(1)

Deleting a node causes an imbalance. AVL needs to maintain the balance of all nodes on the path from the deleted node to the root node root. The magnitude of rotation is O(logN), while RB-Tree only needs to rotate up to 3 times to achieve rebalance. O(1) is required, so the rebalance of RB-Tree deleting nodes is more efficient and less expensive!

However, the red-black tree avoids rotation by changing the color of the node. The specific cost depends on whether the cost of changing the color is small or the cost of changing the pointer is small.

Guess you like

Origin blog.csdn.net/blll0/article/details/129478623