数据结构之红黑树。2-3-4树

2-3-4树

红黑树其实是由2-3-4树演变来的,如果想要理解红黑树,可以先看看2-3-4树,其特点如下:

  • 2-节点:包含 1 个元素的节点,有 2 个子节点;
  • 3-节点:包含 2 个元素的节点,有 3 个子节点;
  • 4-节点:包含 3 个元素的节点,有 4 个子节点;
  • 元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点;而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。

2-3-4树中结点添加需要遵守以下规则:

  • 插入都是向最下面一层插入;
  • 升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
  • 向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入 2-结点中,如果父结点也是4-结点,则递归向上层升元,至到根结点后将树高加1;

接下来我们模拟一下,像一个2-3-4树中插入:

  1. 插入 1 ,不用任何特殊处理
    数据结构之红黑树。2-3-4树

  2. 插入 2,不用任何处理:
    数据结构之红黑树。2-3-4树

  3. 插入 3 ,首先临时变为一个4节点,然后进行升元:
    数据结构之红黑树。2-3-4树
    数据结构之红黑树。2-3-4树
    可以看到,中间元素进行2升元,这时候树依然满足2-3-4树规则。

  4. 插入元素 4 ,不用进行任何处理:
    数据结构之红黑树。2-3-4树

  5. 插入元素5,这时候需要进行升元,将元素4升元:
    数据结构之红黑树。2-3-4树

  6. 插入元素6,不需要额外处理:
    数据结构之红黑树。2-3-4树

  7. 插入元素7,这时候 5 6 7是一个4节点,需要升元:
    数据结构之红黑树。2-3-4树

  8. 插入元素8,不用进行任何处理:
    数据结构之红黑树。2-3-4树

  9. 插入元素9,这时候,7 8 9需要进行升元:
    数据结构之红黑树。2-3-4树

  10. 插入元素10,不需要额外处理:
    数据结构之红黑树。2-3-4树

  11. 插入元素11,9 10 11需要进行升元:
    数据结构之红黑树。2-3-4树
    可以看到,通过2-3-4树的性质、升元操作,一颗2-3-4树,总是能够保持相对的平衡。

红黑树

红黑树属于二叉搜索树,但是每个节点增加了一个存颜色的位,它的规则是:

  • 每个节点要么是红色,要么是黑色;
  • 根节点永远是黑色的;
  • 所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
  • 每个红色节点的两个子节点一定都是黑色(不能有两个连续的红节点);
  • 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;

红黑树保证了最长路径不超过最短路径的两倍,没有AVL树那么严格的平衡,但是旋转少效率高,也是O(LgN)。AVL树追求的是极致平衡,当你插入一个元素时,旋转的次数不能预估,当插入、删除特别频繁时,树就会不停地旋转,严重影响效率。

红黑树插入时对应2-3-4树规则如下:

  • 新插入的结点颜色为红色,这样才可能不会对红黑树的高度产生影响。
  • 2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
  • 3-结点对应红黑树中的黑+红子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
  • 4-结点对应红黑树中的红+黑+红子树,插入后将其修复成红色祖父+黑色父叔+红色孩子子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;

一个节点2-3-4树和红黑树表示如下:
数据结构之红黑树。2-3-4树

2个节点的2-3-4树和红黑树表示如下:

数据结构之红黑树。2-3-4树

3个节点的2-3-4树和红黑树表示如下:
数据结构之红黑树。2-3-4树

可以很明显的看到,红黑树中,每个黑色节点和其红色子节点就是对应到了红黑树中一个节点,在2-3-4树中,就是黑色节点和其红色节点是在同一层的。

红黑树中插入节点规则如下:

  • 新插入的结点颜色为红色,这样才可能不会对红黑树的高度产生影响。
  • 2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
  • 3-结点对应红黑树中的黑+红子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
    4-结点对应红黑树中的红+黑+红子树,插入后将其修复成红色祖父+黑色父叔+红色孩子子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;

红黑树保证了最长路径不超过最短路径的两倍,没有AVL树那么严格的平衡,但是旋转少效率高,也是O(LgN)。AVL树追求的是极致平衡,当你插入一个元素时,旋转的次数不能预估,当插入、删除特别频繁时,树就会不停地旋转,严重影响效率。

红黑树插入时,如果父节点是黑色,那么是不需要任何处理的,这时候实际上对应2-3-4树就是节点是一个2节点,直接加入即可

