【C++ 学习 ㉓】- 详解红黑树

目录

一、红黑树的概念和性质

二、红黑树的两个结论

三、红黑树节点的定义

四、红黑树的插入

五、红黑树的实现

5.1 - RBT.h

5.2 - test.cpp

六、红黑树和 AVL 树的比较


 


一、红黑树的概念和性质

红黑树(red-black tree)是这样的一棵二叉搜索树:树中的每个节点的颜色不是红色就是黑色。可以把一棵红黑树视为一棵扩充二叉树,用外部节点表示空指针。其特性描述如下

  1. 根节点和所有外部节点的颜色是黑色

  2. 从根节点到外部节点的途中没有连续两个节点的颜色是红色

    特性 2 意味着如果一个节点的颜色是红色,则它的左、右孩子的颜色必然是黑色

  3. 对任一节点,所有从该节点到外部节点的路径上都有相同数目的黑色节点


二、红黑树的两个结论

从红黑树中任一节点 x 出发(不包括节点 x),到达任一个外部节点的路径上的黑节点个数叫做节点 x 的黑高度,亦称为节点的阶,记作 bh(x)。红黑树的黑高度定义为其根节点的黑高度。

下图所示的二叉搜索树就是一棵红黑树,节点上边的数字为该节点的黑高度。

  1. 结论一:对任一节点 x,假设它的黑高度为 r,那么从节点 x 到任一外部节点的路径长度为 r ~ 2r

    注意:从节点 x 到任一外部节点的路径长度为该路径上的指针的个数

  2. 结论二:假设 h 是一棵红黑树的高度(不包括外部节点),n 是红黑树中内部节点的个数, r 是红黑树的黑高度,则以下关系式成立

    (1) ​h \le 2r

    (2) n \ge 2^r - 1

    (3) h \le 2log_2^{(n+1)}

    证明

    (1) 由结论一即可证明。上图所示的红黑树的高度(不计外部节点)为 2r = 4

    (2) 因为红黑树的黑高度为 r,即从红黑树的第 1 层到第 r 层没有外部节点,所以内部节点的总数至少为 2^r - 1。在上图所示的红黑树中,红黑树的黑高度为 r = 2,第 1 层和第 2 层总共有 2^r - 1 = 3 个内部节点,而在第 3 层和第 4 层还有 7 个节点,则有 ​n \ge 2^r - 1

    (3) 由 (2) 可得 r \le log_2^{(n+1)},结合 (1),有 ​h \le 2log_2^{(n+1)}

    由此也可以知道红黑树的搜索、插入、删除操作的时间复杂度为 O(log_2^n​)


三、红黑树节点的定义

enum Color
{
    RED,
    BLACK
};
​
template<class K, class V>
struct RBTNode
{
    RBTNode<K, V>* _left;
    RBTNode<K, V>* _right;
    RBTNode<K, V>* _parent;
    std::pair<K, V> _kv;
    Color _clr;
​
    RBTNode(const std::pair<K, V>& kv = std::pair<K, V>(), Color clr = RED)
        : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
    { }
};


四、红黑树的插入

如果插入前树非空,若新节点被染成黑色,将违反红黑树的特性 3,即所有从根节点到外部节点的路径上的黑色节点个数变得不相等了,因此如果插入前树非空,新插入的节点应该被然成红色

如果新插入的节点的父节点是黑色节点,则没有违反红黑树的任何特性,不需要做调整,即插入结束;如果新插入的节点的父节点是红色节点,则违反了红黑树的特性 2,即出现了连续两个红色节点,需要做平衡调整

*cur 为当前节点,它和它的父结点 *parent 都是红色节点,它的祖父节点 *grandparent 是黑色节点,即出现了连续两个红色节点的情形,此时通过考查其叔叔节点 *uncle 做相应的平衡调整

  1. 如果 *uncle 是红色节点,则将 *parent*uncle 改为黑色节点,将 *grandparent 改为红色节点。例如

    如果 *grandparent 是根节点,则将其再改为黑色节点,调整完成

    如果 *grandparent 是子树的根,则其父节点也可能是红色节点,所以还需要继续往上调整

  2. 如果 *uncle 不存在或者是黑色节点,此时又有以下 4 种情况

    (1) *parent*grandparent 的左孩子,*cur*parent 的左孩子。在这种情况下,只要做一次右单旋,再将 *parent 改为黑色节点,*grandparent 改为红色节点,就可恢复红黑树的特性,并结束平衡调整

    (2) *parent*grandparent 的左孩子,*cur*parent 的右孩子。在这种情况下,只要做一次先左后右的双旋转,再将 *cur 改为黑色节点,*grandparent 改为红色节点,就可恢复红黑树的特性,并结束平衡调整

    (3) *parent*grandparent 的右孩子,*cur*parent 的右孩子

    (4) *parent*grandparent 的右孩子,*cur*parent 的左孩子

    情况 (2)、(3) 和情况 (1)、(2) 是镜像的

 


五、红黑树的实现

5.1 - RBT.h

#pragma once
​
#include <utility>
​
enum Color
{
    RED,
    BLACK
};
​
template<class K, class V>
struct RBTNode
{
    RBTNode<K, V>* _left;
    RBTNode<K, V>* _right;
    RBTNode<K, V>* _parent;
    std::pair<K, V> _kv;
    Color _clr;
​
    RBTNode(const std::pair<K, V>& kv = std::pair<K, V>(), Color clr = RED)
        : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
    { }
};
​
template<class K, class V>
class RBT
{
    typedef RBTNode<K, V> Node;
public:
    RBT() : _root(nullptr) { }
​
    ~RBT()
    {
        Destroy(_root);
    }
​
    bool isValidRBT()
    {
        if (_root == nullptr)
            return true;
​
        if (_root->_clr == RED)
            return false;
​
        // 获取从根节点到任一外部节点的路径上的黑色节点的个数
        size_t blackCount = 0;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_clr == BLACK)
                ++blackCount;
            cur = cur->_left;
        }
