目录
1.概念
2.性质
3.实现
3.1定义数据类型
3.2设计基本操作
3.2.1着色问题详解
3.2.2 代码基本框架
3.2.3着色问题代码
3.2.4红黑树的销毁
3.3验证基本操作
4.总结
1.概念
红黑树是一种二叉搜索树,但是在其中的每个结点上增加一个存储表示该节点的颜色,这份颜色便是一种标记,可以是Red也可以是Black。通过对任何一条从根到叶子结点的路径,加以颜色的限制,来保证最长路径中节点数量不超过最短路径中节点数量的2倍。
因此,从红黑树的定义和规则出发,红黑树表现出一种近似平衡的二叉搜索树,但是其性能方面完全不亚于AVL树。
2.性质
- 每个结点不是黑色便是红色;
- 根节点是黑色的;
- 如果一个节点是红色,则它的两个孩子是黑色(不存在连在一起的红色节点);
- 对于每个结点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点;
- 每个叶子节点(空节点)都是黑色的(保证空树的根节点为黑色)。
通过上述性质的限制,我们便可以保证最长路径中节点个数不超过最短路径节点个数的2两倍。具体原因我们可以借助画图来理解,如下图:
我们可以进行结合红黑树性质进行假设,来让一段路径存在的节点数尽可能多,一段路径尽可能少。于是我们考虑极端情况,即红黑树中全部节点都为黑色。
此时,我们便可与插入尽可能多的红色节点。于是我们根据上图可以发现,在这种极端情况下,才存在某一简单路径上的节点个数是另一简单路径的2倍。当这种红黑树节点全为黑色的情况是不存在的,所以也不会存在最长路径中节点个数超过或等于最短路径节点个数2倍的情况。
3.实现
实现红黑树时,我们引入头节点head的内容,使它的左指针域指向begin(),右指针域指向end(),它的parent设置为根节点root,root的parent设置为头节点head。这样安排便于我们快速定位首尾元素位置,并且对于我们后续自己实现map和set存在很大帮助。
(创建RBTree.hpp文件)
3.1定义数据类型
我们来定义红黑树中的数据类型,并给出它的构造方法,最终代码设计如下:
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& data = T(), Color color = RED)
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _color(color)
{}
};
3.2设计基本操作
然后我们来给出红黑树中的基本框架和节点插入。我们需要注意的内容是,红黑树较于AVL树,其最大的不同之处在于它的节点具有颜色,也就是我们对其节点存在标记。并且这些颜色(标记)是存在规则(红黑树性质)的。
所以当我们对红黑树进行基本操作时,对于其中节点的颜色考虑是至关重要的,我们必须保证我们的操作不仅能到达我们想要的预期内容,还需保证我们的操作不会破坏红黑树应有的规则。
对于上述内容提到的红黑树节点着色处理问题,我们在此展开进行讨论。
3.2.1着色问题详解
对于红黑树中的新节点插入,我们需要对新节点插入后,红黑树的性质是否遭到破坏进行检测。若性质满足则直接退出,否则我们对红黑树进行旋转着色处理。
具体而言便是:我们对于新插入的节点默认是为红色,因此:若新插入节点的双亲是黑色,则没有违反红黑树规则;但当新插入节点的双亲为红色时,便违背了红黑树规则中:不能存在连续的红色节点。此时,便需要我们对于红黑树节点的着色情况进行处理。
情况一:当新插入节点cur默认为红色,父节点p为红色,和父节点同根的字节点u为红色,父节点的父节点(祖父节点)g为黑色,如下图:
在这种情况中,存在两种可能,即g为根节点和g为子树。
当g为根节点时,我们可以按照上面方式进行调整,使p节点和u节点变为黑色,g节点变为红色,并最后将根节点g调整为黑色(红黑树性质:根节点必须为黑色)。
当g为字数时,我们将p、u节点调整为黑色,g调整为红色。然后将p当作新的cur来继续线上进行调整,直到该红黑树满足着色规则。
情况二:cur为红色(p的左孩子),p为红色,g为黑色,u不存在/u存在且为黑色。
此时我们将u的不存在情况和u为黑色情况一并考虑,原因如下;
- 若u不存在,则cur一定为新插入节点,因为cur不是新插入节点的话,那么p和cur一定是存在一个颜色为黑色,否则会出现2红节点相邻,不满足红黑树规则;
- 若u为黑色,那么cur原本颜色一定为黑色(此时cur为红色,可能是cur为新插入节点默认为红色,也可能是红黑树经历某次调整使cur为红色),否则不满足红黑树中任意路径黑色节点数量相同规则。
对于前者我们仅需将cur制为黑色即可,对于后者,我们需要进行以下操作:当p为g的左孩子,且cur为p的左孩子时,则进行右单旋转;当p为g的右孩子,且cur为p的左孩子时,则进行左单旋转。
情况三:cur为红色(p的右孩子),p为红色,g为黑色,u不存在/u存在且为黑色,如下图:
当p为g的左孩子,且cur为p的右孩子时,针对p进行左单旋转;相反,当p为g的右孩子,且cur为p的左孩子时,针对p进行右单旋转。则但四种情况便会转化为情况二。继续情况二步骤即可。
3.2.2 代码基本框架
我们设计代码框架如下:
//假设红黑树中的data唯一
template<class T>
class RBTree {
typedef RBTreeNode<T> Node;
public:
RBTree() {
Node* _head = new Node();//值域设置空,并且不设置head颜色(head不算在RBTree中)
//此时不存在节点,head的parent指向null,左右指针域指向自身
_head->_parent = nullptr;
_head->_left = _head;
_head->_right = _head;
}
~RBTree() {
Destory(_head->_parent);
}
//插入节点
bool Insert(const T& data);
//旋转
void RotateLeft(Node* parent);
void RotateRight(Node* parent);
//销毁
void Destory(Node*& root);
//中序遍历
void InOrder(Node* root);
Node* GetHead() {
return _head;
}
private:
//获取红黑树中最小节点,即最左则节点
Node* LeftMost() {
Node* root = _head->_parent;
if (nullptr == root) {
return _head;
}
//寻找最左侧节点
Node* cur = root;
while (cur) {
cur = cur->_left;
}
return cur;
}
//获取红黑树中最大节点,即最右则节点
Node* RightMost() {
Node* root = _head->_parent;
if (nullptr == root) {
return _head;
}
//寻找最右侧节点
Node* cur = root;
while (cur) {
cur = cur->_right;
}
return cur;
}
protected:
Node* _head;
};
3.2.3着色问题代码
在理解3.2.1中的着色问题之后,我们便可设计具体的节点插入代码(分情况讨论)如下;
template<class T>bool RBTree<T>::Insert(const T& data) {
Node*& root = _head->parent;
//空树,直接插入空节点
if (root == nullptr) {
root = new Node(data, BLACK);
root->_parent = _head;
}
//非空
else {
Node* cur = root;
Node* parent = _head;
//向下遍历,根据AVL树模式来寻求插入节点位置
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 if (data > parent->_data) {
parent->_right = cur;
}
//添加插入节点的父节点
cur->_parent = parent;
//双亲不为头节点,且双亲不为根节点(根节点在红黑树中默认为黑色)
while (parent != _head && RED == parent->_color) {
Node* grandFather = parent->_parent;
//分情况讨论,判断parent是grandFather的左孩子还是右孩子
//此时为我们上述讨论的三种情况
if (parent == grandFather->_left) {
Node* unclue = grandFather->_right;//此时u节点为祖父节点的右节点
//情况一:u节点存在,且为红色
if (unclue && RED == unclue->_color) {
unclue->_color = BLACK;
parent->_color = BLACK;
grandFather->_color = RED;
//继续向上更新
cur = grandFather;
parent = cur->_parent;
}
//情况二(三):u节点不存在或为黑色
else {
//先处理情况三,使其改变为情况二
if (cur == parent->_left) {
//自旋和交换指针指向
RotateLeft(parent);
swap(parent, cur);
}
//此时为情况二,变色和自旋
parent->_color = BLACK;
grandFather->_color = RED;
RotateRight(grandFather);
}
}
//此时为我们上述讨论三种情况的反向情况(需要自旋处理)
else {
Node* unclue = grandFather->_left;//此时u节点为祖父节点的左节点
if (unclue && RED == unclue->_color) {
//情况一反向
parent->_color = BLACK;
unclue->_color = BLACK;
grandFather->_color = RED;
cur = grandFather;
parent = cur->_parent;
}
//情况二(三)反向
else {
//处理情况三反向
if (cur == parent->_left) {
RotateRight(parent);
swap(cur, parent);
}
//此时为情况二反向
parent->_color = BLACK;
grandFather->_color = RED;
RotateLeft(grandFather);
}
}
}
}
//更新头节点的左右指针域
_head->_left = LeftMost();
_head->_right = RightMost();
//规则二:根节点为黑色
root->_color = BLACK;
return true;
}
//左单旋
template<class T>void RBTree<T>::RotateLeft(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
subRL->_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;
}
}
}
//右单旋
template<class T>void RBTree<T>::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;
subL->_parent = pparent;
parent->_parent = subL;
if (pparent == _head) {
_head->_parent = subL;
}
else {
if (parent == pparent->_left) {
pparent->_left = subL;
}
else {
pparent->_right = subL;
}
}
}
3.2.4红黑树的销毁
template<class T>void RBTree<T>::Destory(Node*& root) {
if (root) {
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
}
3.3验证基本操作
我们给出中序遍历来查看红黑树结构和主函数中运行方法,如下:
//中序遍历
template<class T>void RBTree<T>::InOrder(Node* root){
cout << "中序遍历结果:";
if (root) {
InOrder(root->_left);
cout << root->_data << " ";
InOrder(root->_right);
}
cout << endl;
}
template<class T>
int main() {
RBTree<int> t;
int arr[] = { 16,3,7,11,9,26,18,14,15 };
for (auto i : arr) {
t.Insert(i);
}
auto temp = RBTree<T>::GetHead();
t.InOrder(temp->_parent);
return 0;
}
4.总结
红黑树是一种自平衡的二叉搜索树,能够保证对于n个节点的红黑树,树高不超过2log(n+1)。其中每个节点包含两个重要属性,即颜色和存储数据。
在我们对红黑树中的节点进行操作的时候,我们需要对其中每个节点的操作加以考虑,即注意在我们的操作过程中,保证红黑树的规则不被打破,这是使用红黑树的关键--保证其原有性质不变。
红黑树是一种广泛使用的数据结构,它的高效性、自平衡性和可靠性使得其成为一种广泛应用的数据结构。