其它几种状况处理:

  1. 当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。
    1. 将“父节点”设为黑色。
    1. 将“叔叔节点”设为黑色。
    1. 将“祖父节点”设为“红色”。
    1. 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
      这种情况红黑树如下,这时候我们要插入4:
      数据结构之红黑树。2-3-4树
      对应到2-3-4树就是:
      数据结构之红黑树。2-3-4树
      这时候就需要对当前节点进行升元:
      数据结构之红黑树。2-3-4树
      对应到红黑树如下:

数据结构之红黑树。2-3-4树

这种情况下不需要旋转,只需要变色即可

  1. 当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的左子节点,同时当前节点也是父节点的左子节点,当前节点与父节点都是左节点
    这时候也就是LL型,
    比如 3-2,这时候插入 1,变成 3-2-1,
    数据结构之红黑树。2-3-4树数据结构之红黑树。2-3-4树

这时候,如果不进行调整的话,那么这个2-3-4节点就不满足2-3-4要求,那么调整逻辑就是,按照顺序排列,中间节点2可以作为新的顺序节点,调整后,让节点恢复标准 2-3-4节点:
数据结构之红黑树。2-3-4树
对应红黑树为: 数据结构之红黑树。2-3-4树
而操作过程如下:
(1)将插入节点父节点变色
(2)以插入节点父节点为轴旋转

数据结构之红黑树。2-3-4树

  1. 当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的左子节点,同时当前节点是父节点的右子节点,
    这就是所谓的LR型,这时候,对应2-3-4树如下:
    数据结构之红黑树。2-3-4树
    对应红黑树如下:
    数据结构之红黑树。2-3-4树
    这时候我们要恢复到标准的2-3-4结构:
    数据结构之红黑树。2-3-4树
    操作思路则是先将LR变为LL,然后在像LL一样操作即可。
    (1)首先将 1 节点左旋,也就是插入节点的父节点左旋,变为LL 型
    (2)按照LL型旋转操作
    数据结构之红黑树。2-3-4树
  2. 当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的右子节点,同时当前节点也是父节点的右子节点,即插入节点与其父节点都是右子节点
    这时候对应2-3-4树如下:
    数据结构之红黑树。2-3-4树

对应红黑树如下:
数据结构之红黑树。2-3-4树
这时候还是不满足2-3-4树的性质,需要调整,调整如下:
(1) 父节点变为黑色,爷爷节点变为红色
(2)爷爷节点为轴,左旋:
数据结构之红黑树。2-3-4树
这样操作之后,能够满足2-3-4树和红黑树的特质。

  1. 当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的右子节点,当前节点是父节点的左子节点
    即所谓的RL型。
    这时候对应2-3-4树如下:
    数据结构之红黑树。2-3-4树
    对应红黑树为:
    数据结构之红黑树。2-3-4树

这时候先将RL转换RR,然后按照RR操作:
(1)当前节点父节点右旋,变为RR,然后按照RR操作:
(2) 父节点变为黑色,爷爷节点变为红色
(3)爷爷节点为轴,左旋:
数据结构之红黑树。2-3-4树

这就是原理性的指导,代码实现我们可以参考下java中TreeMap的实现:

private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;
		// 必须是父节点是红色节点才会调整,黑色节点不需要
        while (x != null && x != root && x.parent.color == RED) {
            // 父节点是爷爷节点的左子节点
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                // 叔叔节点是红色,直接变色,不需要旋转
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                   // 当前节点还是父节点的右子节点,LR型,旋转,变为LL型
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    // LL型处理,将父节点设置为黑色,爷爷节点设置为红色,然后父节点为轴右旋
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
            	// 这时候父节点是爷爷节点的左子节点
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                // 叔叔节点是红色,不需要调整,直接变色即可
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                	// 当前节点是父节点的左子节点,RL型,先旋转为RR型
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    // RR型,将父节点设置为黑色,爷爷节点设置为红色,以父节点为轴,左旋
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }


private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }
private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

算法演示: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

上一篇:首届“鹤城杯”河南·鹤壁CTF网络安全挑战赛部分WP


下一篇:Leetcode 261. 以图判树(中等) 1135. 最低成本联通所有城市(中等) 1584. 连接所有点的最小费用(中等) 并查集&Kruskal最小生成树