【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树
目录
1 -> 红黑树
1.1 -> 红黑树的概念
红黑树,是一种二叉搜索树,但在每个节点上增加了一个存储位表示节点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
1.2 -> 红黑树的性质
- 每个节点不是红色就是黑色。
- 根节点是黑色的。
- 如果一个节点是红色的,则它的两个孩子节点是黑色的。
- 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
- 每个叶子节点都是黑色的(此处的叶子节点指空节点)。
1.3 -> 红黑树节点的定义
1.4 -> 红黑树的结构
为了后续实现关联式容器更加简单,红黑树的实现中增加一个头节点,因为根节点必须是黑色的,为了与根节点区分开,将头节点给成黑色,并且让头节点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。
1.5 -> 红黑树的插入操作
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可以分为两步:
1. 按照二叉搜索树的树规则插入新节点。
2. 检测新节点插入后,红黑树的性质是否遭到破坏。
因为新节点的默认颜色为红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三,即不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
- 情况一:cur为红,p为红,g为黑,u存在且为红。
注意:此处看到的树可能是一棵完整的树,也可能是一棵子树。
如果g是根节点,调整完成后,需要将g改为黑色。
如果g是子树,g一定有双亲,且g的双亲如果是红色,就需要继续向上调整。
cur和p均为红,违反了性质三。
解决方法:将p、u改为黑,g改为红,然后把g当成cur,继续向上调整。
- 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑。
说明:
- 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
- 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成了红色。
p为g的左孩子,cur为p的左孩子,则进行右单旋转。
p为g的右孩子,cur为p的右孩子,则进行左单旋转。
p、g变色——p变黑,g变红。
- 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑。
p为g的左孩子,cur为p的右孩子,则针对p进行左单旋转。
p为g的右孩子,cur为p的左孩子,则针对p进行右单旋转。
则转换成情况二。
针对每种情况进行相应的处理即可。
1.6 -> 红黑树的验证
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
- 检测其是否满足红黑树的性质。
1.8 -> 红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(n),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以在实际运用中红黑树更多。
2 -> 红黑树模拟实现STL中的map与set
2.1 -> 红黑树的迭代器
迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以下问题:
- begin()和end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里呢?能否给成nullptr呢?
答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找到最后一个元素,此处就不行,因此最好的方式是将end()放在头节点的位置:
- operator++()与operator--()
2.2 -> 改造红黑树
2.3 -> map的模拟实现
map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可。
2.4 -> set的模拟实现
set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。
感谢各位大佬支持!!!
互三啦!!!
- 点赞
- 收藏
- 关注作者
评论(0)