红黑树原理、流程图、代码实现

概要

红黑树是一颗二叉搜索树,它在每个节点上增加一个存储位来表示节点的颜色,可以是RED或BLACK。
通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而近视于平衡的。

  1. 每个节点的颜色是红色或是黑色。
  2. 根节点的颜色是黑色
  3. 每个叶子节点(NIL)是黑色
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的(红色不连续)。
  5. 对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

整体架构流程

新增

新创建的节点默认为红色。

x:操作节点,xp:x的父节点 xb:x的兄弟节点 xu:xp的子节点 xg:x的祖父节点

一:x节点的父节点为空

  1. 则说明x节点已经是根节点。
  2. 将x节点设置为黑色(因根节点必须为黑色),并返回。
    在这里插入图片描述

二:xp节点不为空,且xp节点是黑色或是xg为空

  1. xp节点是黑色,x节点是红色。满足红黑树特性,直接返回即可。
  2. xg为空,则说明xp是根节点,xp一定为黑色。所以x节点为红色。满足红黑树特性。返回根节点。

xp节点不为空,且xg为空

在这里插入图片描述

xp节点不为空,且xp节点是黑色

在这里插入图片描述

三:xp是xg的左子节点,x是xp的左子节点,xb不为空,且xb为红色

  1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
  2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
  3. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
    在这里插入图片描述

四:xp是xg的左子节点,x是xp的左子节点,xp是红色。

  1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的右旋。
  2. 当x是xp的右子节点,则需要对xp进行左旋,x = xp,已方便xg的右旋。重新获取xg和xp。
  3. 需要将xp设置为黑色,xg设置为红色,在对xg进行右旋。继续循环。

xp是xg的左子节点,x是xp的左子节点,xp是红色,xu为空。

在这里插入图片描述

xp是xg的左子节点,x是xp的左子节点,xp是红色,xu为黑色。从第二个图开始。

在这里插入图片描述

五:xp是xg的右子节点,x是xp的右子节点,xb不为空,且xb为红色

  1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
  2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
  3. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
    在这里插入图片描述

六:xp是xg的右子节点,xb节点为空或是节点颜色为黑色,且xp是红色

  1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的左旋。
  2. 当x是xp的左子节点,则需要对xp进行右旋,x = xp,已方便xg的左旋。重新获取xg和xp。
  3. 需要将xp设置为黑色,xg设置为红色,在对xg进行左旋。继续循环。

xp是xg的右子节点,xb节点为空,且xp是红色

在这里插入图片描述

xp是xg的右子节点,xb节点为黑色,且xp是红色。从第二个图开始。

在这里插入图片描述

删除

  1. 根据Key查找到删除的节点(remove)。如果为空则直接返回。
  2. 寻找(remove)节点的替换(replace)节点。
    2.1 当remove左右子节点都存在,则(remove)的右子树(removeRight)的最小值(min)。
    2.2 将remove节点和min节点互换。
    2.3 如果min节点存在右子节点(minRight),则(replace)替换节点设置为minRight。否则replace设置为remove。
    2.3 当remove存在右子节点(removeRight)。则(replace)替换节点设置为removeRight。
    2.4 当remove存在左子节点(removeLeft)。则(replace)替换节点设置为removeLeft。
    2.5 当remove左右节点都不存在。则(replace)替换节点设置为remove。
  3. 当remove != replace 则,删除remove节点。
  4. 根据replace调整位置。使其满足红黑树特性。
  5. 当remove == replace 则,删除remove节点。

其中红色箭头线:表示删除节点。蓝色箭头线:表示替换节点。黄色箭头:表示平衡节点。 紫色箭头:表示选择节点。

x:操作节点,xp:x的父节点 xb:x的兄弟节点 xbs:xb的子节点

一:x节点是根据或是x的父节点为空

1.x节点是根节点则直接返回。
2.x的父节点为空,则x设置为黑色,并返回。

节点为空或是根节点
在这里插入图片描述
删除根节点,父节点为空
在这里插入图片描述

二:x节点是红色。

1.如果x是删除节点:只需要返回,在删除该节点即可。因是删除节点所以颜色随意。配合步骤2所以设置为黑色。
2.如果x不是删除节点:则xp向x方向的黑色节点比xp向xb方向的黑色节点数量少1。
3.所以需要把x设置为黑色,返回即可。
在这里插入图片描述

三:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb为红色。

  1. 因b是红色,则说明xb存在两个子节点。且两个子节点的颜色为黑色。xp为黑色。
  2. 因需要xp填补x,即需要xp进行左旋。重新获取xp和xb等节点。
  3. 因b有两个子节点,在左旋之后x还有兄弟节点。所以需要把兄弟节点设置为红色,父节点设置为黑色。并返回。
    在这里插入图片描述

四:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空。

  1. 因x的兄弟节点,存在一个子节点,且为红色。
  2. 只需xp填补x即满足红黑树特性,则需要对xp进行右旋。
  3. 如果xbs的节点是xb的左子节点,则需要先对xb进行右旋,重新获取xb和xbs。已方便xp进行左旋。
  4. 通过上一步操作,xbs已经是xb的右子节点。需要xb继承xp的颜色(左旋之后xb替代xp的位置),xbs设置为黑色。
  5. 设置xp为黑色,对xp进行左旋。即可以直接返回。

x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空,xbs是xb的右子节点,xbs为红色。

在这里插入图片描述

x是其父节点的左子节点,且x是黑色节点,且xb不为空,xbs也不为空,xbs是xb的左子节点,xbs为红色。

在这里插入图片描述

五:x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。

1.当x是删除节点,则根节点到所有子节点的黑色数量将不一致。
2.当x不是删除节点(即x是上移之后的值),则xp到各个子节点的黑色数量不一致。
3.所以b设置为红色以满足红黑树特性,x上移(直至到根节点)父节点。x = xp。重新循环
4.x上移完成之后, x是红色则执行步骤一。
5.x上移完成之后, 重新获取xb可能 xb为空。则x继续向上借数,x = xp。重新循环
6.x上移完成之后, x是根节点,则执行第二步。
7.x上移完成之后,x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤五。
8.x上移完成之后,x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤八。
在这里插入图片描述

六:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb为红色。

  1. 因b是红色,则说明xb存在两个子节点。且两个子节点的颜色为黑色。xp为黑色。
  2. 因需要xp填补x,即需要xp进行右旋。重新获取xp和b等节点。
  3. 因b有两个子节点,在右旋之后x还有兄弟节点。所以需要把兄弟节点设置为红色,父节点设置为黑色。并返回。
    在这里插入图片描述

七:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。

  1. 因x的兄弟节点,存在一个子节点,且为红色。
  2. 只需xp填补x即满足红黑树特性,则需要对xp进行右旋。
  3. 如果xbs的节点是xb的右子节点,则需要先对xb进行左旋,重新获取xb和xbs。已方便xp进行右旋。
  4. 通过上一步操作,xbs已经是xb的左子节点。需要xb继承xp的颜色(右旋之后xb替代xp的位置),xbs设置为黑色。
  5. 设置xp为黑色,对xp进行右旋。即可以直接返回。

x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。xbs是xb的右子节点,xbs是红色:示意图

在这里插入图片描述

x是其父节点的右子节点,且x是黑色节点,且xb不为空,xbs也不为空。xbs是xb的左子节点,xbs是红色:示意图

在这里插入图片描述

八:x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。

  1. 当x是删除节点,则根节点到所有子节点的黑色数量将不一致。
  2. 当x不是删除节点(即x是上移之后的值),则xp到各个子节点的黑色数量不一致。
  3. 所以b设置为红色以满足红黑树特性,x上移(直至到根节点)父节点。x = xp。
  4. x上移完成之后, x是红色则执行步骤一。
  5. x上移完成之后, 重新获取xb可能 xb为空。则x继续向上借数,x = xp。
  6. x上移完成之后, x是根节点,则执行第二步。
  7. x上移完成之后,x是其父节点的左子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤五。
  8. x上移完成之后,x是其父节点的右子节点,且x是黑色节点,且xb不为空,xb是黑色节点,xb的两个子节点都为空或是黑。则执行步骤八。
    在这里插入图片描述

左旋

将需要旋转的节点(C),变成C的右子节点(CR)的左子节点。CR上移一个层级,C下移一个层级。完成C和CR的替换。
在这里插入图片描述

右旋

将需要旋转的节点(C),变成C的左子节点(CL)的右子节点。CL上移一个层级,C下移一个层级。
在这里插入图片描述

代码实现

package study.algorithm.collection;

