一、概念
红黑树,是一种二叉搜索树,但在每个节点增加一个存储位表示节点颜色,可以是Red或者是Black,通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径是另一条路径的两倍,因而是接近平衡的,没有AVL树那么严格。
二、性质
1、每个节点不是红色就是黑色。
2、根节点是黑色的。
3、如果一个节点的颜色是红色的,那么它的两个孩子节点颜色是黑色的。
4、从根节点到所有叶子节点的路径中黑色节点的数量是相同的(此处的叶子节点是空节点)。
三、红黑树节点的定义
从红黑树的性质中可以看出,一条路径中,最短的情况就是全是黑色节点,最长的情况就是一红一黑。
另外由于红黑树的第四条性质,告诉我们,所有根节点到叶子节点的路径中黑色节点数量相同得知,如果要插入一个新节点,那么默认情况下是插入红色节点,因为如果默认插入的是黑色的,那么有一边的黑色节点一定比另一边的数量多。
接下来,我们来定义红黑树的节点:
template<class T>
struct RBTreeNode
{
//typedef RBTreeNode<K,V> Node;
RBTreeNode(const T& data = T())
:_pParent(nullptr)
,_pLeft(nullptr)
,_pRight(nullptr)
,_data(data)
,_color(RED)
{}
RBTreeNode<T>* _pParent;
RBTreeNode<T>* _pLeft;
RBTreeNode<T>* _pRight;
T _data;
Col _color;
};
四、红黑树的插入
插入节点前面还是和AVL树一样,但是要注意的是插入完成之后,要考虑到红黑树的性质,比如不能有两个连续的红色的节点,所以插入之后需要判断是否要做出调整:
我们默认插入的节点颜色是红色的,那么就要判断新插入的这个节点的父节点是不是红色的,是红色的就要进行调整。
调整的过程中先判断祖父节点除了父节点还有没有其他节点,即看父节点有没有兄弟节点,如果有并且为红,那就把父节点和它的兄弟节点都变黑,祖父节点变红,再接着往上调整,如果没有或者兄弟节点颜色为黑,那么就需要根据不同的情况进行旋转并改变颜色。
bool Insert(const T& data)
{
Node* root = GetRoot();
if (root == nullptr)
{
Node* tmp = new Node(data);
tmp->_color = BLACK;
tmp->_pParent = _pHead;
_pHead->_pParent = tmp;
}
else
{
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data > data)
{
parent = cur;
cur = cur->_pLeft;
}
else if (cur->_data < data)
{
parent = cur;
cur = cur->_pRight;
}
else
{
return false;
}
}
cur = new Node(data);
if (parent->_data > data)
parent->_pLeft = cur;
else
parent->_pRight = cur;
cur->_pParent = parent;
while (parent && parent != _pHead && parent->_color == RED)//要注意一个条件:parent != _pHead才走进来
{
Node* greadfather = parent->_pParent;
if (greadfather->_pLeft == parent)
{
Node* uncle = greadfather->_pRight;
if (uncle && uncle->_color == RED)//uncle存在且为红
{
parent->_color = BLACK;
uncle->_color = BLACK;
greadfather->_color = RED;
cur = greadfather;
parent = cur->_pParent;
}
else
{
//uncle不存在or存在且为黑
if (cur == parent->_pLeft)
{
Node* last = greadfather->_pParent;
int flag = 0;//0表示greadfather在last的左边,再判断是否在右,在右就更新
if (last->_pRight == greadfather)
flag = 1;
RotateR(greadfather);
greadfather->_color = RED;
parent->_color = BLACK;
//更新根节点与头节点的链接
if (last == _pHead)
{
_pHead->_pParent = parent;
parent->_pParent = _pHead;
}
else if (flag == 0)
{
last->_pLeft = parent;
parent->_pParent = _pHead;
}
else
{
last->_pRight = parent;
parent->_pParent = _pHead;
}
}
else
{
Node* last = greadfather->_pParent;
int flag = 0;//0表示greadfather在last的左边,再判断是否在右,在右就更新
if (last->_pRight == greadfather)
flag = 1;
RotateL(parent);
RotateR(greadfather);
cur->_color = BLACK;
greadfather->_color = RED;
//更新根节点与头节点的链接
if (last == _pHead)
{
_pHead->_pParent = cur;
cur->_pParent = _pHead;
}
else if (flag == 0)
{
last->_pLeft = cur;
cur->_pParent = _pHead;
}
else
{
last->_pRight = cur;
cur->_pParent = _pHead;
}
}
break;
}
}
else
{
Node* uncle = greadfather->_pLeft;
if (uncle && uncle->_color == RED)//uncle存在且为红
{
parent->_color = BLACK;
uncle->_color = BLACK;
greadfather->_color = RED;
cur = greadfather;
parent = cur->_pParent;
}
else
{
//uncle不存在or存在且为黑
if (cur == parent->_pLeft)
{
Node* last = greadfather->_pParent;
int flag = 0;//0表示greadfather在last的左边,再判断是否在右,在右就更新
if (last->_pRight == greadfather)
flag = 1;
RotateR(parent);
RotateL(greadfather);
cur->_color = BLACK;
greadfather->_color = RED;
//更新根节点与头节点的链接
if (last == _pHead)
{
_pHead->_pParent = cur;
cur->_pParent = _pHead;
}
else if (flag == 0)
{
last->_pLeft = cur;
cur->_pParent = _pHead;
}
else
{
last->_pRight = cur;
cur->_pParent = _pHead;
}
}
else
{
Node* last = greadfather->_pParent;
int flag = 0;//0表示greadfather在last的左边,再判断是否在右,在右就更新
if (last->_pRight == greadfather)
flag = 1;
RotateL(greadfather);
greadfather->_color = RED;
parent->_color = BLACK;
//更新根节点与头节点的链接
if (last == _pHead)
{
_pHead->_pParent = parent;
parent->_pParent = _pHead;
}
else if (flag == 0)
{
last->_pLeft = parent;
parent->_pParent = _pHead;
}
else
{
last->_pRight = parent;
parent->_pParent = _pHead;
}
}
break;
}
}
}
_pHead->_pParent->_color = BLACK;
}
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
这里的红黑树是先定义一个头节点,这个头节点的父节点指向根节点,根节点的父亲也指向头节点,头节点的左右两边分别指向这棵红黑树的最小值和最大值。
另外把上面代码中其他接口也贴出来:
Node* LeftMost()
{
Node* cur = _pHead->_pParent;
while (cur && cur->_pLeft)
{
cur = cur->_pLeft;
}
return cur;
}
// 获取红黑树最右侧节点
Node* RightMost()
{
Node* cur = _pHead->_pParent;
while (cur && cur->_pRight)
{
cur = cur->_pRight;
}
return cur;
}
void RotateL(Node* pParent)
{
Node* SubR = pParent->_pRight;
Node* SubRL = SubR->_pLeft;
pParent->_pRight = SubRL;
if (SubRL)
SubRL->_pParent = pParent;
Node* parent = pParent->_pParent;
SubR->_pLeft = pParent;
pParent->_pParent = SubR;
if (parent == nullptr)
{
_pHead = SubR;
SubR->_pParent = nullptr;
}
else
{
if (parent->_pLeft == pParent)
{
parent->_pLeft = SubR;
}
else
{
parent->_pRight = SubR;
}
SubR->_pParent = parent;
}
}
// 右单旋
void RotateR(Node* pParent)
{
Node* SubL = pParent->_pLeft;
Node* SubLR = SubL->_pRight;
pParent->_pLeft = SubLR;
if (SubLR)
SubLR->_pParent = pParent;
Node* parent = pParent->_pParent;
SubL->_pRight = pParent;
pParent->_pParent = SubL;
if (parent == nullptr)
{
_pHead = SubL;
SubL->_pParent = nullptr;
}
else
{
if (parent->_pLeft == pParent)
{
parent->_pLeft = SubL;
}
else
{
parent->_pRight = SubL;
}
SubL->_pParent = parent;
}
}
// 为了操作树简单起见:获取根节点
Node*& GetRoot()
{
return _pHead->_pParent;
}
五、红黑树的验证
我们可以根据每条路径的黑色节点数量相同这一性质进行验证,先算出最左边路径有多少黑节点:
bool IsValidRBTRee()
{
Node* root = GetRoot();
int benchmark = 0;
Node* cur = root;
while (cur)
{
if (cur->_color == BLACK)
++benchmark;
cur = cur->_pLeft;
}
return _IsValidRBTRee(root, 0, benchmark);
}
接下来再是判断其他路径的黑色节点数量是否和最左边路径数量相同,另外还判断下是否有连续的红色节点:
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
if (pRoot == nullptr)
{
if (blackCount != pathBlack)
{
cout << "路径中黑色节点数量不相等" << endl;
return false;
}
else
return true;
}
if (pRoot->_color == BLACK)
++blackCount;
if (pRoot->_color == RED && pRoot->_pParent && pRoot->_pParent->_color == RED)
{
cout << "有连续的红色节点" << endl;
return false;
}
return _IsValidRBTRee(pRoot->_pLeft, blackCount, pathBlack) && _IsValidRBTRee(pRoot->_pRight, blackCount, pathBlack);
}