红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储为表示结点的颜色,可以使Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
红黑树的性质
1.每个结点不是红色就是黑色
2.根节点是黑色的
3.如果一个节点是红色的,则它的两个孩子结点是黑色的
4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
假如最短路径上的结点颜色全部为黑色,如果要形成一条最长路径,根据第3,4条规定则最长路径结点是红黑交替,且黑色结点和最短路径黑色结点数量相同,所以最长路径中节点个数不会超过最短路径节点个数的两倍。
红黑树节点的定义
enum { BLACK, RED };
template<typename K, typename V>
struct RBTreeNode
{
int _color;
K _key;
V _value;
RBTreeNode<K, V> *_left;
RBTreeNode<K, V> *_right;
RBTreeNode<K, V> *_parent;
RBTreeNode(K key, V value)
:_color(RED) //默认节点是红色
, _key(key)
, _value(value)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
{}
};
为什么默认节点是红色?
因为插入之前所有根至外部节点的路径上黑色节点数目都相同,所以如果插入的节点是黑色肯定错误(黑色节点数目不相同),而相对的插入红节点可能不会违反“没有两个连续两个节点是红色”这一条件,所以插入的节点为红色,如果违反条件再调整。
红黑树的旋转
红黑树的左旋:
动态图:
旋转过程:
以y为中心,将y的父节点x,左边兄弟节点α逆时针旋转,
原来的y左节点β被y的父节点替代,
原来y的左子节点β逆时针方向平移后变成y的原父节点x的右子节点。
void RotateL(Node* parent) //左旋
{
Node* ppNode = parent->_parent;
Node* subR = parent->_right;
parent->_right = subR->_left;
if (subR->_left)
subR->_left->_parent = parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == NULL)
{
_root = subR;
_root->_parent = NULL;
}
else
{ //与上层进行链接
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
}
红黑树的右旋:
动态图:
右旋过程:
将x的父节点y,右边兄弟节点λ,还有右子节点β顺时针旋转。
原来的x右节点β被x的父节点替代,
原来的x的右子节点β顺时针方向平移后变成x的原父节点y的左子节点。
void RotateR(Node* parent) //右旋
{
Node* ppNode = parent->_parent;
Node* subL = parent->_left;
parent->_left = subL->_right;
if (subL->_right)
subL->_right->_parent = parent;
subL->_right = parent;
parent->_parent = subL;
if (ppNode == NULL)
{
_root = subL;
_root->_parent = NULL;
}
else //与上层结点链接
{
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
}
红黑树的插入
新节点默认颜色为红色
1.如果插入的节点是根节点,则直接插入,并且将根节点染成黑色。
2.如果要插入的位置的父亲节点是黑色的,那么直接插入。
3.要插入的位置的父亲是红色的,这时候再插入一个红色节点就会出现两个红色节点梁旭的情况,所以要进行调整。假设要插入的节点是cur,它的父亲是parent,它父亲的兄弟是uncle,它的祖父是grand。
情况一:p为红,cur为红,g为黑,u存在且为红
因为cur和p均为红,违反了性质3,所以需要调整。
解决方法,将p,cur改为黑,g改为红,然后把g当成cur,继续向上调整。
情况二:cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反, p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红
情况三:cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反, p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
bool Insert(const K& key, const V& value)
{
if (_root == NULL) //如果插入的结点是根节点
{
_root = new Node(key, value);
_root->_color = BLACK;
return true;
}
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
return false; //要插入的结点已经存在
}
cur = new Node(key, value);
if (parent->_key>key)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent;
while (cur != _root&&parent->_color == RED) //父节点是红色的需要调整
{
Node* grand = parent->_parent; //找到祖父结点
if (parent == grand->_left)
{
Node* uncle = grand->_right; //找到叔叔结点
if (uncle&&uncle->_color == RED) //叔叔结点是红色
{
grand->_color = RED;
parent->_color = BLACK;
uncle->_color = BLACK;
cur = grand;
parent = cur->_parent; //红色结点上移,需要继续判断
}
else //叔叔结点不存在或为黑色结点
{
//先处理双旋的情况
if (cur == parent->_right) //如果cur是父亲的右孩子
{
RotateL(parent); //先对parent进行左旋
parent = cur;
}
//如果cur是parent的右孩子,则经过旋转之后现在就变成了以grand右旋的情况
RotateR(grand); //对祖父结点进行右旋
parent->_color = BLACK;
grand->_color = RED;
break; //这时候就已经平衡了
}
}
else
{
Node* uncle = grand->_left;
if (uncle&&uncle->_color == RED) //如果叔叔存在且为红色
{
parent->_color = BLACK;
uncle->_color = BLACK;
grand->_color = RED; //红色结点上移,继续向上判断
cur = grand;
parent = cur->_parent;
}
else
{
//如果cur是parent的左孩子,则需要先进行右旋将双旋转换成左旋的情况
if (cur == parent->_left)
{
RotateR(parent);
parent = cur;
}
//在对祖父进行左旋
RotateL(grand);
parent->_color = BLACK;
grand->_color = RED;
break;
}
}
}
_root->_color = BLACK; //把根节点置成黑色
return true;
}
红黑树的验证
1.检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2.检测其是否满足红黑树的性质
bool IsValidRBTree()
{
Node* pRoot = GetRoot();
// 空树也是红黑树
if (nullptr == pRoot)
return true;
```// 检测根节点是否满足情况
if (BLACK != pRoot->_color)
{
cout << "违反红黑树性质二:根节点必须为黑色" << endl;
return false;
}
// 获取任意一条路径中黑色节点的个数
size_t blackCount = 0;
Node* pCur = pRoot;
while (pCur)
{
if (BLACK == pCur->_color)
blackCount++;
pCur = pCur->_left;
}
// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
size_t k = 0;
return _IsValidRBTree(pRoot, k, blackCount);
}
bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount)
{
if (nullptr == pRoot)
return true;
// 统计黑色节点的个数
if (BLACK == pRoot->_color)
k++;
// 检测当前节点与其双亲是否都为红色
Node* pParent = pRoot->_pParent;
if (pParent && RED == pParent->_color && RED == pRoot->_color)
{
cout << "违反性质三:没有连在一起的红色节点" << endl;
return false;
}
// 如果pRoot是因子节点,检测当前路径中黑色节点的个数是否有问题
if (nullptr == pRoot->_left && nullptr == pRoot->_rght)
{
if (k != blackCount)
{
cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
return false;
}
}
return _IsValidRBTree(pRoot->_left, k, blackCount) &&
_IsValidRBTree(pRoot->_right, k, blackCount);
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( ),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。