public class RedBlackTree<K extends Comparable<K>, V> {
    private class Node<K, V> {
        public K key;
        public V val;
        public Node<K, V> left, right, parent;
        public boolean red;

        public Node(K key, V val, Node<K, V> parent) {
            this.key = key;
            this.val = val;
            this.red = true;
            this.parent = parent;
        }

    }

    /**
     * 根节点
     */
    private Node<K, V> root;

    public V get(K key) {
        Node<K, V> node = find(root, key);
        if (node == null) {
            return null;
        }
        return node.val;
    }

    private Node<K, V> find(Node<K, V> node, K key) {
        if (node == null) {
            return null;
        }
        //判断key和当前节点的key。如果小于则从left找,大于从right找。等于则返回。
        int cmp = key.compareTo(node.key);
        if (cmp < 0) {
            return find(node.left, key);
        } else if (cmp > 0) {
            return find(node.right, key);
        } else {
            return node;
        }
    }

    public void put(K key, V val) {
        Node<K, V> current = root;
        Node<K, V> parent = root;
        boolean isRight = true;
        while (current != null) {
            parent = current;
            int cmp = key.compareTo(current.key);
            if (cmp < 0) {
                current = current.left;
                isRight = false;
            } else if (cmp > 0) {
                current = current.right;
                isRight = true;
            } else {
                current.val = val;
                break;
            }
        }
        Node<K, V> node = new Node<>(key, val, parent);
        ;
        if (parent == null) {
            root = node;
        } else if (current != parent) {
            if (isRight) {
                parent.right = node;
            } else {
                parent.left = node;
            }
        }
        root = balanceInsertion(node);
        print();
    }

    public void print() {
        if (root != null) {
            System.out.println("跟节点的值:" + root.val + " 节点颜色:" + root.red);
        }
        print(root);
        printPre(root);
    }

    private void print(Node<K, V> node) {
        if (node != null) {
            print(node.left);
            System.out.println("中序:节点值:" + node.val + " 节点颜色:" + node.red);
            print(node.right);
        }
    }
    private void printPre(Node<K, V> node) {
        if (node != null) {
            System.out.println("先序:节点值:" + node.val + " 节点颜色:" + node.red);
            printPre(node.left);
            printPre(node.right);
        }
    }


