本期我们来手撕红黑树,相信大家很早就听过红黑树的传说了吧,这里最好有AVL树的基础,我这里把AVL的相关博客贴在这里,没有看过的同学建议先看看
话不多说,我们直接进入正题
目录
红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍 ,因而是 接近平衡 的。
红黑树的性质1. 每个结点不是红色就是黑色2. 根节点是黑色的3. 如果一个节点是红色的,则它的两个孩子结点必须是黑色的4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )
从上面的性质我们可以分析出,在红黑树中,任意一条路径都没有连续的红节点,我们再看上面的红黑树图片,这棵红黑树有11条路径,路径的尾部是NULL,也就是图里面的NIL节点,从根节点到NIL节点就是一条路径
红黑树要根据这些条件来做到最长路径不超过最短路径的二倍,是怎么做到的呢?

我们把8下面的节点去掉,再把8变为黑色,按照它的条件,大家想一想
最短路径一定是全黑,最长路径就是一黑一红的重复,而且我们可以分析出,每条路径里黑色节点的占比一定是大于等于1/2的
红黑树是比AVL更优的结构,在同样数量级下,红黑树的高度可能是AVL的二倍,但是一亿个数据也就是30和60的区别,对于cpu来说就是0.001秒和0.002秒的差距(我这里随便举的例),但是AVL树要插入这么多节点是需要进行大量旋转的,因为AVL树的左右高度差不能超过1,而红黑树就不用考虑那么多,所以总体而已红黑树是更优的,现实里也是一样,AVL树是几乎不用的,而红黑树缺被沿用了下来,这些大家了解即可
下面我们来进行代码实现
- enum Colour
- {
- RED,
- BLACK
- };
- template<class K,class V>
- struct RBTreeNode
- {
- RBTreeNode
* _left; - RBTreeNode
* _right; - RBTreeNode
* _parent; - pair
_kv; - Colour _col;
- RBTreeNode(const pair
& kv) - :_left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _kv(kv)
- ,_col(RED)
- {}
- };
-
- template<class K, class V>
- struct RBTree
- {
- typedef RBTreeNode
Node; - public:
- bool Insert(const pair
& 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->_right;
- }
- else if (cur->_kv.first > kv.first)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
- cur = new Node(kv);
- if (parent->_kv.first < kv.first)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
- cur->_parent = parent;
- //...
-
- return true;
- }
- private:
- Node* _root = nullptr;
- };
我们先写好框架和insert的部分,这里insert和AVL树没什么区别,我就直接复制过来了,和AVL树相比,这里还多了一个颜色,我们使用枚举类型即可

下面我们想一想插入该怎么办?我们插入新的节点,是插入红色节点还是黑色呢?答案是红色,插入黑色节点,所有的路径都会受到影响(红黑树各路径上黑色节点数相同),而插入红色节点,只有当前路径会受到影响,两权相害取其轻

并且左边这种插入是不受影响的,所以我们插入红色节点
如果插入后,父亲节点是黑色,我们就插入结束,如果父亲是红色,我们就需要进行变化

并且,如果父亲节点是红色,那么一定会有爷爷节点,而且是黑色,下面我们就需要分类讨论

如果父亲有兄弟,并且兄弟是红色, 我们要先把父亲和叔叔变黑,爷爷变红

这里父亲一定要变黑,不然是没有办法解决连续的红节点问题的 ,此时在这个局部的子树里,黑色节点数里不变,连续的红节点问题暂时解决
再看下一步,这里分三种情况讨论
1.如果祖父没有父亲,也就是祖父是根节点,我们就把祖父再变黑
2.如果祖父有父亲,父亲是黑色,那么就结束
3.如果有父亲并且父亲是红色,那么需要我们就把祖父当作新增节点继续向上处理


比如此时,我们只需把parent和uncle变黑,再把grandfather变红,最后再把grandfather变黑即可
不过这里是恰好uncle存在且为红,还有别的情况需要讨论
我们先看现在的情况,此时插入完成,相比之前的树,相当于所有路径都多了一个黑色节点,黑色节点多是好的,我们插入时也会方便

下面我们来看这种情况,uncle不存在,我们插入新节点后,最长路径超过了最短路径的二倍,此时就需要进行旋转

这里是一个简单的右单旋,然后需要变色

我们把25变红,22变黑即可,所以uncle不存在的话,我们需要旋转加变色
所以总结一下红黑树的变化
1.叔叔存在且为红,2.叔叔不存在,3.叔叔存在且为黑
什么时候会有第三种情况?

比如我们在下面两个红节点的四个位置随意插入一个新节点 ,就会出现叔叔存在且为黑

此时,我们先把父亲和叔叔变黑,祖父变红

然后grandfather变成cur,其他的依次改变,此时就是叔叔存在且为黑

