红黑树的变色和旋转


红黑树

红黑树的概念

红黑树是一种特殊的二叉搜索树,在普通二叉搜索树的基础上,红黑树给每一个结点加了颜色,红黑树的每一个结点为黑色或者红色。

红黑树满足以下性质:

  1. 根节点是黑色
  2. 红色结点如果有孩子,孩子必须是黑色
  3. 每一条路径上的黑色结点数量相等
  4. 叶子结点的左右认为是黑色,称为NIL结点(即空结点)
    在这里插入图片描述

这几条性质联合起来决定了红黑树最长路径上的结点个数不会超过最短路径上的结点个数的两倍,其中第三条性质尤为重要。

红黑树与AVL树相比,它的平衡要求不那么严格,不需要每一个左右子树高度差严格小于等于1,在进行插入等操作的时候,旋转次数较AVL树有所减小。红黑树在保证相对平衡的情况下,能够确保较为高效的查找效率,被用做map和set的底层数据结构。

红黑树的节点

enum Color
{
    RED,
    BLACK
};
template<class Key,class Value>
struct RedBlackTreeNode
{
    typedef RedBlackTreeNode<Key,Value> Node;
    Color color=RED;//默认是红色
    Node* left=nullptr;
    Node* right=nullptr;
    Node* parent=nullptr;
    pair<Key,Value> kv;
    RedBlackTreeNode(const pair<Key,Value>& x=pair<Key,Value>()):kv(x){}
};

红黑树的节点默认是红色,这与它的插入操作有关。

map和set的结构

在STL库中的map、set底层的红黑树除了root节点之外,还有一个head结点,head结点指向root结点,同时也指向最左和最右节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SmEWIJm3-1667981357315)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221108225135263.png)]

在实际实现的过程中,不需要设计的与库完全一致,只要能够实现红黑树的功能和性质即可。

红黑树的插入

红黑树中插入结点,默认应该插入红色结点,例如插入28

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UY9m6DPm-1667981357315)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221108230319969.png)]

当插入的28为红色结点时,只需要把它的parent和uncle变为黑色,然后把它的grandfather变成红色,这样经过调整之后还是红黑树,只需要经过几步简单的变色操作。

如果插入的28是黑色结点,那么从28到13这条路径上的黑色结点数量+1,为了满足红黑树每一条路径上的黑色结点数量相等的性质,需要想办法将其它路径上的黑色结点数量+1,操作成本太高且复杂。

红黑树插入结点的步骤
  1. 寻找插入位置
  2. 进行变色或者旋转,使得插入结点以后的树任然保持红黑树的性质
寻找插入位置
template<class Key,class Value>
class RedBlackTree
{
    typedef typedef RedBlackTreeNode<Key,Value> Node;
public:
    bool insert(const pair<Key,Value>& kv)
    {
        if(root==nullptr)
        {
            root=new Node(kv);
            root->color=BLACK;//根节点的颜色为黑色
            return true;
        }
        Node* cur=root;
        Node* par=root;
        while(cur)
        {
            if(cur->kv.first>kv.first)
            {
                par=cur;
                cur=cur->left;
            }
            else if(cur->kv.first<kv.first)
            {
                par=cur;
                cur=cur->right;
            }
            else
                return false;//默认不允许键值对冗余
        }
        cur=new Node(kv);
        if(par->kv.first>kv.first)
            par->left=cur;
        else
            par->right=cur;
        cur->parent=par;
        //结点插入完成,开始调整
        if(par->color==BLACK)
            return true;
        else//需要进行变色或者旋转
        	Adjust(cur);//cur->color==RED&&par->color==RED
        return true;
    }
private:
    Node* root=nullptr;
};
进行变色或旋转

进行变色或旋转有多种可能的情况。在进行变色或者旋转时,主要受到影响的结点有cur、cur->parent、cur->parent->parent(即cur的grandfather)、以及cur的uncle.

例如插入28

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxrkqBXo-1667981357316)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221108230319969.png)]

需要将27(cur的parent)和22(cur的uncle)变黑,将25(cur的grandfather)变红。

