红黑树(RBTree)

发布于:2025-08-03 ⋅ 阅读:(11) ⋅ 点赞:(0)

印象里面在数据查找当中,常见的数据结构有 红黑树、哈希表、b/b+树、跳表

一、红黑树

1.1、应用场景

个人印象里面,在以下场景中使用较多:

  • C++ STL中的map,set
  • epoll底层数据结构之一
  • Linux的进程调度(CFS)
  • nginx的定时器管理
  • 各种含有key,value的数据查找场景

1.2、定义

红黑树是一种近似平衡的二叉查找树,它能够确保任何一个节点的左右子树的高度差不会超过2。

1.3、特性

  • 每个节点都是红的或者黑的
  • 根节点是黑的
  • 每个叶子节点是黑的
  • 如果一个节点是红的,则它的两个子节点都是黑的
  • 对每个节点,从该节点到其子孙节点的所有路径上包含相同数量的黑节点------决定红黑树的高度
    在这里插入图片描述

在这里插入图片描述

1.4、从根节点出发,最长路径最大能包含多少节点?

从根节点出发,红黑树的最短路径和最长路径的节点数差异是2倍。这意味着,如果最短路径包含n个节点,那么最长路径将包含最多2n-1个节点。

二、具体实操

2.1、如何定义红黑树?

typedef struct _rbtree_node{
    int key;
    void* value;

    struct _rbtree_node* left;
    struct _rbtree_node* right;

    struct _rbtree_node* parent;

    unsigned char color; // 0: red, 1: black
}rbtree_node;

// 叶子节点都是黑色的,可以定义为空节点,为了好判断,不能定义为NULL,否则是非法操作
typedef struct _rbtree{
    struct _rbtree_node* root;
    struct _rbtree_node* nil; // 叶子节点,黑色的空节点
    int size; // 树中节点的数量
}rbtree;

2.2、旋转:

  • 红黑树性质被破坏的时候会发生旋转
    在这里插入图片描述
void leftRotate(rbtree* tree, rbtree_node* x)
{
    rbtree_node* y = x->right; // 右子节点
    
    // 断x与y的父子关系
    x->right = y->left; // 让左子节点成为右子节点的父节点
    if(y->left != tree->nil) // 如果左子节点不是叶子节点,则更新父节点的指向
    {
        y->left->parent = x;
    }

    // 断x与x父节点的父子关系,并建立x父节点与y的父子关系
    y->parent = x->parent; // 让y的父节点指向x的父节点
    if(x->parent == tree->nil) // 如果x是根节点,则更新根节点的指向
    {
        tree->root = y;
    }else if(x == x->parent->left) // 如果x是其父节点的左子节点,则更新其父节点的左子节点
    {
        x->parent->left = y;
    }else // 如果x是其父节点的右子节点,则更新其父节点的右子节点
    {
        x->parent->right = y;
    }

    // 更新x与y的父子关系
    y->left = x; // 让x成为y的左子节点
    x->parent = y; // 更新父节点的指向
}

// 将左旋反过来,x变y,left变right,right变left
void rightRotate(rbtree* tree, rbtree_node* y)
{
    rbtree_node* x = y->left; // 左子节点
    
    // 断y与x的父子关系
    y->left = x->right; // 让右子节点成为左子节点的父节点
    if(x->right != tree->nil) // 如果右子节点不是叶子节点,则更新父节点的指向
    {
        x->right->parent = y;
    }

    // 断y与y父节点的父子关系,并建立y父节点与x的父子关系
    x->parent = y->parent; // 让x的父节点指向y的父节点
    if(y->parent == tree->nil) // 如果y是根节点,则更新根节点的指向
    {
        tree->root = x;
    }else if(y == y->parent->right) // 如果y是其父节点的右子节点,则更新其父节点的右子节点
    {
        y->parent->right = x;
    }else // 如果y是其父节点的左子节点,则更新其父节点的左子节点
    {
        y->parent->left = x;
    }

    // 更新y与x的父子关系
    x->right = y; // 让y成为x的右子节点
    y->parent = x; // 更新父节点的指向
}

2.3、插入:

  • 插入节点默认为红色
void rbtree_insert(rbtree* tree, rbtree_node* node)
{
    rbtree_node* y = tree->nil;  // 记录x的父节点
    rbtree_node* x = tree->root; // 根节点

    // 找到插入的位置
    while(x != tree->nil) // 如果不是叶子节点,则继续查找
    {   
        y = x; // 记录父节点
        if(node->key < x->key) // 如果插入节点的键小于当前节点的键,则向左子节点查找
        {
            x = x->left;
        }else if(node->key > x->key) // 如果插入节点的键大于等于当前节点的键,则向右子节点查找
        {
            x = x->right;
        }else{      // 如果插入节点的键等于当前节点的键,则替换当前节点
            x->value = node->value;
        }
    }

    // 如果是空红黑树,则直接插入
    if(y == tree->nil) // 如果y是叶子节点,则直接插入
    {
        tree->root = node; // 更新根节点
    }else{
        if(y->key > node->key) // 如果插入节点的键小于父节点的键,则将插入节点设置为左子节点
        {
            y->left = node;
        }else // 如果插入节点的键大于父节点的键,则将插入节点设置为右子节点
        {
            y->right = node;
        }
    }

    node->parent = y; // 更新父节点

    node->left = tree->nil;
    node->right = tree->nil;
    node->color = 0 ; // 新插入的节点默认为红色,不改变黑高

    // 插入之后,进行旋转和变色
    insertFixup(tree, node);
}

