高阶数据结构: 红黑树

红黑树的概念

介绍:

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在 计算机科学中用到的一种 数据结构,典型的用途是实现 关联数组
红黑树是在1972年由 Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树是一种特化的AVL树( 平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。 --取自百度

标准的红黑树:


红黑树的性质和结构定义

红黑树的特性:

红黑树,是一种 二叉搜索树,但 在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是 接近平衡的。
性质:
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
补:不存在连续的红节点,但存在连续的黑节点
4. 对于每个结点,从该结点到其所U有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

而我们通过红黑树的性质3和4也可以解释一下(为何红黑树能确保没有一条路径会比其他路径长出俩倍),假设在一颗红黑树中,有一条路径上全是黑节点,黑节点的个数为N,则该路径为最短路径,因此次树中的最长路径的节点个数为2N个(为红黑节点交替存在),因为每个路径的上包含相同的黑色节点,所以这就很好的解释的红黑树能没有一条路径会比其他路径长出俩倍。

红黑树的结构定义:

enum Color
{
    RED,
    BLACK
};

template<class K,class V>
struct RBTreeNode
{
    //三叉链
    RBTreeNode<K,V>* _left;
    RBTreeNode<K,V>* _right;
    RBTreeNode<K,V>* _parent;
    pair<K, V> _kv;  //存储的数据
    Color _col;  //节点的颜色状态
    
    RBTreeNode(const pair<K,V>& kv)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_kv(kv)
        ,_col(RED)
    {}
};

红黑树的插入

红黑树的插入实现与之前的AVL类似,只是平衡调节变成了颜色调节。前面寻找插入的逻辑与之前的差不多。

大体步骤:

  1. 寻找合适插入位置

  1. 插入节点

  1. 调节颜色平衡

/第一次插入
        if (_root==nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        //先寻找插入的位置
        Node* cur = _root;
        Node* parent = nullptr;
        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 false;
            }
        }

但是当我们新插入一个节点时,与AVL树不同的是,AVL树只需要初始化平衡因子即可,红黑树即需要初始化颜色,那么我们将新插入的节点初始化成什么颜色最好呢?

考虑到上面的性质4,如果我们插入的节点每次都初始化成黑色,就意味着我们每一次插入都需要调节颜色平衡,这样就会麻烦,所以最好的方法是每次插入的节点都初始化成红色的。

代码实现:

cur = new Node(kv);
        cur->_col = RED; //节点颜色初始都为红色
        //开始插入
        if (parent->_kv.first > kv.first)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }

红黑树的颜色调节

红黑树的颜色调节基础之一 --旋转
左单旋
如图中:将parent于subR断开,以及subR和subRL也断开,然后将subRL的于parent相接,再将subR的左孩子变成parent,具体实现步骤见AVL树博客中。
右单旋
如图中:如图中:将parent于subL断开,以及subL和subLR也断开,然后将subLR的于parent相接,再将subL的右孩子变成parent,具体实现步骤也见AVL树博客中。

代码:

void rotateRight(Node* Parent)
    {
        Node* SubL = Parent->_left;
        Node* SubLR = SubL->_right;

        //将SubLR交给Parent
        Parent->_left = SubLR;
        if (SubLR)
        {
            //SubLR不为空才更新的父亲节点
            SubLR->_parent = Parent;
        }

        //将Parent进行旋转
        SubL->_right = Parent;
        Node* Parentparent = Parent->_parent;
        SubL->_parent = Parentparent;
        //更新parent的_parent
        Parent->_parent = SubL;

        //检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
        if (_root == Parent)
        {
            _root = SubL;
            _root->_parent = nullptr;
        }
        else
        {
            if (Parentparent->_left == Parent)
            {
                Parentparent->_left = SubL;
            }
            else if (Parentparent->_right == Parent)
            {
                Parentparent->_right = SubL;
            }
        }
    
    }
    //左单旋
    void rotateLeft(Node* Parent)
    {
        Node* subR = Parent->_right;
        Node* subRL = subR->_left;

        //将subLR交给父亲节点的右节点
        Parent->_right = subRL;
        if (subRL)
            subRL->_parent = Parent;

        //开始左旋
        subR->_left = Parent;
        Node* Parentparent = Parent->_parent;
        //更新subR的父亲节点
        subR->_parent = Parentparent;

        Parent->_parent = subR;

        //连接上面的节点
        if (_root == Parent)
        {
            _root = subR;
            _root->_parent = nullptr;
        }
        else
        {
            if (Parentparent->_left == Parent)
            {
                Parentparent->_left = subR;
            }
            else
            {
                Parentparent->_right = subR;
            }
        }

        
    }