接着我们进行左旋,然后把parent变黑,grandfather变红即可
我们对比前面的,就可以发现,这里parent都是要变黑的
最后总结:1.红黑树插入的关键是看uncle,uncle存在且为红,则变色+继续向上处理,2.如果uncle不存在或者存在且为黑,则旋转+变色
上面是具体的图,下面我们来看抽象图

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

再推测,a和b就是红节点,在a和b的任意孩子位置新增节点,都会导致变色,出现叔叔存在且为红的情况 ,上面一共有4*4*4*3 = 256种情况(c/d/e每个4种,a和b4种),但无论是哪一种,处理方法都是这样的,再想一想,如果c/d/e是每条路径有两个黑色节点的呢?那情况就太恐怖了,和AVL树是一样的,这里也是无穷无尽的,所以我们需要抽象图来帮助我们理解
- while (parent && parent->_col == RED)
- {
- Node* grandfather = parent->_parent;
- if (parent == grandfather->_left)
- {
- Node* uncle = grandfather->_right;
- if (uncle && uncle->_col == RED)//叔叔存在且为红
- {
- //变色
- uncle->_col = BLACK;
- parent->_col = BLACK;
- grandfather->_col = RED;
- //继续向上处理
- cur = grandfather;
- parent = cur->_parent;
- }
- }
- }
- _root->_col = BLACK;
我们这里写出代码

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑


它的下面是两个红色节点,在它下面任意一个位置插入都会引发问题
有上面的条件,我们可以推出,c是每条路径包含一个黑色节点的红黑树,这里c有四种情况(如上图所示),d和e可能是空,也可能是红节点(2种),这里要是排列组合的话会有4*2*2*4 = 64种情况,但不管是哪一种,这里都是旋转+变色,下面我们来看具体例子

比如我们在最下面插入一个红色节点,我们需要把父亲和叔叔先变黑

