C++精通之路:红黑树(超详细)

@[toc]

很多小伙伴为了刷题发愁
今天为大家推荐一款刷题神奇哦:刷题面试神器牛客
各大互联网大厂面试真题。从基础到入阶乃至原理刨析类面试题 应有尽有,赶快来装备自己吧!助你面试稳操胜券,solo全场面试官

红黑树

一:红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过
对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩
倍,因而是接***衡的

二:红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

通过以上的规则,就能保证:
其最长路径中节点个数不会超过最短路径节点个数的两倍
从而达到相对平衡

三:红黑树节点的定义

// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
};
  • 注意:

需要将节点的默认颜色给成红色的,因为红色不影响红黑树的整体结构。
在后续插入的情况,也是一样

四:红黑树结构

  • 在stl源码中的结构:
    在这里插入图片描述

五:红黑树的插入操作

  1. 先用简单的比较,找到插入节点需要插入的位置
  2. 因为默认是为红色,所以要向上判断是否违反规则,情况如下:
  1. 父亲是黑色,则不用做任何操作即可满足红黑树的结构
  2. 父亲是红色,出现了连续的红节点,不满足红黑树的结构,需要处理,具体处理情况如下:
  • 具体处理情况:
  1. 因为父亲是红色,所以父亲不可能是根,因为不能出现连续的红节点,所以祖父是黑节点,
  2. 祖父和父亲都确定了,只有祖父的另外一个孩子(叔叔)的颜色没有确定
  3. 所以我们以叔叔的颜色为特殊情况再以次分析如何处理
  • 注:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

    情况一(只需要变色):

  • cur为红,p为红,g为黑,u存在且为红
    在这里插入图片描述

    因为有连续的红节点,必须要做变色处理了,如何保证在不破坏红黑树的整体结构下来做变色处理呢?

  • 我们选择把 g变红,p和u变黑来处理。
  • 这样就保证了在p和u这两条路径下的黑色节点没有发生改变并且没有了连续的红节点
  • 但是g的改变也会导致g上层结构的变化,所以我们还要做出改变:
    1. 假如g为根节点的时候,将其变黑
    2. 假如g不为根节点的时候,则继续以g为新增节点向上调整

    情况二(单旋加变色):

  • cur为红,p为红,g为黑,u不存在/u为黑
    在这里插入图片描述
  1. 假如u不存在,则cur一定是新增节点,因为假如cur原来是黑色的话,就违反了规则:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  2. 假如u存在,所以u为黑色(为红色的情况以讨论):假设cur是新增节点,则在cur未插入前,p左子树的这条路径的长度已经逼u上的路径上的黑色节点少了,违反了规则:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点,所以假设不成立,cur一定是从黑色变为红色的
  • 解决方法:

如果p为g的左孩子,cur为p的左孩子,则进行右单旋转,p变黑,g变红
如果p为g的右孩子,cur为p的右孩子,则进行左单旋转,p变黑,g变红

  • 原因/理由:
    如果p为g的左孩子,cur为p的左孩子,则失去了平衡,通过变色已经无法满足要求了,所以我们就要借助旋转来帮助我们。所以如果p为g的左孩子,cur为p的左孩子,则进行右单旋转,p变黑,g变红。即平衡了整个树,又保证了路径中的黑色节点不变

    情况三(双旋加变色):

也是cur为红,p为红,g为黑,u不存在/u为黑,但cur的位置发生了变化,如图所示:
在这里插入图片描述

  • 解决办法:
  1. 如果p为g的左孩子,cur为p的右孩子,则针对p做左单旋转,p旋转后再对g进行右单旋,旋转后将cur变黑,g变红

  2. 如果p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,p旋转后再对g进行左单旋,旋转后将cur变黑,g变红

  • 具体步骤图:
    在这里插入图片描述

    插入的实现

