本期我们来手撕红黑树,相信大家很早就听过红黑树的传说了吧,这里最好有AVL树的基础,我这里把AVL的相关博客贴在这里,没有看过的同学建议先看看
话不多说,我们直接进入正题
目录
[编辑 红黑树的性质](#编辑 红黑树的性质)
红黑树的概念
红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍 ,因而是 接近平衡 的。
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点必须是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )
从上面的性质我们可以分析出,在红黑树中,任意一条路径都没有连续的红节点,我们再看上面的红黑树图片,这棵红黑树有11条路径,路径的尾部是NULL,也就是图里面的NIL节点,从根节点到NIL节点就是一条路径
红黑树要根据这些条件来做到最长路径不超过最短路径的二倍,是怎么做到的呢?
我们把8下面的节点去掉,再把8变为黑色,按照它的条件,大家想一想
最短路径一定是全黑,最长路径就是一黑一红的重复,而且我们可以分析出,每条路径里黑色节点的占比一定是大于等于1/2的
红黑树是比AVL更优的结构,在同样数量级下,红黑树的高度可能是AVL的二倍,但是一亿个数据也就是30和60的区别,对于cpu来说就是0.001秒和0.002秒的差距(我这里随便举的例),但是AVL树要插入这么多节点是需要进行大量旋转的,因为AVL树的左右高度差不能超过1,而红黑树就不用考虑那么多,所以总体而已红黑树是更优的,现实里也是一样,AVL树是几乎不用的,而红黑树缺被沿用了下来,这些大家了解即可
下面我们来进行代码实现
代码实现
cpp
enum Colour
{
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;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(RED)
{}
};
template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
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->_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为叔叔节点**
情况一 : cur 为红, p 为红, g 为黑, u 存在且为红
解决方式:将 p,u 改为黑, g 改为红,然后把 g 当成 cur ,继续向上调整。
我们在这里简单分析一下,第一种情况是abcde都是空,cur是新增,就是我们上面讲的
第二种情况是c/d/e是每条路径有一个黑色节点的红黑树,有4种情况
再推测,a和b就是红节点,在a和b的任意孩子位置新增节点,都会导致变色,出现叔叔存在且为红的情况 ,上面一共有4*4*4*3 = 256种情况(c/d/e每个4种,a和b4种),但无论是哪一种,处理方法都是这样的,再想一想,如果c/d/e是每条路径有两个黑色节点的呢?那情况就太恐怖了,和AVL树是一样的,这里也是无穷无尽的,所以我们需要抽象图来帮助我们理解
cpp
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****存在且为黑
uncle不存在的很简单,只需要旋转即可
再看叔叔存在且为黑,首先这里cur一定不会是新增,否则这棵树就不是红黑树了,因为右边有两个黑色节点了
所以我们可以确定cur应该是黑色的
它的下面是两个红色节点,在它下面任意一个位置插入都会引发问题
有上面的条件,我们可以推出,c是每条路径包含一个黑色节点的红黑树,这里c有四种情况(如上图所示),d和e可能是空,也可能是红节点(2种),这里要是排列组合的话会有4*2*2*4 = 64种情况,但不管是哪一种,这里都是旋转+变色,下面我们来看具体例子
比如我们在最下面插入一个红色节点,我们需要把父亲和叔叔先变黑
接着进行右旋,最后再把p变黑,g变红即可
这里的旋转就是之前的4种,单旋和双旋各两种,所以我们直接把AVL树那块的左旋和右旋代码拷贝过来,再把平衡因子删掉即可
cpp
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;
}
我们上面举例只举了右旋,双旋也是一样的,这里就不再举例
cpp
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,来看看我们的红黑树是否正确
cpp
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,删除有点复杂了,如果大家想要去看看删除是如何实现的,可以在网上找一找,或者去看看算法导论这本书
红黑树是非常重要的结构,现在很多地方都在使用红黑树,所以要求大家要掌握红黑树
完整代码
cpp
enum Colour
{
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;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(RED)
{}
};
template<class K, class V>
struct RBTree
{
typedef RBTreeNode<K, V> Node;
public:
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->_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;
};
以上即为本期全部内容,希望大家有所收获
如有错误,还请指正