『 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后端技术领…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
高分辨率图像合成归一化流扩展
大家读完觉得有帮助记得关注和点赞!!! 1 摘要 我们提出了STARFlow,一种基于归一化流的可扩展生成模型,它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流(TARFlow&am…...
20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题
20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题 2025/6/9 20:54 缘起,为了跨网段推流,千辛万苦配置好了网络参数。 但是命令iptables -t filter -F tetherctrl_FORWARD可以在调试串口/DEBUG口正确执行。…...
【threejs】每天一个小案例讲解:创建基本的3D场景
代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone,无需安装依赖,直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景(Scene) 使用 THREE.Scene(…...
