红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
- 每个节点不是黑色就是红色
- 根是黑色的
- 如果一个节点是红色,那他的两个孩子一定是黑色的。
- 每条路径都有相同数目的黑色节点。
- 数路径要加上空节点,红黑树中的空节点(NIL节点)都是黑色的。
红黑树的模拟实现
1.节点
enum Colour
{
Red,
Black
};
template<class K, class V>
class RBNode
{
public:
RBNode<K, V>* _left;
RBNode<K, V>* _right;
RBNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
,_col(Red)
{}
};
和AVL不同的是:少了平衡因子,多了颜色。
2.插入
- 前面都和AVL或者说和二叉搜索树一样,先判断大小。
- 后面就要看插入后有没有违反红黑树的规则/性质。
因为红黑树的第四条性质,所以插入节点主要看uncle。
1.u为红
cur为新增节点,新增后有了连续的红节点,违反了第三条规则,而uncle为红色,因此需要进行调整:
把p和u变为黑,再把g变为红,
- 如果g为根,则调整完,把它再变回黑。
- 如果g不为根,则调整完,把g当成cur继续往上调整。
2.u不存在/u为黑
u的情况有两种:
- 如果u不存在,那么cur一定为新增节点,因为插入前,此树默认已经是红黑树,所以cur的p一定是红色
- 如果u存在,那么cur一定不为新增节点,原因同理,而此时的cur是红色,是因为子树因为情况1调整过来的。
解决方法:
- p为g的左孩子,cur为p的左孩子,则进行右单旋转
- p为g的右孩子,cur为p的右孩子,则进行左单旋转
- p、g变色--p变黑,g变红
3.u不存在/u为黑
并不是只有一边高,而是折线。
解决方法:
- p为g的左孩子,cur为p的右孩子,则针对p进行左右双旋
- p为g的右孩子,cur为p的左孩子,则针对p进行右左双旋
- cur、g变色--cur变黑,g变红
看图可以知道双旋的本质: cur的左右分给p和g,再让p和g做cur的左右。
//下面因为情况一和位置无关但二有关,所以写到一起也没啥事
while (parent && parent->_col == Red)
{
Node* grandfather = parent->_parent;
//情况一
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == Red)
{
uncle->_col = parent->_col = Black;
grandfather->_col = Red;
//向上再处理
cur = grandfather;
parent = cur->_parent; // 此时如果parent不存在,就证明g就是根,while就不会进去,在下面会把根改成黑色。
}
//情况二
else
{
//情况二,单旋
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = Black;
grandfather->_col = Red;
}
//情况三,双旋
else if (cur == parent->_right)
{
RotateL(parent);
RotateR(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
break;
}
}
else
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == Red)
{
uncle->_col = parent->_col = Black;
grandfather->_col = Red;
//向上再处理
cur = grandfather;
parent = cur->_parent; // 此时如果parent不存在,就证明g就是根,while就不会进去,在下面会把根改成黑色。
}
else
{
//情况二,单旋
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = Black;
grandfather->_col = Red;
}
//情况三,双旋
else if (cur == parent->_left)
{
RotateR(parent);
RotateL(grandfather);
cur->_col = Black;
grandfather->_col = Red;
}
break;
}
}
}
_root->_col = Black;
return true;
3.检验
思路:
- 根节点不能为红色
- 不能有连续的红色节点
- 每条路径黑色节点的数量要相同
前两天很容易判断,第三条:
先记录一条路径(最左)的黑色节点数目作为标准,再递归走左子树右子树时,记录一下黑节点的数量,等递归到某一条路径为空时,黑节点记录完和标准值比较,如果不一样,就不是红黑树。
bool Check(Node* cur,int refBlacknum,int Blacknum)
{
if (cur == nullptr)
{
if (refBlacknum != Blacknum)
{
return false;
}
return true;
}
else if (cur && cur->_col == Red)//根为红
{
return false;
}
if (cur->_col == Black)
{
Blacknum++;
}
return Check(cur->_left,refBlacknum,Blacknum) && Check(cur->_right, refBlacknum, Blacknum);
}
bool IsBalance()
{
//连续的红节点
if (_root == Red && _root->_parent == Red)
{
return false;
}
int refBlacknum = 0;
Node* subL = _root;
while (subL && subL->_left)
{
if(subL->_col == Black)
refBlacknum++;
subL = subL->_left;
}
return Check(_root,refBlacknum,0);
}
【有道云笔记】红黑树
https://note.youdao.com/s/MLWAvCau