将变色或者旋转进行抽象有3种情况:

  • uncle存在且uncle为红色。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5aOSPk3o-1667981357316)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221108234347563.png)]

    将par和uncle变色为黑色,再将grandfather变色为红色。由于grandfather变为红色,如果grandfather是root的话,需要将grandfather在变为黑色,同时此次调整结束;如果grandfather不是root的话,需要考虑grandfather->parent的颜色,若为黑色,则此次调整可以结束,若为红色,需要将grandfather作为新的cur继续进行调整。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPL2HrOz-1667981357317)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221108235619265.png)]

    template<class Key,class Value>
    class RedBlackTree
    {
        typedef typedef RedBlackTreeNode<Key,Value> Node;
        //......
    private:
        void Adjust(Node* cur)
        {
            //定义出par、grandfather、uncle
            Node* par=cur->parent;
            Node* grandfather=par->parent;
            Node* uncle=grandfather->left==par?grandfather->right:grandfather->left;
            if(uncle&&uncle->color==RED)
            {
                //uncle存在且为红色
                par->color=uncle->color=BLACK;
                grandfather->color=RED;
                if(grandfather==root)
                    grandfather->color=BLACK;
                else
                {
                    if(grandfather->parent->color==BLACK)
                        return;//调整完毕
                    else
                        Adjust(grandfather);//继续调整
                }
            }
        }
    private:
        Node* root=nullptr;
    };
    
  • uncle不存在。

    uncle不存在的时候,情况如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-akyV8ScM-1667981357317)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221109125833100.png)]

    对于case1,需要把grandfather对应的这棵树进行右单旋,同时把par变黑,grandfather变红。

    对于case2,需要把grandfather对应的这颗树进行左单旋,同时把par变黑,grandfather变红。

    对于case3,先把par进行左单旋,在把grandfather进行右单旋,同时将cur变黑,grandfather变红。

    对于case4,先把par进行右单旋,在把grandfather进行左单旋,同时将cur变黑,grandfather变红。

    void Adjust(Node* cur)
    {
        //定义出par、grandfather、uncle
        Node* par=cur->parent;
        Node* grandfather=par->parent;
        Node* uncle=grandfather->left==par?grandfather->right:grandfather->left;
        //uncle不存在
        if(!uncle)
        {
            if(par==grandfather->left)
            {
                if(par->left==cur)//case1
                {
                    RightRotate(grandfather);
                    par->color=BLACK;
                    grandfather->color=RED;
                }  
                else//case3
                {
                    LeftRotate(par);
                    RightRotate(grandfather);
                    cur->color=BLACK;
                    grandfather->color=RED;
                }
            }
            else//grandfather->right==par
            {
                if(par->right==cur)//case2
                {
                    LeftRotate(grandfather);
                    par->color=BLACK;
                    grandfather->color=RED;
                }
                else//case4
                {
                    RightRotate(par);
                    LeftRotate(grandfather);
                    cur->color=BLACK;
                    grandfather->color=RED;
                }
            }
        }
    }
    void LeftRotate(Node *par)
    {
        Node *ppar = par->parent; //先记录par的父,旋转完毕以后让ppar指向cur
        Node *cur = par->right;
        par->right = cur->left;
        if (par->right)
            par->right->parent = par;
        cur->left = par;
        cur->parent = par->parent;
        par->parent = cur;
        if (par == root)
        {
            root = cur;
            return;
        }
        // par不是root
        if (ppar->left == par)
            ppar->left = cur;
        else
            ppar->right = cur;
    }
    void RightRotate(Node *par)
    {
        Node *ppar = par->parent;
        Node *cur = par->left;
        par->left = cur->right;
        if (par->left)
            par->left->parent = par;
        cur->right = par;
        cur->parent = par->parent;
        par->parent = cur;
        if (par == root)
        {
            root = cur;
            return;
        }
        if (ppar->left == par)
            ppar->left = cur;
        else
            ppar->right = cur;
    }
    
  • uncle存在且uncle为黑色

    case1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BE2A5nC-1667981357318)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221109135510555.png)]

    认为1号子树黑色节点的数量为num(1),2号子树黑色节点数量为num(2),依次类推。则有num(1)=num(2)=num(*)=num(3)+1=num(4)+1,因此为了保证旋转以后任然保持红黑树的性质,应该让grandfather变红,par变黑。所以当grandfather->leftpar&&par->leftcur,应该让grandfather右旋,并将par变黑,grandfather变红

    case2

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CekgPJlr-1667981357330)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221109135809317.png)]

    当grandfather->rightpar&&par->rightcur时,将grandfather左旋,同时将par变黑,grandfather变红

    case3

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0Dr4pci-1667981357331)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221109141148074.png)]

    当grandfather->leftpar&&par->rightcur,需要将par左单旋,然后将grandfather右单旋,同时将cur变为黑色,grandfather变为红色。

    case4

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-olw0qEZW-1667981357332)(C:\Users\19199\Desktop\c++研发\数据结构\高阶数据结构\红黑树.assets\image-20221109142146208.png)]

    当grandfather->rightpar&&par->leftcur的时候,先把par进行右单旋,在把grandfather进行左单旋,将grandfather变红,cur变黑

    void Adjust(Node* cur)
    {
        //定义出par、grandfather、uncle
        Node* par=cur->parent;
        Node* grandfather=par->parent;
        Node* uncle=grandfather->left==par?grandfather->right:grandfather->left;
        if(uncle&&uncle->color==BLACK)
        {
            if(grandfather->left==par)
            {
                if(par->left==cur)//case1
                {
                    RightRotate(grandfather);
                    par->color=BLACK;
                    grandfather->color=RED;
                }
                else//case3
                {
                    LeftRotate(par);
                    RightRotate(grandfather);
                    cur->color=BLACK;
                    grandfather->color=RED;
                }
            }
            else//grandfather->right==par
            {
                if(par->right==cur)//case2
                {
                    LeftRotate(grandfather);
                    par->color=BLACK;
                    grandfather->color=RED;
                }
                else//case4
                {
                    RightRotate(par);
                    LeftRotate(grandfather);
                    grandfather->color=RED;
                    cur->color=BLACK;
                }
            }
        }
    }
    