    private Node<K, V> balanceInsertion(Node<K, V> x) {
        x.red = true;
        for (Node<K, V> xp, xpp, xppl, xppr; ; ) {
            //父节点为空,则设置x为红色,并返回。
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            //父节点为黑色或祖父节点为空,则返回根节点。
            if (!xp.red || (xpp = xp.parent) == null) {
                return root;
            }
            if (xp == (xppl = xpp.left)) {
                //三:xp是xg的左子节点,x是xp的左子节点,xb不为空,且xb为红色
                //1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
                //2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
                //2. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                } else {
                    //四:xp是xg的左子节点,xb节点为空或是节点颜色是黑色,且xp是红色。
                    //1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的右旋。
                    //2. 当x是xp的右子节点,则需要对xp进行左旋,x = xp,已方便xg的右旋。重新获取xg和xp。
                    //3. 需要将xp设置为黑色,xg设置为红色,在对xg进行右旋。继续循环。
                    if (x == xp.right) {
                        rotateLeft(x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            rotateRight(xpp);
                        }
                    }
                }
            } else {
                //五:xp是xg的右子节点,x是xp的右子节点,xb不为空,且xb为红色
                //1. 因xp为红且xb为红,且x是红色。不满足红色不联系。
                //2. 解决版本是将xp和xb设置为黑,xg设置为红。因xg设置为红色,可以导致xg节点红色连续。
                //2. 所以需要将x上移到xg。x上移两个层级,x = xg。继续循环
                if (xppl != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                } else {
                    //六:xp是xg的右子节点,xb节点为空或是节点颜色为黑色,且xp是红色
                    //1. x节点和xp节点都是红色,导致不连续。所以需要完成对xg的左旋。
                    //2. 当x是xp的左子节点,则需要对xp进行右旋,x = xp,已方便xg的左旋。重新获取xg和xp。
                    //3. 需要将xp设置为黑色,xg设置为红色,在对xg进行左旋。继续循环。
                    if (x == xp.left) {
                        rotateRight(x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            rotateLeft(xpp);
                        }
                    }
                }
            }
        }
    }

    public Node<K, V> remove(K key) {
        //1. 查找到需要移除的节点。
        Node<K, V> remove = find(root, key);
        if (remove == null) {
            return null;
        }
        //2. 删除,查找右子树的最小值。进行值替换。
        Node<K, V> removeLeft = remove.left, removeRight = remove.right, replacement;
        //2.1 左右子节点都不为空
        if (removeLeft != null && removeRight != null) {
            Node<K, V> min = removeRight, minLeft;
            while ((minLeft = min.left) != null) {
                min = minLeft;
            }
            //交互remove和min的颜色
            boolean c = min.red;
            min.red = remove.red;
            remove.red = c;
            //最小值的右子节点。如果最小值存在左子节点说明min不是最小值。
            Node<K, V> minRight = min.right;
            //删除节点的父节点
            Node<K, V> removeParent = remove.parent;
            if (min == removeRight) {
                remove.parent = min;
                min.right = remove;
            } else {
                Node<K, V> minParent = min.parent;
                //设置最小值的父节点。以及最小值是父节点的左子节点还是右子节点。
                if ((remove.parent = minParent) != null) {
                    if (min == minParent.left) {
                        minParent.left = remove;
                    } else {
                        minParent.right = remove;
                    }
                }
                //设置最小值的右子节点。
                if ((min.right = removeRight) != null) {
                    removeRight.parent = min;
                }
            }
            remove.left = null;
            //设置最小值右子节点的父节点
            if ((remove.right = minRight) != null) {
                minRight.parent = remove;
            }
            //替换后最小值的左子节
            if ((min.left = removeLeft) != null) {
                removeLeft.parent = min;
            }
            if ((min.parent = removeParent) == null) {
                root = min;
            } else if (remove == removeParent.left) {
                removeParent.left = remove;
            } else {
                removeParent.right = remove;
            }
            //如果有右子节点,则替换为设置为minRight.
            if (minRight != null) {
                replacement = minRight;
            } else {
                replacement = remove;
            }
        } else if (removeLeft != null) {
            replacement = removeLeft;
        } else if (removeRight != null) {
            replacement = removeRight;
        } else {
            replacement = remove;
        }
        //删除节点
        if (replacement != remove) {
            Node<K, V> pp = replacement.parent = remove.parent;
            if (pp == null) {
                root = replacement;
            } else if (remove == pp.left) {
                pp.left = replacement;
            } else {
                pp.right = replacement;
            }
            remove.left = remove.right = remove.parent = null;
        }
        //3. 调整位置
        balanceDeletion(replacement);
        //替换节点并删除节点
        if (replacement == remove) {
            Node<K, V> pp = remove.parent;
            remove.parent = null;
            if (pp != null) {
                if (remove == pp.left) {
                    pp.left = null;
                } else {
                    pp.right = null;
                }
            }
        }
        //替换和删除节点等于根节点,说明删除的只剩跟节点了
        if(replacement == root && remove == root){
            root = null;
        }
        print();
        return remove;
    }

    private Node<K, V> balanceDeletion(Node<K, V> x) {
        for (Node<K, V> xp, xpl, xpr; ; ) {
            //1. 替换节点是空,或是替换节点是跟节点,则直接返回。
            if (x == null || x == root)
                return root;
            //2. x的父节点为空,将当前节点染黑,并返回。
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            //3. x是红色,则说明父满足红黑树特性。将x染黑。
            if (x.red) {
                x.red = false;
                return root;
            }
            //如果x是父节点的左子节点
            if ((xpl = xp.left) == x) {
                //因x是黑色,如果xpr不为空,且是红色,则说明xpr的左右子节点都存在且为黑。
                if ((xpr = xp.right) != null && xpr.red) {
                    //将xpr染黑、xp染红,在对xp进行左旋。重新获取xp和xpr。
                    xpr.red = false;
                    xp.red = true;
                    rotateLeft(xp);
                    xpr = (xp = x.parent) == null ? null : xp.right;
                }
                //xpr为空,则需要将x上移。
                if (xpr == null)
                    x = xp;
                else {
                    //xpr是黑色,xpr的左右子节点最多子存在一个,且颜色为红。
                    Node<K, V> sl = xpr.left, sr = xpr.right;
                    if ((sr == null || !sr.red) && (sl == null || !sl.red)) {
                        //xpr左右子节点都为空,则需要将xpr染红,x上移到xp。
                        xpr.red = true;
                        x = xp;
                    } else {
                        //因x的兄弟节点,存在一个子节点,且为红色。
                        //只需xp填补x即满足红黑树特性,则需要对xp进行左旋。
                        //如果xus的节点是xu的左子节点,则需要先对xu进行右旋,重新获取xu和xus。已方便xp进行左旋。
                        //通过上一步操作,xus已经是xu的右子节点。需要xu继承xp的颜色(左旋之后xu替代xp的位置),xus设置为黑色。
                        //设置xp为黑色,对xp进行左旋。即可以直接返回。

                        if (sr == null || !sr.red) {
                            //将xpr进行右旋
                            rotateRight(xpr);
                            xpr = xp.right;
                        }
                        if (xpr != null) {
                            //因xpr不为空,则xp不为空。
                            //设置xpr的颜色为xp的颜色
                            //如果xpr存在右子节点。则将该节点设置为黑色。
                            xpr.red = xp.red;
                            if ((sr = xpr.right) != null)
                                sr.red = false;
                        }
                        //因xp不为空,将xp设置为黑色,并对xp进行左旋。
                        xp.red = false;
                        rotateLeft(xp);
                        return root;
                    }
                }
            } else {
                //x在xp的右子节点。
                if (xpl != null && xpl.red) {
                    //xpl不为空,且xpl为红色。说明xpl存在左右两个子节点,且为黑色。
                    //设置xpl为黑色,xp为红色。
                    //对xp进行右旋
                    //重新获取xp和xpl
                    xpl.red = false;
                    xp.red = true;
                    rotateRight(xp);
                    xpl = (xp = x.parent) == null ? null : xp.left;
                }
                //当xpl为空,则说明需要向上借数
                if (xpl == null)
                    x = xp;
                else {
                    //xpl是黑色,xpl的左右子节点最多子存在一个,且颜色为红。
                    Node<K, V> sl = xpl.left, sr = xpl.right;
                    if ((sl == null || !sl.red) && (sr == null || !sr.red)) {
                        //xpl左右节点都为空,则xpl设置为红色,x上移
                        xpl.red = true;
                        x = xp;
                    } else {
                        if (sl == null || !sl.red) {
                            //当sl为空,则sr不为空,且sr为红色
                            //sr设置为黑色,xpl设置为红色
                            //对xpl进行左旋
                            //重新获取xp和xpl
                            sr.red = false;
                            xpl.red = true;
                            rotateLeft(xpl);
                            xpl = (xp = x.parent) == null ?
                                    null : xp.left;
                        }
                        if (xpl != null) {
                            //当xpl为空,则说明xp不为空
                            //xpl继承xp的颜色
                            //xpl的左子节点设置为黑色
                            xpl.red = xp.red;
                            if ((sl = xpl.left) != null)
                                sl.red = false;
                        }
                        if (xp != null) {
                            //因xp不为空,将xp设置为黑色,并对xp进行左旋。
                            xp.red = false;
                            rotateRight(xp);
                        }
                        x = root;
                    }
                }
            }
        }
    }

    private Node<K, V> rotateLeft(Node<K, V> p) {
        Node<K, V> r, pp, rl;
        if (p != null && (r = p.right) != null) {
            if ((rl = p.right = r.left) != null)
                rl.parent = p;
            if ((pp = r.parent = p.parent) == null)
                (root = r).red = false;
            else if (pp.left == p)
                pp.left = r;
            else
                pp.right = r;
            r.left = p;
            p.parent = r;
        }
        return root;
    }

    Node<K, V> rotateRight(Node<K, V> p) {
        Node<K, V> l, pp, lr;
        if (p != null && (l = p.left) != null) {
            if ((lr = p.left = l.right) != null)
                lr.parent = p;
            if ((pp = l.parent = p.parent) == null)
                (root = l).red = false;
            else if (pp.right == p)
                pp.right = l;
            else
                pp.left = l;
            l.right = p;
            p.parent = l;
        }
        return root;
    }


}


总结

旋转: 是哪边少往哪边旋转。
新增节点是看:父、叔、祖父节点。
新增节点旋转原理:父红叔空或是父红叔黑。

删除节点是看:父、叔、兄、兄的子节点。
删除节点旋转原理:兄红或是兄不为空切兄的颜色黑色且子节点只有一个子树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值