以N-新节点,P-父节点,G-祖父节点,U-叔父节点的命名方式来描述插入之后的调整过程。

情况 条件 调整
1 N是根节点 将N的颜色改为黑色,结束调整。
2 U是红色,P是G的左子节点 将P和U的颜色改为黑色,G的颜色改为红色,然后以G作为新的N继续向上调整
3 U是红色,P是G的右子节点 将P和U的颜色改为黑色,G的颜色改为红色,然后以G作为新的N继续向上调整
4 U是黑色,P是G的左子节点,N是P的左子节点(LL) 将P的颜色改为黑色,G的颜色改为红色,然后对G进行右旋。
5 U是黑色,P是G的左子节点,N是P的右子节点(LR) 对P进行左旋,N的颜色改为黑色,G的颜色改为红色,对G进行右旋。
6 U是黑色,P是G的右子节点,N是P的左子节点(RL) 对P进行右旋,N的颜色改为黑色,G的颜色改为红色,对G进行左旋。
7 U是黑色,P是G的右子节点,N是P的右子节点(RR) 将P的颜色改为黑色,G的颜色改为红色,然后对G进行左旋。

默认叶子节点不出现
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

// 插入之后的调整
void insertFixup(rbtree* tree, rbtree_node* node)
{
    rbtree_node* parent = node->parent;
    rbtree_node* grandparent = NULL;
    rbtree_node* uncle = NULL;
    // 如果节点本身是红色
    while(parent->color == 0) // 如果父节点是红色,那么祖父节点必是黑色,叔父节点可能是红色或黑色
    {
        grandparent = parent->parent; // 获取祖父节点
        if(parent == grandparent->left) // 如果父节点是其祖父节点的左子节点
        {
            uncle = grandparent->right; // 叔父节点
            // 情况2和3:叔父节点是红色
            if (uncle->color == 0)
            {
                parent->color = 1; // 将父节点变为黑色
                uncle->color = 1; // 将叔父节点变为黑色
                grandparent->color = 0; // 将祖父节点变为红色
                node = grandparent; // 继续向上调整
            }else // 叔父节点是黑色
            {
                // 情况4:N是P的左子节点(LL)
                if (node == parent->left)
                {
                    rightRotate(tree, grandparent); // 对祖父节点进行右旋
                    parent = node->parent; // 更新父节点
                }
                // 情况5:N是P的右子节点(LR)
                else
                {
                    leftRotate(tree, parent); // 对父节点进行左旋
                    parent = node->parent; // 更新父节点
                    rightRotate(tree, grandparent); // 对祖父节点进行右旋
                }

                parent->color = 1; // 将父节点变为黑色
                grandparent->color = 0; // 将祖父节点变为红色
                node = parent; // 继续向上调整
            }
        }else // 父节点是其祖父节点的右子节点
        {
            uncle = grandparent->left; // 获取叔父节点

            // 情况2和3:叔父节点是红色
            if (uncle->color == 0)
            {
                parent->color = 1; // 将父节点变为黑色
                uncle->color = 1; // 将叔父节点变为黑色
                grandparent->color = 0; // 将祖父节点变为红色
                node = grandparent; // 继续向上调整
            }
            else // 叔父节点是黑色
            {
                // 情况6:N是P的左子节点(RL)
                if (node == parent->left)
                {
                    rightRotate(tree, parent); // 对父节点进行右旋
                    parent = node->parent; // 更新父节点
                    leftRotate(tree, grandparent); // 对祖父节点进行左旋
                }
                // 情况7:N是P的右子节点(RR)
                else
                {
                    leftRotate(tree, grandparent); // 对祖父节点进行左旋
                    parent = node->parent; // 更新父节点
                }

                parent->color = 1; // 将父节点变为黑色
                grandparent->color = 0; // 将祖父节点变为红色
                node = parent; // 继续向上调整
            }
        }
    }
    tree->root->color = 1; // 将根节点变为黑色
}

在这里插入图片描述

三、问题

3.1、map为什么不用avl树?

AVL树是一种严格平衡的二叉树,要求每个节点的左右子树的高度最多相差1,在大量地进行插入和删除操作时,AVL树需要频繁地进行旋转来维持平衡状态,这会导致较高的时间复杂度。

而RB树(红黑树)是非严格的平衡二叉树,只要从根节点到叶子节点的最长路径不超过最短路径的2倍,那么就不用进行频繁地进行平衡调节,使得插入、删除、查找效率稳定在O(logn)级别。

Code
0vice·GitHub


网站公告

今日签到

点亮在社区的每一天
去签到