🦄个人主页:修修修也
🎏所属专栏:数据结构
⚙️操作环境:Visual Studio 2022
目录
📌红黑树的概念
📌红黑树的操作
🎏红黑树的插入操作
🎏红黑树的删除操作
结语
📌红黑树的概念
我们之前学过了二叉搜索树和平衡二叉搜索(AVL)树, 除了它们, 还有一个被广泛运用的平衡二叉搜索树是红黑树(RB-Tree)。
红黑树是一种平衡二叉搜索树的变体, 它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉搜索树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
红黑树在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的规则如下:
- 每个结点不是红色就是黑色
- 根结点是黑色
- 如果一个结点是红色的,则它的子结点一定是黑色
- 任一结点到NULL(树尾)的任何路径上,所含的黑色结点数一定相同
- 每个NULL(树尾)空结点都是黑色的
依照红黑树的规则,红黑树能确保没有一条路径会比其他路径长出俩倍的原因是:
因此假设所有路径的黑节点数是n,那么最短路径的长度为n,而最长路径的长度为2n-1.
红黑树和AVL树的效率差异:
因为AVL树是较严格的平衡二叉搜索树,因此当数据量为n时,它的查询效率最坏大概在。而对于红黑树来讲,假设它的最短路径层高和AVL树的层高一样,那它的最坏查询效率大概就在。虽然略逊AVL树,但两者的效率仍然处于一个量级,即使有10亿数据,AVL树的查询次数在30次左右,而红黑树的查询次数在60次左右,差距不是很大,但是在插入时红黑树对于AVL树的消耗却少了不少,因此STL库中set和map的底层都是通过封装红黑树实现的.
📌红黑树的操作
🎏红黑树的插入操作
我们同样以一组数据为例, 在模拟红黑树插入的过程中学习红黑树对于插入结点的处理方法。
在此之前,我们需要先搞清楚一个问题, 那就是每个结点都有颜色,或红色或黑色,那么新插入的结点应该是红色还是黑色呢?答案是, 必须是红色!
我们简单分析一下插入新结点的情况, 假设我们现在需要给下面这颗红黑树中插入一个新结点7:
我们先来看看如果这个结点为黑节点的情况:
给大家举一个例子, 想来大家或多或少都有听说过或者经历过被人催婚的经历, 有一个很有趣的现象, 不知道大家有没有观察到, 那就是催婚的力度, 和年龄的关系不是很大,但是和同辈人有没有结婚生子关系非常大,也就是说,当家里亲戚里同辈人都没有结婚生子时,催婚的力度可能就是"天街小雨润如酥"。但是一旦同辈人中有人已经结婚生子了,那催婚的力度可就是"涧底松摇千尺雨"了。
上图中新插入黑节点的行为就像是第一个结婚生子的同辈人,明明同辈人大家一起在坚守战线, 但是他却叛变了, 这下剩下所有的结点的平衡都因为它而打破, 破坏力简直是毁灭级的, 所以我们新插入的结点一定不能是黑色的。
那假如我们插入的新节点是红结点呢?
我们可以看到,如果插入的红结点父亲是黑节点,那么红结点的插入就不会破坏任何红黑树的规则, 这就相当于同辈人没有直接结婚生子,而只是谈了一个男/女朋友,并不会真正影响到平衡的格局, 只是偶尔会遇到父节点也是红结点的情况,这个时候我们按后面学到的处理方法将他们处理一下就可以.
如果我们遇到了插入后违反红黑树规则的情况,那么红黑树的调整规则如下:
- 插入结点是根节点(即破坏了根节点是黑色的规则)--->解决方法,直接将该节点变黑
- 插入结点的父节点也是红色(即破坏了红结点的孩子必须是黑色的规则),分两种情况
- 插入结点的叔叔结点是红色: 将叔叔和父亲变为黑色, 爷爷结点变为红色, 然后继续向上判定爷爷结点是否违反了红黑树的规则并进行调整
- 插入结点的叔叔结点不存在或是黑色: 根据形态进行相应的旋转操作,旋转完成后,将旋转后的根节点变黑,将祖父结点变红即可
接下来我们就一起按照这组数据来模拟红黑树的插入过程:
17 18 23 34 27 15 9 6 8 5 25
首先插入第一个结点17:
可以看到,插入根节点时,我们违反了根结点为黑的性质,解决办法也很简单,把根节点变黑就行:
继续插入下一个结点18:
插入后没有破坏红黑树的任何性质,继续插入
插入下一个结点23:
此时违反了红黑树不能有两个连续红结点的性质,并且对新插入的结点23来讲,它的叔叔结点不存在,因此我们考虑使用旋转来解决,观察发现此时是RR型的旋转,因此我们通过左旋来解决:
左旋之后,我们需要将新的父亲结点变为黑色,原来的爷爷结点变为红色就完成了:
继续插入下一个结点34:
此时违反了红黑树不能有两个连续红结点的性质,并且对新插入的结点34来讲,它的叔叔结点是红色的,因此我们需要对将父亲叔叔变为黑色,爷爷变为红色:
然后继续将爷爷结点18作为新插入结点继续向上判定,发现他不符合根结点是黑色结点的性质,因此我们将根节点18变为黑色即可:
向上判定到了根节点就结束了, 我们继续插入下一个结点.
继续插入下一个结点27:
发现此时新插入结点27和它的父亲结点34违反了不能有两个相同红结点的规则,通过观察,我们发现他没有叔叔结点, 又因为是RL型,因此我们选择右左双旋来解决这个问题:
旋转好后,再对旋转后的根节点27和祖父结点23进行变色:
变色完成,我们的红黑树也就插入成功了.
继续插入下一个结点15:
发现并没有破坏红黑树的任何性质, 继续插入下一个结点.
继续插入下一个结点9:
发现新插入的9和其父亲结点15违反了不能有两个连续红结点的性质,同时其叔叔结点也不存在,通过观察是LL型旋转,因此我们选择右旋来解决这个问题:
右旋完成后,我们更改旋转后的根节点15和爷爷结点17进行变色,就调整好了:
继续插入下一个结点6:
发现新插入的6和其父亲结点9违反了不能有两个连续红结点的性质,同时其叔叔是红色,因此我们选择将父亲结点叔叔结点和爷爷结点变色来解决这个问题:
变色完成后,我们继续判断爷爷结点是否有违反红黑树的性质,发现没有,插入完成.
继续插入下一个结点8:
发现新插入的8和其父亲结点6违反了不能有两个连续红结点的性质,同时其叔叔结点也不存在,通过观察是LR型旋转,因此我们选择左右双旋来解决这个问题:
旋转完成后,我们再对旋转后的根节点8,和爷爷结点9进行变色, 既可完成插入:
继续插入下一个结点5:
发现新插入的5和其父亲结点6违反了不能有两个连续红结点的性质,同时其叔叔是红色,因此我们选择将父亲结点叔叔结点和爷爷结点变色来解决这个问题:
变色完成后,我们继续判断爷爷结点8是否有违反红黑树的性质,发现结点8和其父亲结点15违反了不能有两个连续红结点的性质,同时其叔叔结点27是黑色,通过观察是LL型旋转,因此我们选择右旋来解决这个问题:
右旋完成后,我们更改旋转后的根节点15和爷爷结点18进行变色,就调整好了:
继续插入最后一个结点25:
发现新插入的25和其父亲结点23违反了不能有两个连续红结点的性质,同时其叔叔34是红色,因此我们选择将父亲结点叔叔结点和爷爷结点变色来解决这个问题:
变色之后,继续判断爷爷结点27是否违反红黑树性质,发现结点27和它的父亲结点18违反了不能有两个连续红结点的性质,同时其叔叔结点8是红色,因此我们选择将父亲结点18叔叔结点8和爷爷结点15变色来解决这个问题:
变色之后,继续判断新的爷爷结点15是否违反红黑树性质,发现他违反了根节点必须为黑的性质,因此我们将他变黑,又因为已经判断到根节点, 插入操作完成.
将所有元素插入进红黑树后,我们显示出所有的空结点,验证红黑树的性质,发现是符合红黑树的性质的:
🎏红黑树的删除操作
红黑树和AVL树一样,都是要在二叉搜索树的删除插入基础上然后保持其树本身的特性.
红黑树的删除难点主要在于删除叶子黑节点。
因为有孩子结点时我们只需要按二叉搜索树的逻辑让孩子结点代替它,并让孩子结点变黑即可。
对于叶子红结点呢,因为删除它不会影响任何红黑树的性质,所以直接删除即可, 但是对于删除叶子黑节点就非常复杂,我放一张红黑树的删除操作逻辑图和二叉搜索树的删除逻辑在这里,有兴趣的朋友可以参考研究一下,这里就不详细展开了:
结语
希望这篇关于 红黑树(RB-Tree) 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
相关文章推荐
【数据结构】什么是平衡二叉搜索树(AVL Tree)?
【C++】STL标准模板库容器map
【C++】STL标准模板库容器set
【C++】模拟实现二叉搜索(排序)树
【数据结构】C语言实现链式二叉树(附完整运行代码)
【数据结构】什么是二叉搜索(排序)树?
【C++】模拟实现priority_queue(优先级队列)
【C++】模拟实现queue
【C++】模拟实现stack
【C++】模拟实现list
【C++】模拟实现vector
【C++】标准库类型vector
【C++】模拟实现string类
【C++】标准库类型string
【C++】构建第一个C++类:Date类
【C++】类的六大默认成员函数及其特性(万字详解)
【C++】什么是类与对象?