当前位置: 首页 > news >正文

红黑树深入剖析【C++】

目录

一、红黑树概念

 二、红黑树节点结构设计

三、插入操作

 处理情况1

 处理情况2

处理情况3

 插入总结:

 四、插入操作源码

五、红黑树验证


一、红黑树概念

 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍(由规则二三四来保证),因而是接近平衡的。因为最长路径为一黑一红,最短路径为全黑。

 红黑树还必须满足以下规则:

1. 每个结点不是红色就是黑色(非红即黑)
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(没有连续的红色)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径上黑色节点的数量相等)(从这一规则也可以得出,在插入一新节点时,该节点必须为红,才会满足该条件)
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点

 规则四演示:

 二、红黑树节点结构设计

 因为红黑树节点非红即黑,所以可以用枚举的思想来例举红节点和黑节点。因为在插入的过程中,可能会存在翻转的情况,所以就需要一个节点的父节点_parent,左孩子_left,右孩子_right。

enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{ RBTreeNode<K, V>* _left;  // 左孩子RBTreeNode<K, V>* _right;  // 右孩子RBTreeNode<K, V>* _parent;  // 红黑树需要旋转,为了实现简单给出该字段pair<K, V> _kv;  //值域Colour _col;  //颜色RBTreeNode(const pair<K, V>& kv)  //初始化:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv){}
};

三、插入操作

根据规则4,可以得出在插入一个新节点的时候,这个节点一定是为红色的(上已证)。在插入红色节点的时候可能还是会造成红红节点的冲突(违反规则3),所以我们还需要进行变色+旋转的处理方法

 在插入新节点后,因为新节点的默认颜色是红色,因此:如果其父亲节点的颜色是黑色,没有违反红黑树任何规则,则不需要调整;但当新插入节点的父亲节点颜色为红色时,就违反了规则3不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:为方便我们进行变色和旋转的处理,我们定义父亲节点为p,叔叔节点为u,祖父节点为g,当前节点为cur(新增)。实际上,根据红黑树的规则,我们可以确定要发生变色或旋转操作时,cur、p、g节点一定是红、红、黑,如果不满足这种情况,那么肯定这颗红黑树在次之前就违背的红黑树的规则。所以变化操作主要取决于叔叔节点u的颜色。如下抽象图表示,s/b/c/de代表的是满足规则的子树。

 

 处理情况1

cur为红,p为红,g为黑,u存在且为红

 处理方法:叔叔u和父亲p变黑,祖父g变红,再往上进行处理,如果g是根节点,需要把g再变黑,因为根节点必须是黑(规则2)。再往上处理的过程中因为会存在当前g的父节点为红的情况,又再次冲突,所以要进行处理.

 

 

 处理情况2

cur为红且在外侧,p为红,g为黑,u不存在/u存在且为黑

u的两种情况:

1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。

 

⒉.如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。
 

 

 处理方法:单旋+变色。按p节点进行右旋,此时p为红,g为黑,再将p和g的颜色进行交换,即满足红黑树规则。(若p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色--p变黑,g变红)。处理完成后不需要再进行往上处理,因为此时p为黑,p的父亲节点为黑或红都不会对树产生影响。

处理情况3

cur为红且在内侧,p为红,g为黑,u不存在/u存在且为黑

 处理方法:双旋+变色

u不存在情况:

若p在g的左侧,cur在内侧,则先对g的左子树进行左旋,在对g这棵子树进行右旋;反之,p在g的右侧,cur在内存,就先右旋再左旋再交换cur和g的颜色。

 u存在情况:

像这种情况,都是由情况一经过处理后得来的,此时就需要先对g的左子树进行左单旋,旋转后,就会变为情况二,此时再根据情况二的解决方法进行解决,右旋+变色,旋转后将cur和g的颜色进行交换即可。(如果最开始p是在右子树,则操作相反,先右旋再左旋变色。)

 插入总结:

1.红黑树插入的节点一定为红色

2.处理三种可能的情况关键在于叔叔节点u

3.u存在且为红(情况一),将p和u节点变黑,g节点变红,若g为根节点就将g变为黑色

4.情况二和情况三都是由情况一经过变化后得来的

4.u不存在或存在且为黑,插入的节点cur在p的外侧(情况二),若p是左子树就进行右旋+交换p、g颜色;若p是右子树,反之,左旋+交换颜色。

5.u不存在或存在且为黑,插入节点cur在p的内侧(情况三),若p是左子树就先进行左旋变为情况二,再进行右旋+交换颜色。反之p为右子树,先进行右旋变为情况二,在进行左旋+交换颜色。

 四、插入操作源码

