『 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树采用的是利用平衡因子来控制;

  • 红黑树

    红黑树采用的是以颜色来判断树的结构;

由上文概念中几个规则可能引出一个问题:

  • 红黑树中的新节点应该是什么颜色?

    黑色?

    红色?

从上文中的规则关于节点的颜色中有这么两条规则:

  1. 如果一个节点为红色,其两个孩子节点必定为黑色;
  2. 对于每一个节点,从该节点到所有后代叶节点的简单路径上,都包含相同数目的黑色节点;

根据这两个规则进行引入,权衡利弊可以发现新节点无论是黑色还是红色都有可能违反两条规则中的其中一条规则;

但是对于两条规则的违反代价来说,新节点为黑色的代价要高于新节点为红色的代价;

当新节点为黑色的时候由于每条简单路径的黑色节点数量要相同,所以当插入新节点为黑色节点时其他的简单路径上必须也插入同样的新黑色节点;

而若是新节点为红色时则只需要对树的结构进行处理即可;

故新节点的颜色应该为红色;

  • 插入过程中宁可违反规则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节点存在且该节点的颜色为红色;

以该图为例;

其中这种情况下对于树(子树)的处理情况是最简单的,只需要对节点进行重新着色即可,即:

  1. parent节点变黑

  2. uncle节点变黑

  3. 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(){//...}
  1. 判断根节点_root节点的颜色是否为黑色:

    若是该节点的颜色为红色则直接返回false;

  2. 判断是否存在连续的红色节点与每条简单路径当中是否存在相同个数的黑色节点:

    定义一个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);}
    
  3. 最终对该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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

appium之联动pycharm

前置条件&#xff1a; 1.java环境安装好了 2.android-sdk安装好&#xff08;uiautomatorviewer 也可以把这个启动起来&#xff09; 3.appium安装好 4.adb devices查看下设备是否连接 pycharm入门代码--固定写法 from appium import webdriver# 定义字典变量 desired_caps …

脱离outlook的OST邮件缓存文件查看与转化PST文件教程

对于已经登录了outlook客户端且能正常收发邮件的&#xff0c;可以直接在outlook查看邮件内容和通过outlook转化为可被直接打开的pst备份文件。但对于因服务器故障或者临时拷贝备份的ost文件&#xff0c;因没有通过outlook账户类型的认证&#xff0c;是不能直接用outlook客户端打…

海外购物商城源码 带即时通讯IM源码

海外购物商城源码- 即时通讯IM源码 随着电子商务的快速发展和全球化趋势的增强&#xff0c;越来越多的消费者选择海外购物商城。海外购物商城提供了丰富的商品种类和品牌&#xff0c;满足了消费者对高质量、多样化商品的需求。而且&#xff0c;它还提供了更方便的购物体验&…

macOS系统下载安装IDEA 操作流程

目录 第一步 进入官网&#xff0c;选择箭头指向的版本 第二步 下载完成后打开&#xff0c;拖动安装包安装​编辑 第三步 点击" project"&#xff0c;在JDK下拉框选择"Download JDK" 第四步 下载完成以后&#xff0c;点击右下角的Create按钮。 第一步 进…

2024年阿里云服务器4核8G配置活动价格多少钱?

阿里云服务器4核8g配置云服务器u1价格是955.58元一年&#xff0c;4核8G配置还可以选择ECS计算型c7实例、计算型c8i实例、计算平衡增强型c6e、ECS经济型e实例、AMD计算型c8a等机型等ECS实例规格&#xff0c;规格不同性能不同&#xff0c;价格也不同&#xff0c;阿里云服务器网al…

HarmonyOS—声明式UI描述

ArkTS以声明方式组合和扩展组件来描述应用程序的UI&#xff0c;同时还提供了基本的属性、事件和子组件配置方法&#xff0c;帮助开发者实现应用交互逻辑。 创建组件 根据组件构造方法的不同&#xff0c;创建组件包含有参数和无参数两种方式。 说明 创建组件时不需要new运算…

【数据分析实战】冰雪大世界携程景区游客客源分布pyecharts地图

文章目录 引言数据集展示Python代码可视化展示本人浅薄分析 写在最后 今年冬天&#xff0c;哈尔滨冰雪旅游"杀疯了"&#xff0c;在元旦假期更是被南方游客"包场"。据哈尔滨市文化广电和旅游局提供大数据测算&#xff0c;截至元旦假日第3天&#xff0c;哈尔…

RocketMQ Dashboard 详解

RocketMQ Dashboard 是 RocketMQ 的管控利器&#xff0c;为用户提供客户端和应用程序的各种事件、性能的统计信息&#xff0c;支持以可视化工具代替 Topic 配置、Broker 管理等命令行操作。 一、介绍​ 功能概览​ 面板功能运维修改nameserver 地址; 选用 VIPChannel驾驶舱查…