接着进行右旋,最后再把p变黑,g变红即可
这里的旋转就是之前的4种,单旋和双旋各两种,所以我们直接把AVL树那块的左旋和右旋代码拷贝过来,再把平衡因子删掉即可
- else//叔叔不存在/存在且为黑
- {
- if (cur == parent->_left)
- {
- RotateR(grandfather);
- parent->_col = BLACK;
- grandfather->_col = RED;
- }
- else
- {
- RotateL(parent);
- RotateR(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
- break;
- }
我们上面举例只举了右旋,双旋也是一样的,这里就不再举例
- else//parent在grandfather的右边
- {
- Node* uncle = grandfather->_left;
- if (uncle && uncle->_col == RED)//叔叔存在且为红
- {
- uncle->_col = BLACK;
- parent->_col = BLACK;
- grandfather->_col = RED;
- cur = grandfather;
- parent = cur->_parent;
- }
- else//叔叔不存在/存在且为黑
- {
- if (cur == parent->_right)
- {
- RotateL(grandfather);
- parent->_col = BLACK;
- grandfather->_col = RED;
- }
- else
- {
- RotateR(parent);
- RotateL(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
- break;
- }
- }
接着我们再对称写出另一半代码即可
下面我们来写一个IsBalance,来看看我们的红黑树是否正确
- bool IsBalance()
- {
- return IsBalance(_root);
- }
- bool CheckColour(Node* root,int blacknum,int benchmark)//检查节点颜色
- {
- if (root == nullptr)
- {
- if (benchmark != blacknum)//黑色节点数量不对
- return false;
- return true;
- }
- if (root->_col == BLACK)//检测黑色节点数量
- {
- ++blacknum;
- }
- if (root->_col == RED && root->_parent && root->_parent->_col == RED)
- cout << root->_kv.first << "连续红节点" << endl;
- return false;
-
- return CheckColour(root->_left,blacknum, benchmark)
- && CheckColour(root->_right, blacknum, benchmark);
-
- }
- bool IsBalance(Node* root)
- {
- if (root == nullptr)
- return true;
- if (root->_col != BLACK)
- return false;
- int benchmark = 0;//黑色节点基准值
- Node* cur = _root;
- while (cur)
- {
- if (cur->_col==BLACK)
- ++benchmark;
- cur = cur->_left;
- }
- return CheckColour(root,0, benchmark);
- }
这里我们判断是否有连续红节点,每条路径上的黑色节点数量,黑色节点数量我们使用一个基准值来判断,我们先计算这棵树的最左路径有多少黑色节点,把它作为基准值即可,后续如果不一样,那就说明树有问题
我们简单测试一下,没有问题 ,然后我们再测试一下随机数

这里也没有问题
同样的,红黑树这里我们也只实现insert,删除有点复杂了,如果大家想要去看看删除是如何实现的,可以在网上找一找,或者去看看算法导论这本书
红黑树是非常重要的结构,现在很多地方都在使用红黑树,所以要求大家要掌握红黑树
- enum Colour
- {
- RED,
- BLACK
- };
- template<class K,class V>
- struct RBTreeNode
- {
- RBTreeNode
* _left; - RBTreeNode
* _right; - RBTreeNode
* _parent; - pair
_kv; - Colour _col;
- RBTreeNode(const pair
& kv) - :_left(nullptr)
- , _right(nullptr)
- , _parent(nullptr)
- , _kv(kv)
- ,_col(RED)
- {}
- };
-
- template<class K, class V>
- struct RBTree
- {
- typedef RBTreeNode
Node; - public:
- bool Insert(const pair
& 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->_right;
- }
- else if (cur->_kv.first > kv.first)
- {
- parent = cur;
- cur = cur->_left;
- }
- else
- {
- return false;
- }
- }
- cur = new Node(kv);
- if (parent->_kv.first < kv.first)
- {
- parent->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
- cur->_parent = parent;
-
- while (parent && parent->_col == RED)
- {
- Node* grandfather = parent->_parent;
- if (parent == grandfather->_left)//parent在grandfather的左边
- {
- Node* uncle = grandfather->_right;
- if (uncle && uncle->_col == RED)//叔叔存在且为红
- {
- //变色
- uncle->_col = BLACK;
- parent->_col = BLACK;
- grandfather->_col = RED;
- //继续向上处理
- cur = grandfather;
- parent = cur->_parent;
- }
- else//叔叔不存在/存在且为黑
- {
- if (cur == parent->_left)
- {
- RotateR(grandfather);
- parent->_col = BLACK;
- grandfather->_col = RED;
- }
- else
- {
- RotateL(parent);
- RotateR(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
- break;
- }
- }
- else//parent在grandfather的右边
- {
- Node* uncle = grandfather->_left;
- if (uncle && uncle->_col == RED)//叔叔存在且为红
- {
- uncle->_col = BLACK;
- parent->_col = BLACK;
- grandfather->_col = RED;
- cur = grandfather;
- parent = cur->_parent;
- }
- else//叔叔不存在/存在且为黑
- {
- if (cur == parent->_right)
- {
- RotateL(grandfather);
- parent->_col = BLACK;
- grandfather->_col = RED;
- }
- else
- {
- RotateR(parent);
- RotateL(grandfather);
- cur->_col = BLACK;
- grandfather->_col = RED;
- }
- break;
- }
- }
- }
- _root->_col = BLACK;
- return true;
- }
-
- void RotateL(Node* parent)//左单旋
- {
- Node* cur = parent->_right;
- Node* curleft = cur->_left;
- parent->_right = curleft;
- if (curleft)
- {
- curleft->_parent = parent;
- }
- cur->_left = parent;
- Node* ppnode = parent->_parent;
- parent->_parent = cur;
- if (parent == _root)//parent是根节点
- {
- _root = cur;
- cur->_parent = nullptr;
- }
- else
- {
- if (ppnode->_left == parent)
- {
- ppnode->_left = cur;
- }
- else
- {
- ppnode->_right = cur;
- }
- cur->_parent = ppnode;
- }
- }
-
- void RotateR(Node* parent)//右单旋
- {
- Node* cur = parent->_left;
- Node* curright = cur->_right;
- parent->_left = curright;
- if (curright)
- {
- curright->_parent = parent;
- }
- Node* ppnode = parent->_parent;
- cur->_right = parent;
- parent->_parent = cur;
- if (ppnode == nullptr)
- {
- _root = cur;
- cur->_parent = nullptr;
- }
- else
- {
- if (ppnode->_left == parent)
- {
- ppnode->_left = cur;
- }
- else
- {
- ppnode->_right = cur;
- }
- cur->_parent = ppnode;
- }
- }
- bool IsBalance()
- {
- return IsBalance(_root);
- }
- bool CheckColour(Node* root,int blacknum,int benchmark)//检查节点颜色
- {
- if (root == nullptr)
- {
- if (benchmark != blacknum)//黑色节点数量不对
- return false;
- return true;
- }
- if (root->_col == BLACK)//检测黑色节点数量
- {
- ++blacknum;
- }
- if (root->_col == RED && root->_parent && root->_parent->_col == RED)
- {
- cout << root->_kv.first << "连续红节点" << endl;
- return false;
- }
-
- return CheckColour(root->_left,blacknum, benchmark)
- && CheckColour(root->_right, blacknum, benchmark);
-
- }
- bool IsBalance(Node* root)
- {
- if (root == nullptr)
- return true;
- if (root->_col != BLACK)
- return false;
- int benchmark = 0;//黑色节点基准值
- Node* cur = _root;
- while (cur)
- {
- if (cur->_col==BLACK)
- ++benchmark;
- cur = cur->_left;
- }
- return CheckColour(root,0, benchmark);
- }
- private:
- Node* _root = nullptr;
- };
以上即为本期全部内容,希望大家有所收获
如有错误,还请指正