插入源码: 

	bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;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);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED)  // parent为红色就需要变色处理,因为插入的节点为红{Node* grandfater = parent->_parent;  //祖父节点assert(grandfater);assert(grandfater->_col == BLACK);// 关键看叔叔if (parent == grandfater->_left){Node* uncle = grandfater->_right;  //叔叔节点// 情况一 : uncle存在且为红,变色+继续往上处理if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;  // 往上处理的时候把祖父当做插入的节点即curparent = cur->_parent;}// 情况二+三:uncle不存在 + 存在且为黑else{// 情况二:右单旋+变色//     g //   p   u// cif (cur == parent->_left){RotateR(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:左右单旋+变色//      g //   p     u//     cRotateL(parent);RotateR(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}else // (parent == grandfater->_right){Node* uncle = grandfater->_left;// 情况一if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfater->_col = RED;// 继续往上处理cur = grandfater;parent = cur->_parent;}else{// 情况二:左单旋+变色//     g //   u   p//         cif (cur == parent->_right){RotateL(grandfater);parent->_col = BLACK;grandfater->_col = RED;}else{// 情况三:右左单旋+变色//     g //   u   p//     cRotateR(parent);RotateL(grandfater);cur->_col = BLACK;grandfater->_col = RED;}break;}}}_root->_col = BLACK;return true;}

旋转操作的源码解析可以参考这篇博文:http://t.csdn.cn/iyMac

左旋:

	void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppNode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}}

右旋:

	void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppNode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}

五、红黑树验证

验证方法:判断从根节点起的每一条子路径中的黑节点数是否相等(规则4)。利用递归的思想遍历每一条路径。再遍历第一条路径的时候,设置一个benchmark记录黑色节点数量,因为规则4,所以后面每一条路径的黑节点数应该都要与之相等,如果不等则不是红黑树。再遍历每一条路径的同时,也可以寻找是否存在连续的红节点(规则三),存在则不满足为红黑树。依次递归每一条路径。

源码:

	bool IsBalance(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根节点不是黑色" << endl;return false;}// 黑色节点数量基准值int benchmark = 0;return PrevCheck(_root, 0, benchmark);}bool PrevCheck(Node* root, int blackNum, int& benchmark){if (root == nullptr){if (benchmark == 0)  // 遍历第一条路径的时候,记录他的黑节点数,后面每一条路径的黑节点数一个都和他相等{benchmark = blackNum;return true;}if (blackNum != benchmark){cout << "某条黑色节点的数量不相等" << endl;return false;}else{return true;}}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return PrevCheck(root->_left, blackNum, benchmark)&& PrevCheck(root->_right, blackNum, benchmark);}void TestRBTree()
{size_t N = 1000;srand(time(0));RBTree<int, int> t1;for (size_t i = 0; i < N; ++i){int x = rand();cout << "Insert:" << x << ":" << i << endl;t1.Insert(make_pair(x, i));}cout << "IsBalance:" << t1.IsBalance() << endl;  //打印1则是红黑树,否则不是
}

http://www.lryc.cn/news/99022.html

相关文章:

  • 教育机构视频播放时观看行为分析有哪些应用?
  • Jmeter+验证json结果是否正确小技巧
  • Spring 6.0官方文档示例(22): singleton类型的bean和prototype类型的bean协同工作的方法(一)
  • Android平台GB28181设备接入侧如何同时对外输出RTSP流?
  • el-Cascader 中div上绑定keyDown事件
  • elementUI 表格滚动分页加载请求数据
  • JAVA面试总结-Redis篇章(五)——持久化
  • 【数据结构】·顺序表函数实现·赶紧学起来呀
  • C++,类和对象-多态,制作饮品
  • 网站分析:学习如何分析目标网站的页面结构和URL规律,确定爬取目标和策略。
  • 《向量数据库指南》:向量数据库Pinecone如何集成数据湖
  • Vue3中使用pinia
  • Mysql中(@i:=@i+1)的介绍
  • Nexperia和KYOCERA AVX Components Salzburg 就车规氮化镓功率模块达成合作
  • 数据库应用:Redis安装部署
  • 7.Docker-compose
  • 多线程:管程法
  • 7.1 String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
  • 【C++STL标准库】容器适配器
  • 2023深圳杯(东三省)数学建模ABC题思路及代码
  • Set集合类详解(附加思维导图)
  • 【vue3】vue3接收props以及emit的用法
  • 【Lua学习笔记】Lua入门
  • LLM Data Pipelines: 解析大语言模型训练数据集处理的复杂流程
  • 如何使用postman判断返回结果是否正确
  • A General framework for Prompt
  • 使用python将PDF转word
  • CMU 15-445 -- Logging Schemes - 17
  • 逻辑回归分析实战(根据鸢尾花的性质预测鸢尾花类别)
  • 【每日一题】2050. 并行课程 III