引入
最开始我们学习了搜索二叉树,但是最后我们发现搜索二叉树有缺陷,之后我们又引入了AVL树。AVL树是一种高度平衡的二叉搜索树,能够满足增删查都是O(logN)的时间复杂度。既然AVL树已经满足了我们的期望,那么为什么还要引入红黑树呢?
这是由于AVL树是高度平衡的二叉搜索树,维持一颗AVL树的代价相比于红黑树来说是非常大的,由于它的高度平衡,使得它几乎每次插入或者删除都需要调整树。而红黑树是一种近似平衡的二叉搜索树,它满足最长路径不超过最短路径的两倍,而且它易于维护,在插入的时候也不是每次都需要调整树。
虽然红黑树整体性能上较AVL树能差一点,但是也不是差的太多,而且红黑树胜在易于维护,所以折中下来红黑树还是用的比较多的。
概念
红黑树是一颗二叉搜索树,他在没一个节点上增加了一个存储位来表示节点的颜色,可以是红色或者黑色,通过任何一条从根到叶子简单路径上的颜色来约束,红黑树保证最长路径不超过最短路径的两倍,因而近似于平衡。
性质:
(1)每个节点不是红色就是黑色的
(2)根节点是黑色的
(3)如果一个节点是红色的,则它的两个孩子是黑色的
(4)对于每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点
(5)每个叶子节点都是黑色的(此处的叶子节点指的是空节点)
插入操作
一般情况下,我么将要插入的结点都默认设置成红色的。
(1)如果插入的结点是根节点,则直接插入,并且将根节点染成黑色。
(2)如果要插入的位置的父亲是黑色的,那么直接插入。
(3)要插入的位置的父亲是红色的,这时再插入一个红色结点就会出现两个红色结点连续的情况,所以这时候我们就要调整树来重新恢复平衡。假设要插入的结点是cur,它的父亲是parent,它父亲的兄弟是uncle,它的祖父是grand。
注:(下面只演示parent为grandfather左孩子的情况)
情景一:父节点parent为红,叔叔节点uncle存在且为红。
情景二:父节点parent为红,叔叔节点uncle存在且为黑。
(1)插入节点是parent的左孩子。
(2)插入节点是parent的右孩子。
代码实现:
pair<Node*,bool> Insert(const K& key, const V& value)
{
if (_root == NULL)//若根节点为空直接插入并将其置为黑色
{
_root = new Node(key, value);
_root->_col = BLACK;
return make_pair(_root,true);
}
Node* cur = _root;
Node* parent = NULL;
//找到插入节点的位置
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return make_pair(cur, false);
}
}
//判断将节点插入父节点的左边还是右边
cur = new Node(key, value);
Node* newNode = cur;
if (key < parent->_key)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//循环判断是否需要调整
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
//一,父节点为祖父节点的左孩子
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
//1.叔叔存在且为红,直接变色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;//上调
parent = cur->_parent;
}
else //叔叔不存在/叔叔存在且为黑
{
//2.cur在父节点左边进行单旋
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//3.cur在父节点右边进行双旋
else if(cur == parent->_right)
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
//二,父节点为祖父节点的右孩子
else
{
Node* uncle = grandfather->_left;
//1.叔叔存在且为红,变色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else //叔叔不存在/叔叔存在且为黑
{
//2.进行单旋
if (cur == parent->_right)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
//3.进行双旋
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;//将根节点置为黑色
return make_pair(newNode, true);
}
判断是否为红黑树
由红黑树的性质可以总结如下判别条件:
(1)根节点是否为黑色
bool IsBalance()
{
//1.空树属于红黑树
if (_root == NULL)
return true;
//2.根节点不为黑
if (_root->_col != BLACK)
{
return false;
}
//3.统计出一条路径上黑色节点的数量
size_t k = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
k++;
cur = cur->_left;
}
return CheckColor(_root) && CheckBlackNum(_root, k, 0);
}
(2)每条路径上的黑色节点数是否相同
bool CheckBlackNum(Node* root, const size_t &k, size_t num)
{
//空树,满足
if (root == NULL)
{
return true;
}
if (root->_col == BLACK)
{
++num;
}
//叶子节点,判断黑色节点数量
if (root->_left == NULL && root->_right == NULL && num != k)
{
cout<<"路径上的黑色节点数不相同"<<endl;
return false;
}
return CheckBlackNum(root->_left, k, num)
&& CheckBlackNum(root->_right, k, num);
}
(3)每一个红节点,其父亲节点是否为黑节点
bool CheckColor(Node* root)
{
//1.空树,满足
if (root == NULL)
{
return true;
}
//2.判断父亲,是否是连续的红色
if (root->_col == RED && root->_parent->_col == RED)
{
cout<<"存在连续的红节点"<<endl;
return false;
}
return CheckColor(root->_left) && CheckColor(root->_right);
}
RBT与AVL
(1)红黑树相对AVL树来说,它不追求完全平衡,在他们的增删查改的时间复杂度都为O(lg(N))的情况下,降低了插入节点后的旋转次数;
(2)红黑树没有AVL树的平衡因子的判断,实现起来简单了许多
(3)而AVL树由于本身追求完全平衡的苛刻性,旋转次数多,旋转因子的判断较为麻烦