OpenCV——双边滤波

目录 一、双边滤波二、C代码三、python代码四、结果展示 OpenCV——双边滤波由CSDN点云侠原创。如果你不是在点云侠的博客中看到该文章&#xff0c;那么此处便是不要脸的爬虫与GPT。 一、双边滤波 双边滤波是一种综合考虑滤波器内图像空域信息和滤波器内图像像素灰度值相似性的…

综述:自动驾驶中的 4D 毫米波雷达

论文链接&#xff1a;《4D Millimeter-Wave Radar in Autonomous Driving: A Survey》 摘要 4D 毫米波 (mmWave) 雷达能够测量目标的距离、方位角、仰角和速度&#xff0c;引起了自动驾驶领域的极大兴趣。这归因于其在极端环境下的稳健性以及出色的速度和高度测量能力。 然而…

js逆向第22例:猿人学第18题jsvmp洞察先机

文章目录 一、前言二、定位关键参数1、处理CryptoJS加密2、被加密的值`value`和密钥`secret`是怎么来的三、代码实现一、前言 任务十八:抓取这5页的数字,计算加和并提交结果 标题已经给到提示jsvmp,这里先了解一下它: jsvmp技术提供了一种将JS代码编译成二进制指令集的方法…

小程序 自定义组件和生命周期

文章目录 ⾃定义组件创建⾃定义组件声明组件编辑组件注册组件 声明引⼊⾃定义组件⻚⾯中使⽤⾃定义组件定义段与⽰例⽅法组件-⾃定义组件传参过程 小程序生命周期应用生命周期页面生命周期页面生命周期 ⾃定义组件 类似vue或者react中的自定义组件 ⼩程序允许我们使⽤⾃定义组件…

CSS||选择器

目录 作用 分类 基础选择器 标签选择器 ​编辑类选择器 id选择器 通配符选择器 作用 选择器&#xff08;选择符&#xff09;就是根据不同需求把不同的标签选出来这就是选择器的作用。 简单来说&#xff0c;就是选择标签用的。 选择器的使用一共分为两步&#xff1a; 1.…

java-包详解

1、包介绍 为了更好的组织类&#xff0c;用于区别类名的命名空间&#xff0c;其实就是基于工程的一个文件路径&#xff0c;如&#xff1a; 2、作用 三个作用&#xff1a; 1&#xff09;区分相同名称的类。 2&#xff09;能够较好地管理大量的类。 3&#xff09;控制访问范围…

云边协同的 RTC 如何助力即构全球实时互动业务实践

作者&#xff1a;即构科技 由 51 CTO 主办的“WOT 全球技术创新大会 2023深圳站”于 11 月 24 日 - 25 日召开&#xff0c;即构科技后台技术总监肖潇以“边缘容器在全球音视频场景的探索与实践”为主题进行分享。 边缘计算作为中心云计算的补充&#xff0c;通过边缘容器架构和…

RTMP对接腾讯云问题记录

RTMP对接腾讯云问题分析报告 问题现象及原因分析 1. 连不上腾讯云RTMP服务器 连不上腾讯云RTMP服务器&#xff0c;抓包显示服务器在握手完成后&#xff0c;主动断开了当前TCP链接。问题原因可能是connect中的tcUrl不能把域名转为IP&#xff0c;导致在握手不久服务器主动断开…

【创作活动】ChatGPT 和文心一言哪个更好用?

文章目录 文心一言优点缺点 ChatGPT优点缺点 Java编码能力比较对人工智能的看法 ChatGPT是由OpenAI开发的交互式AI大模型&#xff0c; 文心一言是由百度研发的知识增强大语言模型&#xff0c;本文从Java开发的角度对比一下哪个更好用&#xff08;本文仅用于投稿CSDN创造活动&am…

ERP进出库+办公用品管理系统

系统架构 简介系统架构部分页面结构图UML逻辑图办公用品入出库 简介 本系统适用于ERP企业公司职员关于系统化的申请相关办公用品&#xff0c;提高整体系统整合行&#xff0c;加大上下级之间的联系&#xff0c;规避因人员过多&#xff0c;而浪费人力在简单重复的工作中&#xf…

PTA 6-11 先序输出叶结点

本题要求按照先序遍历的顺序输出给定二叉树的叶结点。 函数接口定义&#xff1a; void PreorderPrintLeaves( BinTree BT ); 其中BinTree结构定义如下&#xff1a; typedef struct TNode *Position; typedef Position BinTree; struct TNode{ElementType Data;BinTree Left…

uni-app中代理的两种配置方式

方式一: 在项目的 manifest.json 文件中点击 源码视图 在最底部的vue版本下编写代理代码 方式二: 在项目中创建 vue.config.js 文件然后进行配置 在页面中发起请求 完整的url&#xff1a;http://c.m.163.com/recommend/getChanListNews?channelT1457068979049&size10 …