一、红黑树的概念
红黑树,是一种二叉搜索树,红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树和AVL树的区别:
红黑树:最长路径不超过最短路径的两倍(近似平衡)
AVL树:左右子树高度差不大于一(严格平衡)
相对而言,插入同样的数据,AVL树旋转的更多,红黑树则旋转的少。
二、红黑树的性质
1.每个节点不是红色就是黑色
2.根节点是黑色的
3.如果一个节点是红色的,那么它的两个孩子节点是黑色的(树中没有连续的红色节点)
4.对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。(每条路径的黑色节点数量相等)
5.每个叶子节点(又称为NIL节点)都是黑色的(此处叶子节点指的是空节点)
三、红黑树的实现
红黑树节点的实现
//红黑树节点的实现
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)//初始化为红色,因为每次插入都是默认为红色插入的。
{}
};
插入的过程和AVL树一样都是三叉链插入, 所以后续会用到旋转 ,但是对于红黑树来说它还需要变色:插入的节点默认为红色的,如果插入的节点的父亲为黑色,那么插入没问题,如果插入的节点的父亲为红色 那就和上面红黑树的性质冲突,此时就需要变色了
红黑树的插入操作
cur -->当前位置节点
parent-->当前位置的父节点
grandparent-->parent的父节点
uncle -->parent的兄弟节点
情况1:cur为红,parent为红,grandparent为黑,uncle存在且为红。
a/b/c/d/e 为符合红黑树规则的子树 ,若a/b/c/d/e为空树 ,则cur为新增
处理:parent和uncle变黑,grand变红,继续向上处理,grandparent若为根则再变为黑。
注意:只变色处理不考虑左右插入。
情况二:cur为红,parent为红,grandparent为黑,uncle存在且为黑/uncle不存在
uncle 不存在的情况就是abcde为空 cur为新增
处理:把p变黑,g变红,右旋
单旋+变色
情况三:cur为红,p为红,g为黑,u存在且为黑/u不存在,和情况二样,p为g的左孩子,cur为p的右孩子,则针对p左旋。变为上面的情况。
总结:看uncle ,uncle为红色就变色,继续往上更新; uncle不存在或者为黑色,就需要变色并旋转:
cur的位置 \旋转+变色 | ||
cur在左左 | 右旋 | p->黑 u->黑 g->红 |
左右 | 先p左旋 再右旋 | cur->黑 g->红 |
右右 | 左旋 | p->黑 u->黑 g->红 |
右左 | 先p右旋 再左旋 | c->黑 g->红 |
代码实现:
template <class K,class V>
class RBTree
{
typedef RBTreeNode<K,V> Node;
typedef RBTreeNode<K,V>* PNode;
public:
RBTree()
:_root(nullptr)
{}
bool Insert(pair<K, V>& kv)
{
// 1、搜索树的规则插入
// 2、看是否违反平衡规则,如果违反就需要处理:旋转
//判断是否为空树 是空树直接插入黑色节点作为根节点
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
PNode parent = nullptr;//记录下父节点以便后续插入链接
PNode cur = _root;//当前位置--即插入位置节点
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);//插入
cur->_col = RED;//插入节点默认为红色
//判断插入节点是父亲的左还是右儿子 再和父节点链接
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
// 存在连续红色节点 此时与红黑树的性质三相悖 需要处理
while (parent && parent->_col == RED)
{
PNode grandfater = parent->_parent;//grandparent节点
assert(grandfater);//防御
if (grandfater->_left == parent)
{
PNode uncle = grandfater->_right;//找出uncle节点 再根据uncle的状态来分情况讨论
// 情况一:
if (uncle && uncle->_col == RED) // 叔叔存在且为红 此时变色即可解决不需要旋转 变色处理不考虑左右
{
// 变色
parent->_col = uncle->_col = BLACK;//parent和uncle变黑 g变红
grandfater->_col = RED;
// 继续往上处理 直到根节点为止
cur = grandfater;
parent = cur->_parent;
}
else // 叔叔不存在 或者 叔叔存在且为黑
{
if (cur == parent->_left) // 左左 -- p变黑 g变红 并且右单旋
{
RotateR(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
else // 左右 -- p左旋 g右旋并且变色
{
RotateL(parent);
RotateR(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}
break;
}
}
else //(grandfater->_right == parent)
{
Node* uncle = grandfater->_left;
// 情况一:
if (uncle && uncle->_col == RED)
{
// 变色
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
// 继续往上处理
cur = grandfater;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)//右右 左单选并变色 :p变黑 g变红
{
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
else // 双旋 右左 -- p右旋 g左旋 并且 变色
{
RotateR(parent);
RotateL(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;//记住根节点要变为黑色 符合性质1
return true;
}
private:
PNode _root;
};
其中的左右旋转 和AVL树中的左右旋转是一样的 我们再温故一波:
void RotateR(PNode parent)
{
PNode subL = parent->_left;
PNode subLR = subL->_right;
PNode ppNode = parent->_parent;//注意旋转完成之后新的根节点要和原来根节点的父节点链接
parent->_left = subLR;
subL->_right = parent;
parent->_parent = subL;
if (subLR)
{
subLR->_parent = parent;
}
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
}
void RotateL(PNode parent)
{
PNode subR = parent->_right;
PNode subRL = subR->_left;
PNode ppNode = parent->_parent;
parent->_right = subRL;
subR->_left = parent;
parent->_parent = subR;
if (subRL)
{
subRL->_parent = parent;
}
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
}