二叉树(4)红黑树

封装基于 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 树的旋转方法类似的话是:右右_左旋 和 右左_右左旋),不需要笔者继续画图了吧!

至此,插入操作结束!总结......就不用了吧。接下去是删除操作,情况很多,坐稳扶好!!!(不用慌,笔者会以清晰的层次进行分类说明)。

删除操作:

  待续......

猜你喜欢

转载自www.cnblogs.com/teternity/p/RBTree.html