​
        size_t k = 0;  // k 用来路径种的黑色节点个数
        return _isValidRBT(_root, blackCount, k);
    }
​
    bool insert(const std::pair<K, V>& kv)
    {
        if (_root == nullptr)
        {
            _root = new Node(kv, BLACK);
            return true;
        }
​
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            parent = cur;
            if (kv.first < cur->_kv.first)
                cur = cur->_left;
            else if (kv.first > cur->_kv.first)
                cur = cur->_right;
            else
                return false;
        }
​
        // 如果插入前树非空,新插入的节点应该是红色节点
        cur = new Node(kv, RED); 
        cur->_parent = parent;
​
        // 出现连续两个红色节点的情形
        while (parent && parent->_clr == RED)
        {
            Node* grandparent = parent->_parent;
            Node* uncle;
            if (grandparent->_left == parent)
                uncle = grandparent->_right;
            else
                uncle = grandparent->_left;
​
            // 如果 *uncle 是红色节点
            if (uncle && uncle->_clr == RED)
            {
                parent->_clr = uncle->_clr = BLACK;
                grandparent->_clr = RED;
​
                cur = grandparent;
                parent = cur->_parent;
            }
            else  // 如果 *uncle 不存在或者是黑色节点
            {
                if (grandparent->_left == parent && parent->_left == cur)
                {
                    LL(grandparent);
                    parent->_clr = BLACK;
                    grandparent->_clr = RED;
                }
                else if (grandparent->_right == parent && parent->_right == cur)
                {
                    RR(grandparent);
                    parent->_clr = BLACK;
                    grandparent->_clr = RED;
                }
                else if (grandparent->_left == parent && parent->_right == cur)
                {
                    LR(grandparent);
                    cur->_clr = BLACK;
                    grandparent->_clr = RED;
                }
                else if (grandparent->_right == parent && parent->_left == cur)
                {
                    RL(grandparent);
                    cur->_clr = BLACK;
                    grandparent->_clr = RED;
                }
                break;
            }
        }
        _root->_clr = BLACK;
        return true;
    }
private:
    void Destroy(Node*& root)
    {
        // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
        if (root == nullptr)
            return;
​
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
        root = nullptr;
    }
    
    bool _isValidRBT(Node* root, size_t blackCount, size_t k)
    {
        if (root == nullptr)
        {
            if (blackCount == k)
                return true;
            else
                return false;  // 违反了特性 3
        }
​
        if (root->_clr == BLACK)
        {
            ++k;
        }
        else
        {
            if (root->_parent && root->_parent->_clr == RED)
                return false;  // 违反了特性 2
        }
​
        return _isValidRBT(root->_left, blackCount, k)
            && _isValidRBT(root->_right, blackCount, k);
    }
​
    void LL(Node* parent)
    {
        Node* cur = parent->_left;
        Node* curRight = cur->_right;
​
        parent->_left = curRight;
        if (curRight)
            curRight->_parent = parent;
​
        cur->_right = parent;
        Node* tmp = parent->_parent;
        parent->_parent = cur;
        cur->_parent = tmp;
​
        if (tmp == nullptr)
        {
            _root = cur;
        }
        else
        {
            if (tmp->_left == parent)
                tmp->_left = cur;
            else
                tmp->_right = cur;
        }
    }
​
    void RR(Node* parent)
    {
        Node* cur = parent->_right;
        Node* curLeft = cur->_left;
​
        parent->_right = curLeft;
        if (curLeft)
            curLeft->_parent = parent;
​
        cur->_left = parent;
        Node* tmp = parent->_parent;
        parent->_parent = cur;
        cur->_parent = tmp;
​
        if (tmp == nullptr)
        {
            _root = cur;
        }
        else
        {
            if (tmp->_left == parent)
                tmp->_left = cur;
            else
                tmp->_right = cur;
        }
    }
​
    void LR(Node* parent)
    {
        Node* cur = parent->_left;
        RR(cur);
        LL(parent);
    }
​
    void RL(Node* parent)
    {
        Node* cur = parent->_right;
        LL(cur);
        RR(parent);
    }
private:
    Node* _root;
};

5.2 - test.cpp

#include "RBT.h"
#include <iostream>
#include <vector>
#include <ctime>
using namespace std;
​
void TestRBT()
{
    srand((unsigned int)time(0));
    size_t N = 10000;
    vector<int> v;
    for (size_t i = 0; i < N; ++i)
    {
        v.push_back(rand());
    }
​
    RBT<int, int> t;
    for (const auto& e : v)
    {
        t.insert(make_pair(e, e));
        if (!t.isValidRBT())
            cout << "插入操作有误!" << endl;
    }
    cout << "插入完成~" << endl;
}
​
int main()
{
    TestRBT();
    return 0;
}


六、红黑树和 AVL 树的比较

红黑树和 AVL 树的增删查改的效率都很高,时间复杂度为 O(​),但红黑树不追求极致的平衡,其只需保证最长路径不超过最短路径的 2 倍,相对而言,降低了平衡旋转的次数,所以在经常进行增删的结构中,红黑树的性能比 AVL 树更好,并且红黑树的实现更简单,所以在实际应用中,使用红黑树的也更多

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值