在解释调节平衡之前我们先来认识一下定义:约定新插入节点为cur ,p为父母亲节点,u为uncle(叔叔)节点,g为祖父节点。

而由于我们前面插入的节点都是初始化为红节点(除了根节点的插入除外),我们插入节点的parent节点为黑色时,我们是不需要进行平衡调节的;而当我们的parent节点为红色时,我们插入节点就需要调节颜色了,因为红黑树中是不允许存在连续的红节点的,只要出现连续红色节点时,我们就需要进行颜色平衡调节。


需要调节颜色的平衡的情况一:cur节点和parent节点为红色,uncle节点存在且为红色

如图:

这种情况下我们只需将parent,uncle节点变成黑色,然后再将grandparent节点变成红色即可实现颜色调节。

而在这种情况下还会有另外一种小情况:需要持续调节颜色平衡

例:当前的granparent是其他节点的子节点

如图:

我们进行第一次调节之后,为了防止忽略掉这种情况,我们需要将cur更新为grandparent,继续向上观察是否需要调节颜色平衡。

而为此我们也需要多做一部操作,在平衡调节的最后,将根结点重新置为黑色,为防止grandparent是根节点情况,而将其更新为红色。


需要调节颜色的平衡的情况二:cur节点和parent节点为红色,uncle节点不存在或为黑色

以下这4种情况一般都是第一情况调节之后得来的,我们只以省略图举例。

cur为parent的左子树,parent为祖父母节点的左子树--右单旋+颜色

如:完全图

略图演示

操作为:

  1. 对parent节点进行右单旋

  1. 将parent调成黑色,grandparent调成红色

并且我们对旋转调色结果进行分析,我们会发现对于上层节点,左右路的黑色节点个数分布并没有发生变化,详图兄弟们可以自己动手去画画


cur为parent的右子树,parent为祖父母节点的右子树--左单旋+颜色

完全图:

操作以略图进行操作:

操作为:

  1. 对parent节点进行左单旋

  1. 将parent调成黑色,grandparent调成红色

如:


cur为parent的左子树,parent为祖父母节点的右子树--右左双旋+颜色

完全图:

操作以略图进行操作:

操作为:

  1. 对parent节点进行右单旋

  1. 对cur节点进行左单旋

  1. 将cur调成黑色,grandparent调成红色

如:


cur为parent的右子树,parent为祖父母节点的左子树--左右双旋+颜色

完全图:

操作以略图进行操作:

操作为:

  1. 对parent节点进行左单旋

  1. 对cur节点进行右单旋

  1. 将cur调成黑色,grandparent调成红色

如:

插入的总代码:

bool Insert(const pair<K,V>& kv)
    {
        //第一次插入
        if (_root==nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        //先寻找插入的位置
        Node* cur = _root;
        Node* parent = nullptr;
        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 false;
            }
        }
        cur = new Node(kv);
        cur->_col = RED; //节点颜色初始都为红色
        //开始插入
        if (parent->_kv.first > kv.first)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        //调节平衡

        //父亲节点存在 且父亲节点为红色
        while (parent&&parent->_col==RED)
        {
            Node* grandParent = parent->_parent;
            if (grandParent && parent == grandParent->_left)
            {
                Node* uncle = grandParent->_right;
                //情况一:叔叔节点存在 且为红色
                if (uncle &&uncle->_col == RED)
                {
                    //调整 将父亲节点和叔叔节点都变成黑色的 祖父节点变成红色的
                    parent->_col = uncle->_col = BLACK;
                    grandParent->_col = RED;

                    //更新迭代  只有这种情况需要迭代更新 因为没有发生旋转
                    cur = grandParent;
                    parent = cur->_parent;
                }
                //情况二:叔叔节点存在且为黑 或者叔叔节点不存在
                else if (uncle == nullptr || uncle->_col == BLACK)
                {
                    //1.cur为父亲节点的左子树,Parent也为祖父节点的左子树 进行右单旋
                    if(cur==parent->_left)
                    {
                        rotateRight(grandParent);
                        //将父亲节点变成黑,祖父节点变成红
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //2.cur为父亲节点的右节点
                    else if (cur == parent->_right)
                    {
                        //进行双旋 左右双旋
                        //先左旋parent节点,然后再右旋祖先节点
                        rotateLeft(parent);
                        rotateRight(grandParent);

                        //将cur点变成黑,祖父节点变成红
                        cur->_col = BLACK;
                        grandParent->_col = RED;
                    }

                    //旋转完后基本都是平衡的了
                    break;
                }
            }
            else if(grandParent && parent==grandParent->_right)
            {
                Node* uncle = grandParent->_left;
                //情况一:叔叔节点存在 且为红色
                if (uncle && uncle->_col == RED)
                {
                    //调整 将父亲节点和叔叔节点都变成黑色的 祖父节点变成红色的
                    parent->_col = uncle->_col = BLACK;
                    grandParent->_col = RED;

                    //更新迭代
                    cur = grandParent;
                    parent = cur->_parent;
                }
                //情况二:叔叔节点存在且为黑 或者叔叔节点不存在
                else if (uncle == nullptr || uncle->_col == BLACK)
                {
                    //1.cur为父亲节点的右子树,Parent也为祖父节点的右子树 进行左单旋
                    if (cur == parent->_right)
                    {
                        rotateLeft(grandParent);
                        //将父亲节点变成黑,祖父节点变成红
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //2.cur为父亲节点的左节点
                    else if (cur == parent->_left)
                    {
                        //进行双旋 右左双旋
                        //先右旋parent节点,然后再左旋祖先节点
                        rotateRight(parent);
                        rotateLeft(grandParent);

                        //将cur节点变成黑,祖父节点变成红
                        cur->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //
                    break;
                }
            
            }
        }
        //防止祖父节点是根节点,统一最后处理成黑色
        _root->_col = BLACK;

        return true;
    }

红黑树的检测

红黑树的检测分为两步:

1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

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

代码实现:

bool IsBalance()
    {
        if (_root && _root->_col == RED)
        {
            cout << "根节点不是黑色" << endl;
            return false;
        }

        // 最左路径黑色节点数量做基准值
        int banchmark = 0;
        Node* left = _root;
        while (left)
        {
            if (left->_col == BLACK)
                ++banchmark;

            left = left->_left;
        }

        int blackNum = 0;
        return _IsBalance(_root, banchmark, blackNum);
    }

    bool _IsBalance(Node* root, int banchmark, int blackNum)
    {
        if (root == nullptr)
        {
            if (banchmark != blackNum)
            {
                cout << "存在路径黑色节点的数量不相等" << endl;
                return false;
            }

            return true;
        }

        if (root->_col == RED && root->_parent->_col == RED)
        {
            cout << "出现连续红色节点" << endl;
            return false;
        }

        if (root->_col == BLACK)
        {
            ++blackNum;
        }

        return _IsBalance(root->_left, banchmark, blackNum)
            && _IsBalance(root->_right, banchmark, blackNum);
    }
    void Inorder()
    {
        _Inorder(_root);
    }
    void _Inorder(Node* root)
    {
        if (root == nullptr)
        {
            return;
        }
        //中序遍历
        _Inorder(root->_left);
        cout << root->_kv.first << ' ' << root->_kv.second << endl;
        _Inorder(root->_right);
    }

红黑树性能分析

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log2 N),红黑树不追

求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,

所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红

黑树更多。

更严格意义上来说,AVL树的搜索复杂度大概为logN,而红黑树的搜索复杂度2logN,即使在数据量为10亿个,俩者所需的搜索效率大概为30次和60次,这并不算什么,所以总体来说,红黑树的实用性更好。

总代码:

#pragma once
#include <iostream>
using namespace std;

enum Color
{
    RED,
    BLACK
};

template<class K,class V>
struct RBTreeNode
{
    RBTreeNode<K,V>* _left;
    RBTreeNode<K,V>* _right;
    RBTreeNode<K,V>* _parent;
    pair<K, V> _kv;
    Color _col;
    
    RBTreeNode(const pair<K,V>& kv)
        :_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_kv(kv)
        ,_col(RED)
    {}
};

template<class K,class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
    RBTree()
        :_root(nullptr)
    {}
    bool Insert(const pair<K,V>& kv)
    {
        //第一次插入
        if (_root==nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        //先寻找插入的位置
        Node* cur = _root;
        Node* parent = nullptr;
        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 false;
            }
        }
        cur = new Node(kv);
        cur->_col = RED; //节点颜色初始都为红色
        //开始插入
        if (parent->_kv.first > kv.first)
        {
            parent->_left = cur;
            cur->_parent = parent;
        }
        else
        {
            parent->_right = cur;
            cur->_parent = parent;
        }
        //调节平衡

        //父亲节点存在 且父亲节点为红色
        while (parent&&parent->_col==RED)
        {
            Node* grandParent = parent->_parent;
            if (grandParent && parent == grandParent->_left)
            {
                Node* uncle = grandParent->_right;
                //情况一:叔叔节点存在 且为红色
                if (uncle &&uncle->_col == RED)
                {
                    //调整 将父亲节点和叔叔节点都变成黑色的 祖父节点变成红色的
                    parent->_col = uncle->_col = BLACK;
                    grandParent->_col = RED;

                    //更新迭代  只有这种情况需要迭代更新 因为没有发生旋转
                    cur = grandParent;
                    parent = cur->_parent;
                }
                //情况二:叔叔节点存在且为黑 或者叔叔节点不存在
                else if (uncle == nullptr || uncle->_col == BLACK)
                {
                    //1.cur为父亲节点的左子树,Parent也为祖父节点的左子树 进行右单旋
                    if(cur==parent->_left)
                    {
                        rotateRight(grandParent);
                        //将父亲节点变成黑,祖父节点变成红
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //2.cur为父亲节点的右节点
                    else if (cur == parent->_right)
                    {
                        //进行双旋 左右双旋
                        //先左旋parent节点,然后再右旋祖先节点
                        rotateLeft(parent);
                        rotateRight(grandParent);

                        //将cur点变成黑,祖父节点变成红
                        cur->_col = BLACK;
                        grandParent->_col = RED;
                    }

                    //旋转完后基本都是平衡的了
                    break;
                }
            }
            else if(grandParent && parent==grandParent->_right)
            {
                Node* uncle = grandParent->_left;
                //情况一:叔叔节点存在 且为红色
                if (uncle && uncle->_col == RED)
                {
                    //调整 将父亲节点和叔叔节点都变成黑色的 祖父节点变成红色的
                    parent->_col = uncle->_col = BLACK;
                    grandParent->_col = RED;

                    //更新迭代
                    cur = grandParent;
                    parent = cur->_parent;
                }
                //情况二:叔叔节点存在且为黑 或者叔叔节点不存在
                else if (uncle == nullptr || uncle->_col == BLACK)
                {
                    //1.cur为父亲节点的右子树,Parent也为祖父节点的右子树 进行左单旋
                    if (cur == parent->_right)
                    {
                        rotateLeft(grandParent);
                        //将父亲节点变成黑,祖父节点变成红
                        parent->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //2.cur为父亲节点的左节点
                    else if (cur == parent->_left)
                    {
                        //进行双旋 右左双旋
                        //先右旋parent节点,然后再左旋祖先节点
                        rotateRight(parent);
                        rotateLeft(grandParent);

                        //将cur节点变成黑,祖父节点变成红
                        cur->_col = BLACK;
                        grandParent->_col = RED;
                    }
                    //
                    break;
                }
            
            }
        }
        //防止祖父节点是根节点,统一最后处理成黑色
        _root->_col = BLACK;

        return true;
    }

    bool isbalance()
    {
        //保留一条路线黑色节点的数量 对比每一条黑色的数量
        if (_root && _root->_col == RED)
        {
            cout << "根节点不是黑色的" << endl;
            return false;
        }

        //计算最左路的黑色节点个数 为基准
        int Blackbase = 0;
        Node* left = _root;
        while (left)
        {
            if(left->_col==BLACK)
            {
                ++Blackbase;
            }
            left = left->_left;
        }
        int BlackNum = 0;
        return _isbalance(_root,Blackbase,BlackNum);
    }
    bool _isbalance(Node * root,int Blackbase,int Blacknum)
    {
        if (root == nullptr)
        {
            if (Blackbase != Blacknum)
            {
                cout << "黑色节点数量不同" << endl;
                return false;
            }
            return true;
        }

        //不能出现连续红色节点
        if (root->_col == RED && root->_parent->_col == RED)
        {
            cout << "出现连续的红色节点" << endl;
            return false;
        }
        
        if (root->_col == BLACK)
        {
            ++Blacknum;
        }

        return  _isbalance(root->_left, Blackbase, Blacknum) && _isbalance(root->_right, Blackbase, Blacknum);
    }
    bool IsBalance()
    {
        if (_root && _root->_col == RED)
        {
            cout << "根节点不是黑色" << endl;
            return false;
        }

        // 最左路径黑色节点数量做基准值
        int banchmark = 0;
        Node* left = _root;
        while (left)
        {
            if (left->_col == BLACK)
                ++banchmark;

            left = left->_left;
        }

        int blackNum = 0;
        return _IsBalance(_root, banchmark, blackNum);
    }

    bool _IsBalance(Node* root, int banchmark, int blackNum)
    {
        if (root == nullptr)
        {
            if (banchmark != blackNum)
            {
                cout << "存在路径黑色节点的数量不相等" << endl;
                return false;
            }

            return true;
        }

        if (root->_col == RED && root->_parent->_col == RED)
        {
            cout << "出现连续红色节点" << endl;
            return false;
        }

        if (root->_col == BLACK)
        {
            ++blackNum;
        }

        return _IsBalance(root->_left, banchmark, blackNum)
            && _IsBalance(root->_right, banchmark, blackNum);
    }
    void Inorder()
    {
        _Inorder(_root);
    }
private:
    void rotateRight(Node* Parent)
    {
        Node* SubL = Parent->_left;
        Node* SubLR = SubL->_right;

        //将SubLR交给Parent
        Parent->_left = SubLR;
        if (SubLR)
        {
            //SubLR不为空才更新的父亲节点
            SubLR->_parent = Parent;
        }

        //将Parent进行旋转
        SubL->_right = Parent;
        Node* Parentparent = Parent->_parent;
        SubL->_parent = Parentparent;
        //更新parent的_parent
        Parent->_parent = SubL;

        //检查Parent是否为根节点 若为根节点 则更新根节点 连接子树
        if (_root == Parent)
        {
            _root = SubL;
            _root->_parent = nullptr;
        }
        else
        {
            if (Parentparent->_left == Parent)
            {
                Parentparent->_left = SubL;
            }
            else if (Parentparent->_right == Parent)
            {
                Parentparent->_right = SubL;
            }
        }
    
    }
    //左单旋
    void rotateLeft(Node* Parent)
    {
        Node* subR = Parent->_right;
        Node* subRL = subR->_left;

        //将subLR交给父亲节点的右节点
        Parent->_right = subRL;
        if (subRL)
            subRL->_parent = Parent;

        //开始左旋
        subR->_left = Parent;
        Node* Parentparent = Parent->_parent;
        //更新subR的父亲节点
        subR->_parent = Parentparent;

        Parent->_parent = subR;

        //连接上面的节点
        if (_root == Parent)
        {
            _root = subR;
            _root->_parent = nullptr;
        }
        else
        {
            if (Parentparent->_left == Parent)
            {
                Parentparent->_left = subR;
            }
            else
            {
                Parentparent->_right = subR;
            }
        }

        
    }
    
    void _Inorder(Node* root)
    {
        if (root == nullptr)
        {
            return;
        }
        //中序遍历
        _Inorder(root->_left);
        cout << root->_kv.first << ' ' << root->_kv.second << endl;
        _Inorder(root->_right);
    }
private:
    Node* _root;
};

红黑树的删除在面试中也很少出现,所以可能以后会更新,如果此文章有什么错误的地方,希望各位观看的大佬能够指点出来,谢谢支持!

😃

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值