文章目录
前言
本章对红黑树做一个总结,包括红黑树的基本规则,代码实现以及图片展示等
一、红黑树的概念
红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。
二、红黑树的规则
- 每个结点不是红⾊就是⿊⾊
- 根结点是⿊⾊的
- 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点。
- 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点
三、红⿊树的效率
假设N是红⿊树树中结点数量,h最短路径的⻓度,那么 由此推出2^h − 1 <= N < 2^2∗h − 1由此推出h ≈ logN 也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径 2 ∗ logN,那么时间复杂度还是O(logN)
红黑树与AVL树的关系:
AVL树和红黑树都是自平衡二叉搜索树,旨在优化插入、删除和查找操作的性能。它们在设计和实现上有所不同,但在某些条件下存在相互转换的可能性。
- AVL树可以染成红黑树,通过特定的染色算法可以满足红黑树的规则。
- 红黑树不一定是AVL树,因为红黑树的平衡性要求较低,可能不满足AVL树的严格高度平衡条件。
- 在实际应用中,选择使用哪种树结构取决于具体场景的需求,例如插入、删除和查询操作的频率。
- 红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格
三、红⿊树的插⼊
红黑树的插入无非就是插入一个节点,如果父亲节点是黑色,那就直接插入,如果不是,然后在去维护它的规则。使其满足对应规则。
思考:当新插入一个节点时,是插入红色节点还是黑色节点呢?
注意 :这里插入的是红色节点
原因 :如果这里插入黑色节点时,那么就必会违背规则4,如果插入的是红色节点是有可能会违背规则3的。综合考虑,所以这里插入红色节点。
那怎样去维护红黑树的规则呢?这里我们通过变色和旋转去维护。不过要分为几种情况。这具体还是要看父亲的兄弟节点(uncle,这里方便我就称他为叔叔节点)。
1.叔叔节点(uncle)存在且为红
当叔叔节点亲存在且为红色时。这个时候就只需要变色就能维护红黑树的规则。
具体操作 :
把父亲和叔叔节点变成黑色,然后把父亲节点的父亲节点(grandpa)变成红色,然后再向上更新,如果上一节点为黑色,那就不用再向上更新。如果上一节点为红色,那就要进行下一操作了。
2.当叔叔节点不存在或则为黑
当叔叔节点不存在或则为黑时,这个时候就要旋转+变色了,但是这里旋转也要分情况:
第一种情况:单旋转+变色
满足条件:当新插入的节点在父亲节点的左边且叔叔节点不存在或则为黑
具体操作:对grandpa节点进行右旋转然后在把父亲节点变黑,grandpa节点变红。
当叔叔节点不存在时 :
当叔叔节点存在且为红,下面画的是抽象图。
第二种情况:双旋转+变色
满足条件:当新插入的节点在父亲节点的右边且叔叔节点不存在或则为黑
具体操作:先对parent节点进行左单旋然后在对grandpa节点进行右单旋最后在把cur变成黑色,grandpa变成红色
当叔叔节点存在且为黑,下面是抽象图
注意 :如果叔叔存在且为黑。那么cur节点一定不是新增节点,一定是从新增节点更新上来的。如果是新增节点的话那就不满足红黑树的规则4了。
四、红黑树的代码实现(只展示插入)
虽然红黑树的插入有很多种情况,但是代码实现起来还是比较简单的。这里说明一下就是我们把grandpa节点的称呼改成grandfather了。意思都一样的。只是小编在写代码的时候没有注意。
cpp
#include<iostream>
#include<assert.h>
#include<vector>
#include<list>
using namespace std;
enum Color
{
RED,
BLACK
};
template<class T,class V>
struct RBTreeNode
{
pair<T, V> _ky;
RBTreeNode<T, V>* left;
RBTreeNode<T, V>* right;
RBTreeNode<T, V>* parent;
Color _cor;
RBTreeNode(const pair<T, V>& ky = pair<T,V>())
:_ky(ky)
,left(nullptr)
,right(nullptr)
,parent(nullptr)
{}
};
template<class T,class V>
class RBTree
{
typedef RBTreeNode<T, V> Node;
public:
void RotateL(Node* parent)//左旋转
{
Node* subR = parent->right;
Node* subRL = subR->left;
Node* pparent = parent->parent;
parent->right = subRL;
if (subRL)subRL->parent = parent;
parent->parent = subR;
subR->left = parent;
if (pparent == nullptr)
{
_root = subR;
subR->parent = nullptr;
}
else if (pparent->left == parent)
{
pparent->left = subR;
}
else if (pparent->right == parent)
{
pparent->right = subR;
}
}
void RotateR(Node* parent)//右旋转
{
Node* subL = parent->left;
Node* subLR = subL->right;
Node* pparent = parent->parent;
parent->left = subLR;
if (subLR)subLR->parent = parent;
subL->right = parent;
parent->parent = subL;
if (pparent == nullptr)
{
_root = subL;
subL->parent = nullptr;
}
else if (pparent->left == parent)
{
pparent->left = subL;
}
else if (pparent->right == parent)
{
pparent->right = subL;
}
else assert(false);
}
bool Insert(const pair<T, V>& ky)//红黑树的插入
{
//按照二叉搜索树的规则插入
if (_root == nullptr)
{
_root = new Node(ky);
_root->_cor = BLACK;
_root->parent = nullptr;
return 0;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_ky.first < ky.first)
{
parent = cur;
cur = cur->right;
}
else if (cur->_ky.first > ky.first)
{
parent = cur;
cur = cur->left;
}
else return false;
}
cur = new Node(ky);
cur->_cor = RED;
parent->_ky.first < ky.first ? parent->right = cur : parent->left = cur;
cur->parent = parent;
while (parent && parent->_cor == RED)
{
Node* grandfather = parent->parent;
if (grandfather->left == parent)
{
// g
// p u
Node* uncle = grandfather->right;
if (uncle && uncle->_cor == RED)//叔叔节点存在且为红
{
uncle->_cor = parent->_cor = BLACK;//变色
grandfather->_cor = RED;
//向上更新
cur = grandfather;
parent = cur->parent;
}
else//叔叔节点存在且为黑或者不存在
{
// g
// p u
//c
if (cur == parent->left)//右单旋+变色
{
RotateR(grandfather);//右旋转
parent->_cor = BLACK;
grandfather->_cor = RED;
}
// g
// p u
// c
else//左右双旋+变色
{
RotateL(parent);//左旋转
RotateR(grandfather);//右旋转
cur->_cor = BLACK;
grandfather->_cor = RED;
}
break;
}
}
else//跟上面差不多的只是旋转的方向改变一下就行了。
{
// g
// u p
Node* uncle = grandfather->left;
if (grandfather->right == parent)
{
if (uncle && uncle->_cor == RED)
{
uncle->_cor = parent->_cor = BLACK;
grandfather->_cor = RED;
cur = grandfather;
parent = cur->parent;
}
else
{
if (cur == parent->right)//右单旋
{
RotateL(grandfather);
parent->_cor = BLACK;
grandfather->_cor = RED;
}
else
{
RotateR(parent);
RotateL(grandfather);
cur->_cor = BLACK;
grandfather->_cor = RED;
}
break;
}
}
}
}
_root->_cor = BLACK; //防止根节点在更新的时候变成红色
return true;
}
void _InOrder()
{
InOrder(_root);
}
private:
void InOrder(Node*root)
{
if (root == nullptr)
{
return;
}
InOrder(root->left);
cout << root->_ky.first << ":" << root->_ky.second << " ";
InOrder(root->right);
}
Node* _root = nullptr;
};
int main()
{
RBTree<int, int >v;
int arr[] = { 1,5,3,6,4,8,2,9,7 };
for (auto e : arr)
{
v.Insert({ e,e });
}
v._InOrder();
}
五、红黑树的查找
由于查找的代码和逻辑比较简单,这里小编就直接写了。我相信大家没有问题的。
cpp
Node* Find(const pair<T,V>& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_ky.first < key.first )
{
cur = cur->right;
}
else if (cur->_ky.first > key.first )
{
cur = cur->left;
}
else
{
return cur;
}
}
return nullptr;
}
六 、红⿊树的验证
这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。
- 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
- 规则2直接检查根即可
- 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
- 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。
- 验证红黑树的代码演示:
cpp
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
// 前序遍历到空时,意味着条路径完了
//cout << blackNum << endl;
if (refNum != blackNum)
{
cout << "存在黑色结点的数量不相等的路径" << endl;
return false;
}
return true;
}
// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检父亲就方便多了
if (root->_cor == RED && root->parent->_cor == RED)
{
cout << root->_ky.first << "存在连续的红色结点" << endl;
return false;
}
if (root->_cor == BLACK)
{
blackNum++;
}
return Check(root->left, blackNum, refNum) && Check(root->right, blackNum, refNum);
}
bool IsBalance()
{
if (_root == nullptr)
return true;
if (_root->_cor == RED)
return false;
// 参考值
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_cor == BLACK)
{
++refNum;
}
cur = cur->left;
}
return Check(_root, 0, refNum);
}
思考:为什么红⿊树如何确保最⻓路径不超过最短路径的2倍的?
- 分析:
由红黑树的规则4得知:每条从根到NULL 的路径的黑色节点数量是相同的。所以在极端情况下,最短的路径就是两个黑色节点的路径。但是又由规则2,3得知,红黑树的所有路径中没有连续的红节点而且根节点为黑,所以在极端情况下最长的路径就是一黑一红。刚好是最短路径的2倍。
总结
今天就到这里吧,这里我给大家分享了红黑树的部分,但是还有一部分没有写。比如红黑树的删除。如果对红黑树的删除感兴趣的话可以去参考《算法导论》或者《STL源码剖析》中讲解。