『 C++ 』红黑树RBTree详解 ( 万字 )
文章目录
- 🦖 红黑树概念
- 🦖 红黑树节点的定义
- 🦖 红黑树的插入
- 🦖 数据插入后的调整
- 🦕 情况一:ucnle存在且为红
- 🦕 情况二:uncle不存在或uncle存在且为黑
- 🦕 插入函数代码段(参考)
- 🦕 旋转操作代码段(参考)
- 🦖 判断红黑树是否符合规则
- 🦖 红黑树的析构函数
- 🦖 完整代码(供参考)
🦖 红黑树概念
红黑树是一棵较为复杂的树;
其与AVL树相同,也为一棵平衡搜索二叉树;
其与AVL树不同的是,在AVL树中依靠平衡因子bf(Balance Factor)来保证树的结构始终保持为一个高度平衡的状态(每个节点的左右子树高度差不超过1);
而对于红黑树来说,其在节点当中存储了一个颜色,颜色不为红则为黑,根据颜色制定了一系列的规则;
其最终的结果为,在红黑树当中其的最长路径不会超过最短路径的两倍;
可以从这个最终的结果看出,对于红黑树来说其并不是一棵高度平衡的二叉树,相对其只是一棵较为平衡的二叉树;
以该图为例,该图即为一棵红黑树,其具有搜索二叉树的属性;
在上文中提到,红黑树中存在一系列的规则,通过一系列的规则来约束树的结构使其最终能够达到一棵树的最长路径长度不会超过最短路径的二倍;
其规则如下:
-
红黑树的节点不是红色就是黑色;
-
根节点的颜色是黑色;
-
如果一个节点为红色,其两个孩子节点必定为黑色;
-
对于每一个节点,从该节点到所有后代叶节点的简单路径上,都包含相同数目的黑色节点;
-
每个叶子节点的颜色都是黑色;
在该处的叶子节点所指的并不是平时在树的结构中所说的最后一个非空节点,在红黑树当中所谓的叶子节点即为空节点,在此处以
NIL
表示;
根据上面的规则来看,上图中的树都符合这几条规则,上图中的树为一棵红黑树;
-
为什么当符合上面五条规则时,其树的结构的最终结果就能保证其最长路径中的节点个数不会超过最短路径节点个数的2倍?
以该图为例,该图为一棵红黑树,其符合上述的五条规则;
在该树当中绘出了两条路径,其中最左侧的黄色路径为其中一条的最长路径;最右侧的蓝色路径为其中一条最短路径;
对于5条规则中的第四条规则
对于每一个节点,从该节点到所有后代叶节点的简单路径上,都包含相同数目的黑色节点
,与最终的结果最长路径长度不会超过最短路径的二倍
中的黑色节点都包含其中叶子节点的NIL
节点;如此所见,最长路径的长度为
5
,最短路径的长度为3
;可以得出问题中所提到的问题;
从上文可知,红黑树的平衡程度不比AVL树;
AVL树为高度平衡搜索二叉树,而红黑树只是近似平衡搜索二叉树,但在实际的使用场景当中,大多数情况都会使用红黑树而不是AVL树,原因如下:
-
插入删除操作的难易程度
当红黑树和AVL树都处于一种规则被破坏的情况下都要使用一些处理手段使其恢复平衡状态;
对于红黑树来说,红黑树的整体结构与规则处于一种近似平衡的状态而不是高度平衡的状态,这使得红黑树能够允许树在一些情况中不是那么的平衡(最长路径不超过最短路径的2倍);
而对于AVL树来说,其的平衡为高度平衡状态,虽然说在高度平衡的状态对于数据的读取效率要高于红黑树,但也暴露出来一些问题;
由于AVL树中节点的平衡因子的绝对值不能大于
1
,即每当平衡因子的绝对值为2
时表示这棵树破坏了AVL树的高度平衡状态,需要对树进行一定的旋转操作使得恢复其本身的高度平衡状态;但是就是因为每次在少次插入过后都要进行一次旋转从而降低了AVL树在插入数据情况中的效率;
-
维护成本
在维护成本当中,由于AVL树中存在平衡因子,所以每次插入的时候都需要判断新插入节点是否影响其祖先节点的平衡因子致使其不平衡;
而对于红黑树而言,红黑树中判断是否符合规则的为节点的颜色;
由于默认插入节点的颜色都为红色,只需要判断这个红色的节点是否影响破坏红黑树本身的规则,若是新插入的节点破坏了其整棵树(子树)的规则结构则对树(子树)的结构进行处理即可;
两者相比较实际上AVL树的维护成本要高于红黑树,在特殊情况中AVL树的结构可能在实现过程中其可能因为平衡因子未正确更新从而可能导致整棵树的结构被破坏;
故AVL树的维护成本高于红黑树;
-
综合性能
综上所述,红黑树的综合性能高于AVL树,其处于一种较为平衡的状态;
在极端情况当中,AVL树可能会因为插入大量数据而引发大量的旋转操作导致效率的下降,但红黑树可以很好的避免这个问题;
当然就算是查找一个数据的情况下红黑树只是略逊AVL树一点;
🦖 红黑树节点的定义
从上文可知,红黑树的节点与AVL树的节点设置的不同点为:
-
AVL树
AVL树采用的是利用平衡因子来控制;
-
红黑树
红黑树采用的是以颜色来判断树的结构;
由上文概念中几个规则可能引出一个问题:
-
红黑树中的新节点应该是什么颜色?
黑色?
红色?
从上文中的规则关于节点的颜色中有这么两条规则:
- 如果一个节点为红色,其两个孩子节点必定为黑色;
- 对于每一个节点,从该节点到所有后代叶节点的简单路径上,都包含相同数目的黑色节点;
根据这两个规则进行引入,权衡利弊可以发现新节点无论是黑色还是红色都有可能违反两条规则中的其中一条规则;
但是对于两条规则的违反代价来说,新节点为黑色的代价要高于新节点为红色的代价;
当新节点为黑色的时候由于每条简单路径的黑色节点数量要相同,所以当插入新节点为黑色节点时其他的简单路径上必须也插入同样的新黑色节点;
而若是新节点为红色时则只需要对树的结构进行处理即可;
故新节点的颜色应该为红色;
- 插入过程中宁可违反规则
3
,也不违反规则4
;
代码段:
enum COLOR{//红黑树的颜色控制,采用枚举确保颜色只能是红或黑RED,BLACK
};/*红黑树是搜索二叉树;同时红黑树利用颜色控制二叉树的平衡;红黑树确保没有一条路径会比其他路径长出二倍;不为高度平衡 而为近似平衡;
*//* 红黑树的规则:1.每个节点不是红色就是黑色;2.根节点是黑色;3.如果一个节点是红色的,则它的两个孩子节点是黑色;4.对于每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点5.每个叶子节点都是黑色的(这里的叶子节点指的是空节点NIL)为什么满足上面的性质红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?最短路径即为全黑路径最长路径可以指的是红黑相接的路径假设存在N个黑色节点的话(包括NIL空节点),则红黑相间的红色节点个数为N-1,则最终的节点个数为2N-1;*/
template<class K,class V>
struct RBTreeNode{RBTreeNode<K, V> *_left;RBTreeNode<K, V> *_right;RBTreeNode<K, V> *_parent;std::pair<K,V> _kv; COLOR _col;RBTreeNode(const std::pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//给定缺省值默认为红色{}/*节点颜色默认值为新增的节点为红色还是黑色的问题这是选择违反红黑树规则中规则3和规则4的问题,权衡利弊宁可违反规则3也不违反规则4;*/
};template<class K,class V>
class RBTree{//红黑树整体模型typedef RBTreeNode<K,V> Node;
public:~RBTree();Node *Find(const K &key);bool Insert(const std::pair<K,V>& kv);bool IsBalance();protected:void RotateL(Node *parent);void RotateR(Node *parent);void _Destory(Node* &root);private:Node* _root = nullptr;}
本文中的红黑树实现一样采用key value
模型进行演示;
🦖 红黑树的插入
红黑树也是基于搜索二叉树后控制其平衡条件的一种二叉树;
其插入模式与逻辑与搜索二叉树相同;
与之不同的是由于红黑树的新节点插入为红色节点,所以插入过程当中可能违反红黑树五条规则中的第三条规则;
- 如果一个节点为红色,其两个孩子节点必定为黑色;
即可能发生红色节点的孩子节点也为红色节点,在这种情况当中就要对树的结构进行对应的调整更新使其恢复原来的近似平衡状态;
-
代码段
bool Insert(const std::pair<K,V>& kv){if(_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node *cur = _root;Node *parent = nullptr;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;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//....../*判断与处理*/return true;}
当插入的动作结束后应该及时判断新增节点是否影响整棵红黑树的结构;
由于新增节点为红色节点,所以只需判断节点是否破坏红黑树规则中的规则3即可;
即判断新增节点的父亲节点是否为红色:
-
不为红色
若是新增节点的父亲节点不为红色,则说明该次插入并不影响红黑树的规则,算是一次正常插入;
-
为红色
若是新增节点的父亲节点为红色节点,说明该次插入违反了红黑树规则中的第三条规则,需要对树(子树)进行调整使其恢复原来的红黑树;
当红黑树的第三条规则被违反时将会出现几种情况;
下图为一张抽象图,其中uncle
表示一棵子树;
以单边为例,该图即为单边(左边)可能出现的违反第三条规则的情况;
🦖 数据插入后的调整
在上文中提到了当数据插入结束后需要对插入的新节点进行判断,由于插入方式的问题(节点的颜色),所以需要对新节点的位置判断其是否存在违反第三条规则的情况;
从该图中可以看到,对于红黑树的处理方式与AVL的调整方式不同,因为对于红黑树而言主要是需要对节点进行重新着色;
同时在此基础上对树(子树)在需要的情况下对其进行旋转操作;
从图中可以发现在红黑树中重点看几个节点:
-
cur
节点新增节点
-
parent
节点新增节点的父亲节点
-
grandfather
节点新增节点的祖父节点
-
uncle
节点新增节点的父亲节点的兄弟节点
而在红黑树当中,重点所看的节点就算所谓的uncle
节点叔叔节点;
这个节点决定了红黑树在违反了规则之后需要使用哪种方式对树(子树)进行调整;
而红黑树的调整方法中uncle
节点扮演着重要的作用,若是没有使用或者关注uncle
节点,则可能使用错误的调整方案使得红黑树的结构变得更加的混乱;
下面的例子主要以单边为例,对于红黑树的调整来说不重点对节点的位置进行处理而是对于uncle
节点来进行处理;
🦕 情况一:ucnle存在且为红
当出现这种情况时即cur
新增节点与其父亲节点parent
节点的颜色为红色;
且在这种情况当中uncle
节点存在且该节点的颜色为红色;
以该图为例;
其中这种情况下对于树(子树)的处理情况是最简单的,只需要对节点进行重新着色即可,即:
-
parent
节点变黑 -
uncle
节点变黑 -
cur
节点变红
根据上面三步的操作即可完成该情况的解决方式;
-
为什么说这个方式是最容易处理的情况?
出现这种情况时由于解决的方式只需要对节点进行重新着色即可,并不需要对节点的方向进行判断;
即
cur
节点在parent
节点的左右方向,出现这种情况都可以使用该方式进行解决,可以对其进行单独的if
条件语句判断; -
代码段
while(parent && parent->_col == RED){//由于默认插入的节点为红色 所以当parent节点存在且为红色时表示需要进行调整Node *grandfather = parent->_parent;//根节点必须为黑色 所以说明该节点上还有节点if(grandfather->_left == parent){Node *uncle = grandfather->_right;//插入时颜色的变化分三种情况if(uncle && uncle->_col == RED){/*uncle存在且为红当uncle存在且为红的时候,说明cur节点为新增节点这时候的处理方式只需要对节点进行变色处理即可将parent节点与uncle节点变黑 uncle节点变红*/parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//uncle节点不存在或者uncle节点为黑色//......}break;}}else{ // if(grandfather->_right == parent){Node *uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{//uncle节点不存在或者uncle节点为黑色//......break;}}}_root->_col = BLACK;return true;}
当处理完之后应继续向上更新节点位置:
cur
节点更新为当前的grandfather
节点;parent
节点更新为新的cur
节点的_parent
节点;
并按照循环来判断该处解决之后是否需要再向上进行解决
- 若是
parent
节点不为空且parent
节点为红色时则继续向上更新; - 若是
parent
节点为空或是parent
节点存在且为黑色时说明该树(子树)不存在问题,插入环节结束;
🦕 情况二:uncle不存在或uncle存在且为黑
当出现这种情况时,简单的对节点进行重新着色的方案就不予推荐;
因为出现这种情况时无论如何对节点进行重新着色都将不可能将树(子树)的结构恢复为原来的红黑树结构;
当出现这种情况时则说明树的结构已经趋于不平衡,需要借助到在AVL树种所提到的旋转方案;
且出现该情况后需要进行判断cur节点存在于parent节点的左边还是右边
:
-
cur
存在于parent
节点的左边当
cur
节点存在于parent
节点的左侧时则需要进行单旋操作; -
cur
存在于parent
节点的右边当
cur
节点存在于parent
节点的右侧时则需要进行双旋操作;
根据cur
节点所处的位置来判断需要进行单旋还是双旋操作;
当然若是需要进行双旋时双旋的顺序判断取决于是grandfather
的左子树出现问题还是该节点的右子树出现问题;
-
为什么uncle不存在和uncle存在且为黑两种情况可以用同一种方案进行解决?
实际上在操作中
uncle
不存在 与uncle
存在且为黑的情况可以用同一种方式进行解决是因为其构造是相同的,只不过一个具有节点一个没有节点;uncle
节点不存在的情况下cur
节点可能是新增节点;而
uncle
节点存在且为黑的情况其一定是由情况一变化而来,即情况一解决后向上更新所发现的新情况;
该图即为单边情况下子树出现问题的解决办法的图例(具象图);
-
代码段
while(parent && parent->_col == RED){//由于默认插入的节点为红色 所以当parent节点存在且为红色时表示需要进行调整Node *grandfather = parent->_parent;//根节点必须为黑色 所以说明该节点上还有节点if(grandfather->_left == parent){Node *uncle = grandfather->_right;//插入时颜色的变化分三种情况if(uncle && uncle->_col == RED){/*uncle存在且为红*/}else{// (uncle == nullptr || uncle->_col == BLACK)/*另一种情况即为uncle节点不存在或者uncle节点存在且为黑色节点当uncle节点不存在时说明cur节点为新增节点当uncle节点存在但是节点颜色为黑色时说明cur节点不为新增节点而是由情况1变化而来;这两种情况虽然cur节点的状态不同但是真正意义上来说两者在处理方式可以以相同的方式进行处理但是uncle节点不存在或是uncle存在且为黑的情况虽然统一采取同一种方式进行处理但是还将这种处理方式进行两种情况的划分 分别为cur节点处于parent节点的左子树还是右子树通过左子树或者是右子树取决于使用的措施是采用单选操作还是双旋操作*/if(cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{ // cur == parent->_leftRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{ // if(grandfather->_right == parent){Node *uncle = grandfather->_left;if (uncle && uncle->_col == RED){/*uncle存在且为红*/}else{// (uncle == nullptr || uncle->_col == BLACK)if(cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}
当 uncle
不存在 或 是uncle
存在且为黑 的两种情况处理后则可以直接break
退出循环;
🦕 插入函数代码段(参考)
bool Insert(const std::pair<K,V>& kv){if(_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node *cur = _root;Node *parent = nullptr;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;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while(parent && parent->_col == RED){//由于默认插入的节点为红色 所以当parent节点存在且为红色时表示需要进行调整Node *grandfather = parent->_parent;//根节点必须为黑色 所以说明该节点上还有节点if(grandfather->_left == parent){Node *uncle = grandfather->_right;//插入时颜色的变化分三种情况if(uncle && uncle->_col == RED){/*uncle存在且为红当uncle存在且为红的时候,说明cur节点为新增节点这时候的处理方式只需要对节点进行变色处理即可将parent节点与uncle节点变黑 uncle节点变红*/parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// if(uncle == nullptr || uncle->_col == BLACK)/*另一种情况即为uncle节点不存在或者uncle节点存在且为黑色节点当uncle节点不存在时说明cur节点为新增节点当uncle节点存在但是节点颜色为黑色时说明cur节点不为新增节点而是由情况1变化而来;这两种情况虽然cur节点的状态不同但是真正意义上来说两者在处理方式可以以相同的方式进行处理但是uncle节点不存在或是uncle存在且为黑的情况虽然统一采取同一种方式进行处理但是还将这种处理方式进行两种情况的划分 分别为cur节点处于parent节点的左子树还是右子树通过左子树或者是右子树取决于使用的措施是采用单选操作还是双旋操作*/if(cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{ // cur == parent->_leftRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{ // if(grandfather->_right == parent){Node *uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// if(uncle == nullptr || uncle->_col == BLACK)if(cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}
🦕 旋转操作代码段(参考)
void RotateL(Node *parent){Node *cur = parent->_right;Node *curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node *ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node *parent){Node *cur = parent->_left;Node *curright = cur->_right;parent->_left = curright;if (curright)curright->_parent = parent;Node *ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}
旋转操作中无需像AVL树那样关心对应的平衡因子;
🦖 判断红黑树是否符合规则
与AVL树相同,当一棵红黑树被创建完毕之后若是只用中序遍历来判断的话只能判断出该树是否为搜索二叉树,但是不能保证出所创建出的树是一棵红黑树;
对于红黑树是否平衡的判断一样可以利用一个接口去是先解决;
当然判断红黑树是否平衡的方法多种多样;
其中在本文章的方法即为: 利用一个pair<bool,vector<int>>
对象来存储结果,最终返回pair
中的bool
结果,当然该中方式也需要用到子函数来完成其他的判断:
bool IsBalance(){//...}
-
判断根节点
_root
节点的颜色是否为黑色:若是该节点的颜色为红色则直接返回
false
; -
判断是否存在连续的红色节点与每条简单路径当中是否存在相同个数的黑色节点:
定义一个
Check()
子函数用来判断是否出现连续的红色节点;传递一个
blackNum
变量用来记录每条简单路径的黑色节点个数;Check()
函数传递了一个pair<bool,vector<int>>
对象的引用,为了能在函数之中利用其中的vector<int>
来判断每条简单路径上是否存在相同的黑色节点数量;bool
则是用来判断是否存在连续相同的红色节点;利用递归的思路,当节点为
nullptr
空时vector<int>
部分添加对应的blackNum
黑色节点个数;void _Check(std::pair<bool,std::vector<int>> &ret,Node *root,size_t blackNum = 0){if(root == nullptr){ret.first = ret.first && true;ret.second.push_back(blackNum);return;}if(root->_col == RED && root->_parent->_col == RED){ret.first = ret.first && false;return ;}if(root->_col == BLACK){blackNum++;}_Check(ret, root->_left, blackNum);_Check(ret, root->_right, blackNum);}
-
最终对该
pair
对象进行判断即可;
-
代码段
bool IsBalance(){if(_root == nullptr){return true;}if(_root && _root->_col == RED){//头节点为红色节点说明树的插入或者某次旋转后的颜色变化出现问题return false;}std::pair<bool,std::vector<int>> ret ;ret.first = true;_Check(ret,_root,0);if(!ret.first){std::cout << "某一路径出现连续红色节点" << std::endl;}bool to_comp = true;size_t _comp = ret.second[0];for(auto &it : ret.second){if(it != _comp){to_comp = false;break;}std::cout << it << std::endl;}return to_comp && ret.first;}
当然可以使用其他方法,并在对应的出现错误的位置根据条件使用assert(false)
断言断死来排查问题所在;
🦖 红黑树的析构函数
红黑树的析构函数只需要写一个_Destroy()
函数并在析构函数中进行调用即可;
当然_Destroy()
函数采用后序遍历的方式对节点逐个进行释放;
void _Destory(Node* &root){if(root == nullptr){return ;}_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}
🦖 完整代码(供参考)
该段代码中由于测试需要定义了一些非必要的函数;
#include<iostream>#include<vector>enum COLOR{//红黑树的颜色控制,采用枚举确保颜色只能是红或黑RED,BLACK
};/*红黑树是搜索二叉树;同时红黑树利用颜色控制二叉树的平衡;红黑树确保没有一条路径会比其他路径长出二倍;不为高度平衡 而为近似平衡;*//* 红黑树的规则:1.每个节点不是红色就是黑色;2.根节点是黑色;3.如果一个节点是红色的,则它的两个孩子节点是黑色;4.对于每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点5.每个叶子节点都是黑色的(这里的叶子节点指的是空节点NIL)为什么满足上面的性质红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?最短路径即为全黑路径最长路径可以指的是红黑相接的路径假设存在N个黑色节点的话(包括NIL空节点),则红黑相间的红色节点个数为N-1,则最终的节点个数为2N-1;*/
template<class K,class V>
struct RBTreeNode{RBTreeNode<K, V> *_left;RBTreeNode<K, V> *_right;RBTreeNode<K, V> *_parent;std::pair<K,V> _kv; COLOR _col;RBTreeNode(const std::pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//给定缺省值默认为红色{}/*节点颜色默认值为新增的节点为红色还是黑色的问题这是选择违反红黑树规则中规则3和规则4的问题,权衡利弊宁可违反规则3也不违反规则4;*/
};template<class K,class V>
class RBTree{typedef RBTreeNode<K,V> Node;
public:~RBTree(){_Destory(_root);}void InOrder(){_InOrder(_root);}Node *Find(const K &key){Node* cur = _root;while(cur){if(key>cur->_kv.first){cur = cur->_right;}else if(key<cur->_kv.first){cur = cur->_left;}else{return cur;}}return nullptr;}bool Insert(const std::pair<K,V>& kv){if(_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node *cur = _root;Node *parent = nullptr;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;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while(parent && parent->_col == RED){//由于默认插入的节点为红色 所以当parent节点存在且为红色时表示需要进行调整Node *grandfather = parent->_parent;//根节点必须为黑色 所以说明该节点上还有节点if(grandfather->_left == parent){Node *uncle = grandfather->_right;//插入时颜色的变化分三种情况if(uncle && uncle->_col == RED){/*uncle存在且为红当uncle存在且为红的时候,说明cur节点为新增节点这时候的处理方式只需要对节点进行变色处理即可将parent节点与uncle节点变黑 uncle节点变红*/parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// if(uncle == nullptr || uncle->_col == BLACK)/*另一种情况即为uncle节点不存在或者uncle节点存在且为黑色节点当uncle节点不存在时说明cur节点为新增节点当uncle节点存在但是节点颜色为黑色时说明cur节点不为新增节点而是由情况1变化而来;这两种情况虽然cur节点的状态不同但是真正意义上来说两者在处理方式可以以相同的方式进行处理但是uncle节点不存在或是uncle存在且为黑的情况虽然统一采取同一种方式进行处理但是还将这种处理方式进行两种情况的划分 分别为cur节点处于parent节点的左子树还是右子树通过左子树或者是右子树取决于使用的措施是采用单选操作还是双旋操作*/if(cur == parent->_left){RotateR(grandfather);// parent->_col = RED;// grandfather->_col = cur->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;}else{ // cur == parent->_leftRotateL(parent);RotateR(grandfather);// cur->_col = RED;// grandfather->_col = BLACK;// parent->_col = BLACK;cur->_col = BLACK;grandfather->_col = RED;}break;}}else{ // if(grandfather->_right == parent){Node *uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// if(uncle == nullptr || uncle->_col == BLACK)if(cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}bool IsBalance(){if(_root == nullptr){return true;}if(_root && _root->_col == RED){//头节点为红色节点说明树的插入或者某次旋转后的颜色变化出现问题return false;}std::pair<bool,std::vector<int>> ret ;ret.first = true;_Check(ret,_root,0);if(!ret.first){std::cout << "某一路径出现连续红色节点" << std::endl;}bool to_comp = true;size_t _comp = ret.second[0];for(auto &it : ret.second){if(it != _comp){to_comp = false;break;}std::cout << it << std::endl;}return to_comp && ret.first;}int getHeight(){return getHeight(_root);}protected:void RotateL(Node *parent){Node *cur = parent->_right;Node *curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node *ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void RotateR(Node *parent){Node *cur = parent->_left;Node *curright = cur->_right;parent->_left = curright;if (curright)curright->_parent = parent;Node *ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}}void _InOrder(Node *root){if(root == nullptr) return ;_InOrder(root->_left);std::cout << root->_kv.first << " : " << root->_kv.second << " col : " << root->_col << std::endl;_InOrder(root->_right);}void _Check(std::pair<bool,std::vector<int>> &ret,Node *root,size_t blackNum = 0){if(root == nullptr){ret.first = ret.first && true;ret.second.push_back(blackNum);return;}if(root->_col == RED && root->_parent->_col == RED){ret.first = ret.first && false;return ;}if(root->_col == BLACK){blackNum++;}_Check(ret, root->_left, blackNum);_Check(ret, root->_right, blackNum);}int getHeight(Node *root){if (root == nullptr){return 0;}int left = getHeight(root->_left);int right = getHeight(root->_right);return left > right ? left + 1 : right + 1;}void _Destory(Node* &root){if(root == nullptr){return ;}_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}private:Node* _root = nullptr;
};
相关文章:

『 C++ 』红黑树RBTree详解 ( 万字 )
文章目录 🦖 红黑树概念🦖 红黑树节点的定义🦖 红黑树的插入🦖 数据插入后的调整🦕 情况一:ucnle存在且为红🦕 情况二:uncle不存在或uncle存在且为黑🦕 插入函数代码段(参考)🦕 旋转…...
c# 人脸识别的思路
在C#中实现人脸识别,您可以使用诸如虹软ArcFace等第三方人脸识别SDK。以下是一个基于虹软ArcFace SDK的C#人脸识别示例的大致步骤: 安装与引用SDK: 首先,您需要从虹软官网下载适用于C#的ArcFace人脸识别SDK,并将其安装…...

如何用AI提高论文阅读效率?
已经2024年了,该出现一个写论文解读AI Agent了。 大家肯定也在经常刷论文吧。 但真正尝试过用GPT去刷论文、写论文解读的小伙伴,一定深有体验——费劲。其他agents也没有能搞定的,今天我发现了一个超级厉害的写论文解读的agent ,…...

文件重命名方法:不同路径的文件名大小写如何批量转换技巧
在文件管理中,经常要处理文件重命名的问题,尤其是涉及到不同路径下的文件名大小写转换时。下面来看云炫文件管理器如何批量转换文件名的大小写的技巧,轻松完成这项任务。 准备多个不同路径文件夹,在里面各放几个文件。接下来开始…...
深度学习中的最优化算法是什么?
在深度学习中,最优化算法主要用于调整神经网络的参数(如权重和偏差),以最小化或最大化某个目标函数(通常是损失函数)。这些算法对于训练高效、准确的深度学习模型至关重要。以下是几种在深度学习中常用的最…...
SQL执行时间过长如何优化
这个问题,其实跟慢 SQl 排查解决有点像。可以从以下这几个方面入手: 确定瓶颈 首先查看 MySQL 日志、慢查询日志、explain 分析 SQL 的执行计划、profile 分析执行耗时、Optimizer Trace分析详情等操作,确定查询执行的瓶颈在哪里。只有确定…...
局部阈值 local_threshold
Currently the operator offers only the Method adapted_std_deviation. This algorithm is a text binarization technique and provides good results for document images. 目前这个算子只提供adapted_std_deviation方法,这个算子是一个文本二值化技术…...
【C/C++】C语言的高级编程(内存分区,指针)
C语言的高级编程【内存,指针】 基本知识变量gcc size工具 内存分区指针相关定义和赋值指针加法函数指针多级指针数组指针传参 基本知识 变量 变量解释全局变量出现在代码块{}之外的变量就是全局变量局部变量一般情况下,代码块{}内部定义的变量就是自动…...

Python ❀ 使用代码实现API接口调用详解
文章目录 1. 工具准备1.1. requests代码包1.2. BurpSuite抓包工具 2. 操作过程2.1. 一个简单的请求2.1.1. Burp获取响应2.1.2. 转发获取响应 2.2. 构造GET类型URL参数2.3. 构造请求头部2.4. 构造POST类型payload数据2.4.1. urlencoded格式2.4.2. json格式 本文主要讲解常用API接…...

关于KT6368A双模蓝牙芯片的BLE在ios的lightblue大数量数据测试
测试简介 关于KT6368A双模蓝牙芯片的BLE在ios的lightblue app大数量数据测试 测试环境:iphone7 。KT6368A双模程序96B6 App:lightblue ios端 可以打开log日志查看通讯流程 测试数据:长度是1224个字节,单次直接发给KT6368A&a…...

云边协同的 RTC 如何助力即构全球实时互动业务实践
作者:即构科技 由 51 CTO 主办的“WOT 全球技术创新大会 2023深圳站”于 11 月 24 日 - 25 日召开,即构科技后台技术总监肖潇以“边缘容器在全球音视频场景的探索与实践”为主题进行分享。 边缘计算作为中心云计算的补充,通过边缘容器架构和…...

使用python连接elasticsearch
有一个困惑了好久的问题,那就是从python里面连接elasticsearch总是报错。大致长这样 一开始我是看网上把es的安全功能关闭,也就是下面的内容,这个要进入到es的docker中去改config/elasticsearch.yml配置文件,但是这样改了以后kib…...
使用elasticsearchdump迁移elasticsearch数据实战
目录 1.安装nodejs 2.安装elasticsearchdump 3.迁移 4.核对数据 5.注意事项 1.安装nodejs https://ascendking.blog.csdn.net/article/details/135509838 2.安装elasticsearchdump npm install elasticdump -g 3.迁移 elasticdump --inputhttp://用户:密码源ES地址/源…...

指向未来: 量子纠缠的本质是一个指针
指向未来: 量子纠缠的本质是一个指针 概述基本概念理解量子纠缠PythonJavaC 理解波粒二象性PythonJavaC 理解量子隧穿理解宇宙常量PythonJavaC 概述 量子纠缠 (Quantum Entanglement) 是量子系统重两个或多个粒子间的一种特殊连接, 这种连接使得即使相隔很远, 这些粒子的状态也…...

Zookeeper启动报错常见问题以及常用zk命令
Zk常规启动的命令如下 sh bin/zkServer.sh start 启动过程如果存在失败,是没办法直接看出什么问题,只会报出来 Starting zookeeper … FAILED TO START 可以用如下命令启动,便于查看zk启动过程中的详细错误 sh bin/zkServer.sh start-for…...

【数据结构 】哈夫曼编译码器
数据结构-----哈夫曼编译码器 题目题目描述基本要求算法分析 代码实现初始化编码解码打印代码打印哈夫曼树 总结 题目 题目描述 利用哈夫曼编码进行信息通信可大大提高信道利用率,缩短信息传输时间,降低传输成本。 要求:在发送端通过一个编…...

大屏项目:react中实现3d效果的环形图包括指引线
参考链接3d环形图 3d效果的环形图 项目需求实现方式指引线(线的样式字体颜色) 项目需求 需要在大屏上实现一个3d的环形图,并且自带指引线,指引线的颜色和每段数据的颜色一样,文本内容变成白色,数字内容变…...

【STM32】STM32学习笔记-FlyMCU串口下载和STLINK Utility(30)
00. 目录 文章目录 00. 目录01. 串口简介02. 串口连接电路图03. FlyMCU软件下载程序04. 串口下载原理05. FlyMCU软件其它操作06. STLINK Utility软件07. 软件下载08. 附录 01. 串口简介 串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简…...
oracle rac 12.2.0.1CPU使用率100%
oracle rac 12.2.0.1 CPU使用率100% 查看是集群的java进程"oracle.ops.opsctl.OPSCTLDriver config database"占用cpu 根据进程号查找父进程,发现是/oracle/GRID/122/perl/bin/perl /oracle/GRID/122/tfa/gcmproddb01/tfa_home/bin/tfactl.pl rediscover -mode full …...

LeetCode、2542. 最大子序列的分数【中等,排序+小顶堆】
文章目录 前言LeetCode、2542. 最大子序列的分数【中等,排序小顶堆】题目及类型思路及代码实现 资料获取 前言 博主介绍:✌目前全网粉丝2W,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...