pair<Node*, bool> Insert(const pair<K, V>& kv)
{
    //空树的情况
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;
        return make_pair(_root, true);
    }

    //查找位置插入节点
    Node* cur = _root, * parent = _root;
    while (cur)
    {
        if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            return make_pair(cur, false);
        }
    }

    //创建链接节点
    cur = new Node(kv);
    Node* newnode = cur;
    if (parent->_kv.first > kv.first)
    {
        parent->_left = cur;
    }
    else
    {
        parent->_right = cur;
    }
    cur->_parent = parent;

    //父节点存在且为红,则需要调整(不能存在连续的红色节点)
    while (parent && parent->_col == RED)
    {
        //此时当前节点一定有祖父节点
        Node* granparent = parent->_parent;
        //具体调整情况主要看叔叔节点
        //分左右讨论
        if (parent == granparent->_left)
        {
            Node* uncle = granparent->_right;
            //情况1:叔叔节点存在且为红
            if (uncle && uncle->_col == RED)
            {
                //修改颜色,继续向上检查
                granparent->_col = RED;
                parent->_col = uncle->_col = BLACK;

                cur = granparent;
                parent = cur->_parent;
            }
            else//情况2和3:叔叔节点不存在 或者存在且为黑
            {
                //单旋(三代节点为斜线)+变色
                if (cur == parent->_left)
                {
                    RotateR(granparent);

                    granparent->_col = RED;
                    parent->_col = BLACK;
                }
                else//双旋(三代节点为折线)+变色
                {
                    RotateL(parent);
                    RotateR(granparent);

                    cur->_col = BLACK;
                    granparent->_col = RED;
                }
                //旋转后不需再向上调整了
                break;
            }
        }
        else//parent=grandparent->right
        {
            Node* uncle = granparent->_left;
            if (uncle && uncle->_col == RED)
            {
                parent->_col = uncle->_col = BLACK;
                granparent->_col = RED;

                cur = granparent;
                parent = cur->_parent;
            }
            else
            {
                if (cur == parent->_right)
                {
                    RotateL(granparent);

                    parent->_col = BLACK;
                    granparent->_col = RED;
                }
                else
                {
                    RotateR(parent);
                    RotateL(granparent);

                    cur->_col = BLACK;
                    granparent->_col = RED;
                }
                break;
            }
        }
    }

    //确保根节点为黑
    _root->_col = BLACK;
    return make_pair(newnode, true);
}

旋转实现

void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    Node* parentP = parent->_parent;


    parent->_right = subRL;
    if (subRL)
    {
        subRL->_parent = parent;
    }

    subR->_left = parent;
    parent->_parent = subR;

    if (parent == _root)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        subR->_parent = parentP;
        if (parentP->_left == parent)
        {
            parentP->_left = subR;
        }
        else
        {
            parentP->_right = subR;
        }
    }
}

void RotateR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    Node* parentP = parent->_parent;


    parent->_left = subLR;
    if (subLR)
    {
        subLR->_parent = parent;
    }

    subL->_right = parent;
    parent->_parent = subL;

    if (parent == _root)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
        subL->_parent = parentP;
        if (parentP->_left == parent)
        {
            parentP->_left = subL;
        }
        else
        {
            parentP->_right = subL;
        }
    }
}

六:红黑树的验证

  • 红黑树的检测分为两步:
  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

  2. 检测其是否满足红黑树的性质

实现代码:

bool IsRBTree()
{
    //空树
    if (_root == nullptr)
    {
        return true;
    }
    //根节点为黑色
    if (_root->_col == RED)
    {
        cout << "根节点为红色" << endl;
        return false;
    }
    //黑色结点数量各路径上相同
    //先走一条得到基准值
    int Blacknum = 0;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_col == BLACK)
            Blacknum++;
        cur = cur->_left;
    }
    //检查子树
    int i = 0;
    return _IsRBTree(_root, Blacknum, i);
}

bool _IsRBTree(Node* root, int blacknum, int count)
{
    //递归到空节点
    if (root == nullptr)
    {
        if (blacknum == count)
            return true;
        cout << "各路径上黑色节点个数不同" << endl;
        return false;
    }
    //子节点为红则检查父节点是否为红(通过父节点检查子节点会遇到空节点)
    if (root->_col == RED && root->_parent->_col == RED)
    {
        cout << "存在连续红色节点" << endl;
        return false;
    }
    //计数黑结点
    if (root->_col == BLACK)
        count++;
    //递归左右子树
    return _IsRBTree(root->_left, blacknum, count) && _IsRBTree(root->_right, blacknum, count);
}

七、红黑树的删除

红黑树的删除不做讲解,有兴趣可参考:《算法导论》或者《STL源码剖析》
http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
http://blog.csdn.net/chenhuajie123/article/details/11951777

八:红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN ),红黑树不追求绝对平衡,其
只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多.

九:红黑树的应用

  1. C++ STL库 -- map/set、mutil_map/mutil_set
  2. Java 库
  3. linux内核
  4. 其他一些库

下一章我们将会将map/set如何通过红黑树来实现的,敬请期待吧!!

总结

红黑树是一个极其优秀的数据结构,也是面试中比较爱考的。在liunx,c++,java中也有很多的使用。
对于我们这些将来的互联网从业者来说,是一个必须要掌握的数据结构(可以不知道具体的代码实现,但要懂红黑树是如何实现的,以及后来如何封装出map/set的)。

全部评论
成都华为校招,留学生,社招开始了!私信找我内推~随时在线,入职有导师带教~前沿的技术和具有竞争力的薪资!22和23届的同学快来私信,!毕业的同学也可联系~软件和硬件都有职
1 回复 分享
发布于 2022-08-23 15:05 香港

相关推荐

10-17 10:05
已编辑
北华大学 全栈开发
牛客872465272号:掉头发了哥
点赞 评论 收藏
分享
3 22 评论
分享
牛客网
牛客企业服务