标签:封装 接口 不用 所有路径 mamicode 调整 部分 red 画图
封装基于 BinaryTreeOperations 的 红黑树(一种自平衡的二叉查找树)。
除了提供 BinaryTreeOperations 中的部分基础接口外,增加按键的插入 和 按键或节点指针的删除操作。
在阅读本文前,您应该先了解二叉树中的旋转是怎么回事(相关文章很多且简单,笔者不再赘述)。
讲解红黑树的教程很多,但是很多讲解并不足以让读者清楚的学会红黑树,尤其是删除操作,许多教程十分凌乱,因此本文将使用清晰的层次分类及必要的图进行讲解。
节点定义:
enum class Color :bool { RED = 0, BLACK }; struct Node { _Ty key; Node* left = nullptr; Node* right = nullptr; Node* parent = nullptr; Color color = Color::RED; Node(const _Ty& _key) :key(_key) {} };
红黑树的规则:
① 每个节点是红色或者黑色。
② 根节点是黑色。
③ 每个叶子节点是黑色(注意:这里的叶子节点指 为空的叶子节点)。
④ 如果一个节点是红色,则它的孩子必须是黑色(或者说支路上不得出现连续的红节点)。
⑤ 从任意节点到其叶子节点的所有路径中,所办含的黑色节点数相同(叶子节点同样指为空的节点)。
请务必尽快熟练的记住以上规则(尤其是 ②,④,⑤),尽管这看似复杂,但在应用中正是因为这些特性会使得红黑树没这么难。
红黑树的增删操作分为两步:
① 按二叉查找树的规则将节点插入到相关位置。
② 讨论各种情况,若红黑树失衡则采取相关方法进行调整使之重新恢复平衡。
插入操作(令插入的节点为 cur,cur 的父节点为 par,par 的兄弟节点为 uncle,par 的父节点为 gpa):
如嵌套 if else 一样,我们将插入情况分为两类(称为外层分类),再根据这两类的 子情况 进行其他分类(称为内层分类)。
注意,新插入节点 cur 一定是红色(因为这不会违背规则 ⑤,只有可能违背规则 ① ④,违背 ① 时容易处理,即插入空树时只需将其变为黑色即可)。
为何宁愿违背 ① ④ 而不宁愿违只背 ⑤(即新插节点是黑色)?(你可以理解为这会更容易实现自平衡,不用过于纠结)。
插入空树情况比较简单,后文不特地说明该情况。
① 外层分类分为:par 是黑色 或 par 是红色。
② 内层分类是在 par 是红色 的情况下分类的,这在稍后进行讲解。
现在先解决 ①:
1) par 是黑色时,直接插入即可(这不会打破平衡)。
2)par 是红色(打破规则 ④),进入 ②。(注:此时 gpa 一定是黑色,看规则 ④)。
现在解决 ② (分为 uncle 是红色 或 uncle 是黑色):
1)如图 uncle 是红色时(空的黑色节点没有画出):
如图进行变色后将 cur 指向 gpa 的节点,继续执行 1)。
直到 cur 是红色且为根节点时,直接将根节点变黑即可。或者出现 新的 uncle 是黑色 时进入后面的情况。
2)uncle 是黑色时分为四类情况(不用担心,原理都一样,分为两类也可以的,这里也可以类似 AVL 树四种旋转情况)该情况调整后便已经平衡,可直接返回。
① 直接看图,图中给出 par 是左孩子的两种情况:
图上P1,以 gpa 右旋(看!是不是类似 AVL 树的 左左_右旋!),并交换 par 和 gpa 的颜色(小的两类情况是:cur 是左孩子还是右孩子)。
图下P2,先将 gpa 的左孩子左旋,在将 gpa 右旋(看!是不是类似 AVL 树的 左右_左右旋!),然后交换 cur 和 gpa 的颜色。
② 接下来,par 是右孩子的两种情况(小的两类情况同样看 cur 是左孩子还是右孩子)。
由于 ② 与 ① 是左右对称的情况,因此交给读者自行思考(用 AVL 树的旋转方法类似的话是:右右_左旋 和 右左_右左旋),不需要笔者继续画图了吧!
至此,插入操作结束!总结......就不用了吧。接下去是删除操作,情况很多,坐稳扶好!!!(不用慌,笔者会以清晰的层次进行分类说明)。
删除操作:
待续......
标签:封装 接口 不用 所有路径 mamicode 调整 部分 red 画图
原文地址:https://www.cnblogs.com/teternity/p/RBTree.html