目录
1、红黑树的概念
2、红黑树的性质
3、红黑树结点的定义
4、红黑树的插入
4.1 特殊情况
4.2 叔叔结点是红色
4.3 叔叔结点不存在或是黑色
5、红黑树的验证
6、红黑树与AVL树比较
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
其中的NIL是空结点
为了控制红黑树最长路径的长度不会超过最短路径长度的两倍,给红黑树设置了5条性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
一条路径结束是要以空结点为判定标准,像上面这个图,就有11条路径
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
因为一颗红黑树中每条路径上黑色结点的数量是相等的,假设一颗红黑树一条路径上有n个黑色结点,所以路径最短的情况就是这条路径全是黑色结点,此时这条路径上结点个数是n,路径最长的情况是这条路径上每一个黑色结点中间都有一个红色结点,此时这条路径上结点个数是2*n,满足最长路径中节点个数不会超过最短路径节点个数的两倍
因为红黑树是控制最长路径中节点个数不会超过最短路径节点个数的两倍,所以红黑树是一种近似平衡,而AVL树是控制左右子树的高度差不超过1,是一种绝对平衡
与AVL树相比,红黑树不再需要平衡因子,因为红黑树控制平衡是利用上面5条性质对颜色进行限制来控制平衡,所以红黑树的结点需要存储这个结点是什么颜色,这里使用的是枚举变量来表示颜色。在红黑树的插入和删除操作中,需要用到结点的父亲、叔叔、爷爷,所以也需要使用三叉链结构
enum Colour
{
RED,
BLACK
};
template
struct RBTreeNode
{
pair _kv;
// 三叉链
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Colour _col; // 结点颜色
RBTreeNode(const pair& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
在上面的RBTreeNode类里面定义RBTreeNode类型的指针没有指定模板参数是可以的,因为在一个类的内部定义这个类类型的对象、指针都是可以不加实参的
思考:创建结点时,为什么默认将结点设置成红色呢?
因为红黑树每条路径上的黑色结点个数需要相同,若是默认设置成黑色,则插入新结点的这条路径会多出一个黑色结点,此时需要整体变色,而若是插入红色结点,若其父亲结点是黑色,则不需要任何操作,若其父亲结点是红色,则只需要进行局部的改变
像红黑树中插入一个结点时,规则仍然是与二叉搜索树是相同的,只是插入之后,需要检查一下现在的红黑树是否还是红黑树,若不是红黑树,则需要进行相应的修改。
先讨论一个特殊情况,若在这个结点插入进来之前,红黑树是空的,也就是说这个结点插入进来是要作为根结点的,那么这个结点需要变成黑色。
接下来是插入结点不作为根结点的情况。因为新插入的结点默认是设置成红色,所以若是其父亲是黑色,则此时的树仍然是红黑树,不需要进行任何修改
若其父亲是红色,则此时会有两个红色结点链接着,就不是红黑树,要进行修改(其父亲结点是红色,则其爷爷一定是黑色,因为在新结点插入进来之前,这颗树是红黑树,但是叔叔存不存在,是红色还是黑色,就是说不定的),此时要如何进行修改,就需要看叔叔结点的颜色
注意:父亲结点是红的,则一定有爷爷,因为根结点是黑色的
下面以cur来称呼新插入进来的这个结点
若叔叔结点是红色的,则此时需要将父亲结点和叔叔结点变成黑色,爷爷结点变成红色,让爷爷结点变成cur,继续向上面检查
让爷爷变红是为了保证这条路径上的黑色结点个数与其他路径相同,具体正不正确,还需要向上检查才能知道
在下面图中,cur为当前节点,p为父节点,g为爷爷节点,u为叔叔节点
当a、b、c、d、e为空时,则cur就是新插入进来的结点,当a、b、c、d、e不为空时,则此时就是继续向上检查时的情况。使用抽象图是为了表示所有情况
因为当叔叔结点不存在时,也就是空结点,而空结点也是黑色,所以叔叔结点不存在和是黑色是一样的
若出现这种情况,此时就需要旋转 + 变色了
如何旋转?需要看cur和g是什么关系,旋转过程与AVL树中的是一致的,像上面这幅图,cur和g是LL关系,需要进行右旋
如何变色?需要改变旋转点和旋转中心的颜色
单旋:修改父亲结点和爷爷结点的颜色,父亲结点变为黑色,爷爷结点变为红色
双旋:修改cur和爷爷结点的颜色,cur变为黑色,爷爷结点变为红色。
注意:叔叔结点存在且为黑,则这种情况一定是由叔叔结点是红色的情况继续向上检查来的。因为若cur是新增,则a、b、c、d、e均为空,叔叔那条路径会比父亲这条路径多一个黑色结点,是不合理的。当叔叔结点不存在时,则cur就是新增
当叔叔结点不存在或是黑色时,旋转 + 变色后不需要继续向上,因为此时这颗子树的根就是黑色结点,不会与上面形成两个红色结点的情况
bool Insert(const pair& kv)
{
// 若_root为空,直接让新插入的结点变成根
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点
Node* parent = nullptr;
Node* 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);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
// 因为现在是三叉链结果,还要链接一下父亲结点
cur->_parent = parent;
// 链接上去后要判断一下父亲颜色是否为红色,若是红色则需要处理
while (parent && parent->_col == RED)
{
// 若父亲是红色,则看叔叔是什么颜色
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 如果叔叔结点存在且为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
// 叔叔结点不存在或者是黑色
else
{
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; // 旋转 + 变色后就可以直接退出循环了
}
}
else
{
Node* uncle = grandfather->_left;
// 如果叔叔结点存在且为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
// 叔叔结点不存在或者是黑色
else
{
if (parent->_right == cur)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; // 旋转 + 变色后就可以直接退出循环了
}
}
}
_root->_col = BLACK;
return true;
}
void RotateL(Node* parent) // 左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* parentParent = parent->_parent;
// 1. 将subRL变成parent的右子树
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
// 2. 将parent变成subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 3. 建立subR与parent的父亲结点parentParent的关系
if (parentParent == nullptr) // 说明parent就是根结点
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
parentParent->_left = subR;
else
parentParent->_right = subR;
subR->_parent = parentParent;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;
// 1. 将subLR变成parent的左子树
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
// 2. 将parent变成subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 3. 建立subL与parent的父亲结点parentParent的关系
if (parentParent == nullptr) // 说明parent就是根结点
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
parentParent->_left = subL;
else
parentParent->_right = subL;
subL->_parent = parentParent;
}
}
在上面的插入函数中,当叔叔结点是红色时,将cur更新为grandfather后,需要继续向上检查,所以需要将parent也进行更新,更新为爷爷的父亲,但是爷爷可能就是根结点,也就是说此时parent为空,不能对parent解引用,所以while中要先判断parent是否为空,并且若是爷爷结点为根结点,此时并没有将爷爷结点变成黑色,所以在插入函数的结尾,直接将根节点变为黑色,不管原来是红色还是黑色。
上面的插入操作将红黑树构造出来了,但这颗树一定就是红黑树吗?所以需要对其进行验证
验证就需要看4个点:
1. 根节点是否为黑色
2. 是否存在连续的红色结点
3. 每条路径上的黑色结点个数是否相同
4. 中序遍历是否有序
bool IsBalance()
{
// 空树是红黑树
if (_root == nullptr)
return true;
// 判断根节点是否为黑色
if (_root->_col == RED)
return false;
int refNum = 0; // 用来记录一条路径上的黑色结点,以便后面比较
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++refNum;
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
if (blackNum != refNum)
{
cout << "存在黑色结点的数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
blackNum++;
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
上面采用的是先遍历一条路径,用一个变量来记录这条路径上黑色结点的个数,然后进行前序递归,每条路径走完时,判断是否与作为基准的路径相同。在遍历过程中,还要判断是否存在连续的红色结点
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。但是若仅仅看搜索,AVL树更优,因为其高度相对较低。
enum Colour
{
RED,
BLACK
};
template
struct RBTreeNode
{
pair _kv;
// 三叉链
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Colour _col; // 结点颜色
RBTreeNode(const pair& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED)
{}
};
template
class RBTree
{
typedef RBTreeNode Node;
public:
RBTree() = default;
~RBTree()
{
Destory(_root);
_root = nullptr;
}
RBTree(const RBTree& kv)
{
_root = Copy(kv._root);
}
RBTree operator=(RBTree kv)
{
swap(_root, kv._root);
}
bool Insert(const pair& kv)
{
// 若_root为空,直接让新插入的结点变成根
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点
Node* parent = nullptr;
Node* 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);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
// 因为现在是三叉链结果,还要链接一下父亲结点
cur->_parent = parent;
// 链接上去后要判断一下父亲颜色是否为红色,若是红色则需要处理
while (parent && parent->_col == RED)
{
// 若父亲是红色,则看叔叔是什么颜色
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 如果叔叔结点存在且为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
// 叔叔结点不存在或者是黑色
else
{
if (parent->_left == cur)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; // 旋转 + 变色后就可以直接退出循环了
}
}
else
{
Node* uncle = grandfather->_left;
// 如果叔叔结点存在且为红色
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = grandfather->_parent;
}
// 叔叔结点不存在或者是黑色
else
{
if (parent->_right == cur)
{
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; // 旋转 + 变色后就可以直接退出循环了
}
}
}
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
}
int Height()
{
return _Height(_root);
}
int Size()
{
_Size(_root);
}
bool IsBalance()
{
// 空树是红黑树
if (_root == nullptr)
return true;
// 判断根节点是否为黑色
if (_root->_col == RED)
return false;
int refNum = 0; // 用来记录一条路径上的黑色结点,以便后面比较
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++refNum;
cur = cur->_left;
}
return Check(_root, 0, refNum);
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
cur = cur->_right;
else if (cur->_kv.first > key)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
private:
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
if (blackNum != refNum)
{
cout << "存在黑色结点的数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED)
{
cout << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK)
blackNum++;
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr) return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
void RotateL(Node* parent) // 左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* parentParent = parent->_parent;
// 1. 将subRL变成parent的右子树
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
// 2. 将parent变成subR的左子树
subR->_left = parent;
parent->_parent = subR;
// 3. 建立subR与parent的父亲结点parentParent的关系
if (parentParent == nullptr) // 说明parent就是根结点
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
parentParent->_left = subR;
else
parentParent->_right = subR;
subR->_parent = parentParent;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;
// 1. 将subLR变成parent的左子树
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
// 2. 将parent变成subL的右子树
subL->_right = parent;
parent->_parent = subL;
// 3. 建立subL与parent的父亲结点parentParent的关系
if (parentParent == nullptr) // 说明parent就是根结点
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
parentParent->_left = subL;
else
parentParent->_right = subL;
subL->_parent = parentParent;
}
}
void Destory(Node* root)
{
if (root == nullptr) return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr) return nullptr;
Node* newNode = new Node(root->_kv);
newNode->_left = Copy(root->_left);
newNode->_right = Copy(root->_right);
return newNode;
}
void _InOrder(Node* _root)
{
if (_root == nullptr) return;
_InOrder(_root->_left);
cout << _root->_kv.first << "--" << _root->_kv.second << endl;
_InOrder(_root->_right);
}
Node* _root;
};