概念
红黑树是一种二叉搜索树,在每个节点都上都增加一个字段表示颜色,红色或者黑色,通过一些条件的限制,达到最长路径中节点个数不会超过最短路径中节点个数的两倍。红黑树可以是空树。
因此红黑树是一颗近似平衡的二叉搜索树
性质:
1.红黑树中的节点不是红色就是黑色。
2.根节点一定是黑色。
3.如果一个节点是红色,那么它的两个孩子节点是黑色。
4.对于每个节点,从该节点到其后代所有节点的简单路径中,均包含相同数目的黑色节点。
5.每个叶子节点都是黑色的。
通过以上性质的约束,最终可以达到最长路径中节点个数不会超过最短路径中节点个数的两倍。
红黑树的结构:
红黑树中,第一个节点是head 头结点,它的值域没有意义,颜色是红色,head中的parent指向红黑树的根节点root,root的parent指向头结点head,head的left指向红黑树中最左侧的节点,也是最小的节点,head中的right指向红黑树中最右侧的节点也是最大的节点。
关于设置head的原因,在通过迭代器遍历整个树的时候,根据二叉搜索树的性质,begin()指向的就是红黑树中最小的节点,是中序遍历的第一个元素,end()指向的就是头结点head,是中序遍历最后一个元素的后一个位置,如此安排满足迭代器的使用规则,end()–刚好就能拿到最右侧的节点。
实现
树的节点的构造,
增加了一个枚举类型的color用来标记颜色,新创建的节点默认是红色的。因为如果新节点的默认颜色设置为黑色,那么每次插入新节点必回导致从根节点到此节点路径中的黑色节点个数增加了1,就会违反红黑树的性质中提到的第四条。
enum Color{RED,BLACK};
template<class T>
//节点的构造
struct BRTreeNode {
BRTreeNode* left;
BRTreeNode* right;
BRTreeNode* parent;
T data;
Color color;
BRTreeNode(const T& val = T(),Color c = RED)
:left(nullptr)
,right(nullptr)
,parent(nullptr)
,data(val)
,color(c)
{}
};
插入
红黑树是一种二叉搜索树,其插入规则也一致,如果是空树,插入进去的就是根节点,只需要改变head中指针域的指向,和新插入节点的parent指向。
如果不是空树,根据二叉搜索树的性质找待插入节点在树中的位置,然后插入节点。
节点插入后,判断有没有破坏红黑树的性质,根据性质的要求进行调整。
首先,新插入的节点是红色,如果新插入的节点的双亲节点是黑色,那不会有影响,
如果双亲节点的颜色是红色,那就违反了红黑树的性质,需要调整,需要调整就有一些复杂情况,需要具体分析。
1.cur的叔叔节点uncle存在且为红色,双亲节点parent为红色,祖父节点grandfather为黑色。此时不满足红黑数的性质。
如果要进行修改有如下方案,
方案一:将cur改成黑色,此时树中没有了连在一起的红色节点,但是从grandfather到parent到parent右这一路径中黑色节点的个数永远少了1个,所以方案一不成立。
方案二:将parent修改为黑色,此时没有了连在一起的红色节点,为了满足性质4的要求,uncle也要改为黑色。
此时性质3和性质4都满足了,但是只可以说以grandfather为根节点的这颗树是红黑树了。
但是这棵树可能只是一颗子树,假设这是一颗左子树,那么插入cur前树的结构又是如下的情况。
可以确定的是插入cur前,原来的树是满足红黑树的性质的,在图中,gp左子树中只有一个黑色节点,所以其未知结构的右子树只能有一个黑色节点,经过修改后,parent和uncle都修改为了黑色:
此时左子树中的每条路径上都多了一个黑色节点,这时候又违反了红黑树的性质。因此如果grandfather为根的这颗树只是子树,还需要继续调整。那么想办法让左子树路径上只有一个黑色节点,很显然将grandfather改为红色是唯一方案,
此时左右子树中黑色节点个数相同了。继续向上看,gp节点还没有确定,它有两种情况如果是黑色,
看起来已经满足了红黑树的性质。但是如果是红色。
则又出现了两个相连的红色节点的情况,那还需要继续向上调整,将cur更新到此时grandfather的位置,将parent更新到gp的位置,可以看到,情况就有点类似于之前刚插入cur时的树的结构,cur和parent是连个连在一起的红色节点,还需要进行调整。
总结一下这种情况:调整方式为,将其父亲节点和叔叔节点改为黑色,将其祖父节点改为红色,将cur更新为原来grandfather的位置,再根据情况进行调整。
情况2 :cur为红,其父亲节点parent为红,祖父节点grandfather为黑,叔叔节点uncle不存在或者为黑。
叔叔节点不存在:
那么树的结构应该是如下的样子,parent的子树也不存在,假如存在的话那不管是红色还是黑色都会违反红黑树的性质。所以cur一定是新插入的节点不是从下面调整上来的。
叔叔节点存在且为黑色:那cur之前一定是黑色,是通过向上调整改为的红色,因为grandfather到右子树中至少有两个黑色节点,而左边parent是红色,从grandfather到parent再到下面的路径中一定要有黑色节点,所以方框代表的子树存在,
修改前,
这种情况的处理方案分析:首先cur和parent中一定要有一个改成黑色,经过分析,cur之前本来就是黑色,改为红色后,以cur为根的二叉树才满足红黑树的性质,进而向上调整。而如果改动parent,将parent改成黑色,则整个左子树中的黑色节点必多了一个,为了平衡如果将grandfather改为红色,那右子树中的黑色节点又少了一个,成为如下情况。
正确方法是将这颗树进行右单旋,满足红黑树性质。
所以第二种情况的处理方法,将父亲节点和祖父节点的颜色互换,然后对整体进行右单旋。
情况3:cur为红,parent为红,grandfather为黑,uncle不存在或为黑。
将这颗树进行左单旋,将cur和parent的指向交换一下,发现就变成了情况2的条件,当然可以根据情况2的处理方式进行调整。
这些情况的相同点,都是parent是grandfather的左孩子,而如果parent是grandfather的右孩子时,对于情况1,处理方法相同,对于情况2,parent是grandfather的左孩子时,进行的是右单旋,如果parent是grandfather的右孩子,实际进行左单旋即可。对于情况3,图示中parent是grandfather的左孩子,cur是parent的右孩子,处理上是对parent为根的子树进行左单旋,转换为情况2来处理。相反如果parent是grandfather的右孩子,cur是parent的左孩子,则对以parent为根的子树进行右单旋再处理即可。
#pragma once
#include<iostream>
using namespace std;
enum Color { RED, BLACK };
template<class T>
struct RBTreeNode{
RBTreeNode<T>* left;
RBTreeNode<T>* right;
RBTreeNode<T>* parent;
T data;
Color color;
RBTreeNode(const T& val = T(), Color c = RED)
: left(nullptr)
, right(nullptr)
, parent(nullptr)
, data(val)
, color(c)
{}
};
template<class T>
class RBTree{
typedef RBTreeNode<T> Node;
public:
RBTree(){
head = new Node;
head->left = head;
head->right = head;
head->parent = nullptr;
}
bool Insert(const T& data){
// 空树
Node*& root = GetRoot();
if (nullptr == root){
root = new Node(data, BLACK);
root->parent = head;
head->left = root;
head->right = root;
return true;
}
// 非空
Node* cur = root;
Node* parent = head;
while (cur) {
parent = cur;
if (data < cur->data) {
cur = cur->left;
}
else if (data > cur->data) {
cur = cur->right;
}
else {
return false;
}
}
cur = new Node(data);
if (data < parent->data) {
parent->left = cur;
}
else {
parent->right = cur;
}
cur->parent = parent;
while (parent != head && RED == parent->color){
Node* granderFather = parent->parent;
if (parent == granderFather->left){
Node* unclue = granderFather->right;
if (unclue && RED == unclue->color){
parent->color = BLACK;
unclue->color = BLACK;
granderFather->color = RED;
cur = granderFather;
parent = cur->parent;
}
else{
if (cur == parent->right){
RotateLeft(parent);
swap(parent, cur);
}
parent->color = BLACK;
granderFather->color = RED;
RotateRight(granderFather);
}
}
else{
Node* unclue = granderFather->left;
if (unclue && RED == unclue->color){
parent->color = BLACK;
unclue->color = BLACK;
granderFather->color = RED;
cur = granderFather;
parent = cur;
}
else{
if (cur == parent->left){
RotateRight(parent);
swap(parent, cur);
}
parent->color = BLACK;
granderFather->color = RED;
RotateLeft(granderFather);
}
}
}
root->color = BLACK;
head->left = MostLeft();
head->right = MostRight();
return true;
}
Node*& GetRoot(){
return head->parent;
}
Node* MostLeft(){
Node* cur = GetRoot();
if (nullptr == cur) {
return head;
}
while (cur->left){
cur = cur->left;
}
return cur;
}
Node* MostRight(){
Node* cur = GetRoot();
if (nullptr == cur) {
return head;
}
while (cur->right){
cur = cur->right;
}
return cur;
}
void InOrder(){
InOrder(GetRoot());
}
private:
void InOrder(Node* root){
if (root){
InOrder(root->left);
cout << root->data << " ";
InOrder(root->right);
}
}
void RotateLeft(Node* parent){
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
if (subRL) {
subRL->parent = parent;
}
subR->left = parent;
Node* pparent = parent->parent;
parent->parent = subR;
subR->parent = pparent;
if (pparent == head){
head->parent = subR;
}
else {
if (parent == pparent->left) {
pparent->left = subR;
}
else {
pparent->right = subR;
}
}
}
void RotateRight(Node* parent){
Node* subL = parent->left;
Node* subLR = subL->right;
parent->left = subLR;
if (subLR) {
subLR->parent = parent;
}
subL->right = parent;
Node* pparent = parent->parent;
parent->parent = subL;
subL->parent = pparent;
if (pparent == head){
head->parent = subL;
}
else {
if (parent == pparent->left) {
pparent->left = subL;
}
else {
pparent->right = subL;
}
}
}
private:
Node* head;
};