验证红黑树的整体代码

#include <iostream>
#include <utility>
using std::cout;
using std::endl;
using std::make_pair;
using std::pair;
enum Color
{
    RED,
    BLACK
};
template <class Key, class Value>
struct RedBlackTreeNode
{
    typedef RedBlackTreeNode<Key, Value> Node;
    Color color = RED; //默认是红色
    Node *left = nullptr;
    Node *right = nullptr;
    Node *parent = nullptr;
    pair<Key, Value> kv;
    RedBlackTreeNode(const pair<Key, Value> &x = pair<Key, Value>()) : kv(x) {}
};
template <class Key, class Value>
class RedBlackTree
{
    typedef RedBlackTreeNode<Key, Value> Node;

public:
    bool insert(const pair<Key, Value> &kv)
    {
        if (root == nullptr)
        {
            root = new Node(kv);
            root->color = BLACK;
            return true;
        }
        Node *cur = root;
        Node *par = root;
        while (cur)
        {
            if (cur->kv.first > kv.first)
            {
                par = cur;
                cur = cur->left;
            }
            else if (cur->kv.first < kv.first)
            {
                par = cur;
                cur = cur->right;
            }
            else
                return false; //默认不允许键值对冗余
        }
        cur = new Node(kv);
        if (par->kv.first > kv.first)
            par->left = cur;
        else
            par->right = cur;
        cur->parent = par;
        //结点插入完成,开始调整
        if (par->color == BLACK)
            return true;
        else             //需要进行变色或者旋转
            Adjust(cur); // cur->color==RED&&par->color==RED
        return true;
    }
    void Adjust(Node *cur)
    {
        //定义出par、grandfather、uncle
        Node *par = cur->parent;
        Node *grandfather = par->parent;
        Node *uncle = grandfather->left == par ? grandfather->right : grandfather->left;
        if (uncle && uncle->color == RED)
        {
            // uncle存在且为红色
            par->color = uncle->color = BLACK;
            grandfather->color = RED;
            if (grandfather == root)
                grandfather->color = BLACK;
            else
            {
                if (grandfather->parent->color == BLACK)
                    return; //调整完毕
                else
                    Adjust(grandfather); //继续调整
            }
        }
        // uncle不存在
        else if (!uncle)
        {
            if (par == grandfather->left)
            {
                if (par->left == cur) // case1
                {
                    RightRotate(grandfather);
                    par->color = BLACK;
                    grandfather->color = RED;
                }
                else // case3
                {
                    LeftRotate(par);
                    RightRotate(grandfather);
                    cur->color = BLACK;
                    grandfather->color = RED;
                }
            }
            else // grandfather->right==par
            {
                if (par->right == cur) // case2
                {
                    LeftRotate(grandfather);
                    par->color = BLACK;
                    grandfather->color = RED;
                }
                else // case4
                {
                    RightRotate(par);
                    LeftRotate(grandfather);
                    cur->color = BLACK;
                    grandfather->color = RED;
                }
            }
        }
        else // uncle存在且为黑色
        {
            if (grandfather->left == par)
            {
                if (par->left == cur) // case1
                {
                    RightRotate(grandfather);
                    par->color = BLACK;
                    grandfather->color = RED;
                }
                else // case3
                {
                    LeftRotate(par);
                    RightRotate(grandfather);
                    cur->color = BLACK;
                    grandfather->color = RED;
                }
            }
            else // grandfather->right==par
            {
                if (par->right == cur) // case2
                {
                    LeftRotate(grandfather);
                    par->color = BLACK;
                    grandfather->color = RED;
                }
                else // case4
                {
                    RightRotate(par);
                    LeftRotate(grandfather);
                    grandfather->color = RED;
                    cur->color = BLACK;
                }
            }
        }
    }
    bool IsRedBlackTree() const //验证红黑树
    {
        if (IsBlackEqualInEveryPath() && IsEveryRedNodePar_a_Black())
        {
            cout << "最长路径与最短路径的比例为" << ProportionMaxPath_MinPath() << endl;
            cout << "红色结点的个数为" << CountNum(root, RED) << endl;
            cout << "黑色结点的个数为" << CountNum(root, BLACK) << endl;
            return true;
        }
        if (!IsBlackEqualInEveryPath())
            cout << "每一条路径上黑色结点的是数量不相等" << endl;
        if (!IsEveryRedNodePar_a_Black())
            cout << "存在2个红色结点为父子关系" << endl;
        cout << "最长路径与最短路径的比例为" << ProportionMaxPath_MinPath() << endl;
        cout << "红色结点的个数为" << CountNum(root, RED) << endl;
        cout << "黑色结点的个数为" << CountNum(root, BLACK) << endl;
        return false;
    }
    //是否每一条路径上黑色结点的数量都相等
    bool IsBlackEqualInEveryPath() const
    {
        int mark = 0;
        Node *tmp = root;
        while (tmp)
        {
            if (tmp->color == BLACK)
                mark++;
            tmp = tmp->right;
        }
        return PrevOrder(root, mark, 0);
    }
    bool PrevOrder(Node *cur, int mark, int blacknum) const
    {
        if (cur == nullptr)
        {
            if (blacknum != mark)
                return false;
            return true;
        }
        if (cur->color == BLACK)
            blacknum++;
        return PrevOrder(cur->left, mark, blacknum) && PrevOrder(cur->right, mark, blacknum);
    }
    //是否每一个红色结点的父亲都是黑色
    bool IsEveryRedNodePar_a_Black() const
    {
        return Inorder(root);
    }
    bool Inorder(Node *root) const
    {
        if (root == nullptr)
            return true;
        bool prev = Inorder(root->left);
        if (root->color == RED)
        {
            if (root->parent->color == RED)
                return false;
        }
        bool last = Inorder(root->right);
        return last && prev;
    }
    //最长路径与最短路径的比例
    double ProportionMaxPath_MinPath() const
    {
        int max = INT32_MIN, min = INT32_MAX;
        DFS(root, max, min, 0);
        return max == 0 ? 0 : max / (double)min;
    }
    void DFS(Node *root, int &max, int &min, int len) const
    {
        if (root == nullptr)
        {
            if (len > max)
                max = len;
            if (len < min)
                min = len;
            return;
        }
        len++;
        DFS(root->left, max, min, len);
        DFS(root->right, max, min, len);
    }
    int CountNum(Node *cur, Color color)const //统计结点的个数
    {
        if (cur == nullptr)
            return 0;
        if (cur->color == color)
            return 1 + CountNum(cur->left, color) + CountNum(cur->right, color);
        else
            return CountNum(cur->left, color) + CountNum(cur->right, color);
    }

private:
    void LeftRotate(Node *par)
    {
        Node *ppar = par->parent; //先记录par的父,旋转完毕以后让ppar指向cur
        Node *cur = par->right;
        par->right = cur->left;
        if (par->right)
            par->right->parent = par;
        cur->left = par;
        cur->parent = par->parent;
        par->parent = cur;
        if (par == root)
        {
            root = cur;
            return;
        }
        // par不是root
        if (ppar->left == par)
            ppar->left = cur;
        else
            ppar->right = cur;
    }
    void RightRotate(Node *par)
    {
        Node *ppar = par->parent;
        Node *cur = par->left;
        par->left = cur->right;
        if (par->left)
            par->left->parent = par;
        cur->right = par;
        cur->parent = par->parent;
        par->parent = cur;
        if (par == root)
        {
            root = cur;
            return;
        }
        if (ppar->left == par)
            ppar->left = cur;
        else
            ppar->right = cur;
    }

private:
    Node *root = nullptr;
};
void Test()
{
    RedBlackTree<int, int> tree;
    srand((unsigned int)time(nullptr));
    int i = 0;
    const int n = 10000;
    for (int i = 0; i < n; i++)
    {
        tree.insert(make_pair(rand() % 32767, i));
        tree.IsRedBlackTree();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值