Java实现数据结构——红黑树

2 篇文章 0 订阅
1 篇文章 0 订阅

红黑树定义

相比二叉查找树,红黑树中的节点多个颜色属性。通过颜色属性,确保了从根节点到每个叶子节点的简单路径,没有一条路径超过其他路径2倍,近似于平衡。
性质:

  1. 每个节点或是红色,或是黑色
  2. 根节点是黑色
  3. 每个叶节点是黑色
  4. 如果一个节点是红色,那么它的两个子节点都是黑色
  5. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,包含相同数目的黑色节点
    Java代码实现中,性质3:每个叶节点为黑色,默认无值叶节点指向Null

旋转

通过旋转操作,改变树中节点的指针结构,并且保持二叉查找树性质(当前节点大于等于左子树所有节点,小于右子树所有节点)。

左旋

将当前节点移动到其左孩子节点的位置,右孩子移动到当前节点的位置
步骤:

  1. 关联当前节点c和其右孩子的左孩子
  2. 关联当前节点的双亲和右孩子
  3. 关联当前节点和右孩子
    /**
     * 左旋
     *
     * @param root 根结点
     * @param c 当前结点
     * @return 根结点
     */
    public <E> RBTreeNode<E> rotateLeft(RBTreeNode<E> root, RBTreeNode<E> c) {
        RBTreeNode<E> r, cp, rl;
        if (c != null && (r = c.right) != null) {
            // 1.connect c and rl
            if ((rl = c.right = r.left) != null) {
                rl.parent = c;
            }
            // 2.connect r and cp
            if ((cp = r.parent = c.parent) == null) {
                (root = r).red = false; // done if c is root
            } else if (cp.left == c) {
                cp.left = r;
            } else {
                cp.right = r;
            }
            // 3.connect c and r
            r.left = c;
            c.parent = r;
        }
        return root;
    }

右旋

将当前节点移动到其右孩子节点的位置,左孩子移动到当前节点的位置
步骤:

  1. 关联当前节点和其左孩子的右孩子
  2. 关联当前节点的双亲和其左孩子
  3. 关联当前节点和其左孩子
    /**
     * 右旋
     *
     * @param root 根结点
     * @param c 当前结点
     * @return root 根结点
     */
    public <E> RBTreeNode<E> rotateRight(RBTreeNode<E> root, RBTreeNode<E> c) {
        RBTreeNode l, cp, lr;
        if (c != null && (l = c.left) != null) {
            // 1.connect c and lr
            if ((lr = c.left = l.right) != null) {
                lr.parent = c;
            }
            // 2.connect l and cp
            if ((cp = l.parent = c.parent) == null) {
                (root = l).red = false;
            } else if (cp.left == c) {
                cp.left = l;
            } else {
                cp.right = l;
            }
            // 3.connect c and l
            l.right = c;
            c.parent = l;
        }
        return root;
    }

插入

查找树的插入位置,可参考二叉查找树-添加元素
根据红黑树的基本性质,新增节点的颜色为红色更为方便进行操作(黑色的话会破坏性质5)
在插入节点为红色的前提下,破坏红黑树的性质有且仅有下面两种情况:
- 插入节点为根节点(空树新增节点)
- 插入节点的父节点为红色

插入节点x的父节点xp是左孩子

迭代下面操作,直到x或xp为根节点:
1. 如果x的叔父节点u为红色:将x的祖父节点xpp的黑色属性赋予给它的两个孩子,xpp设置为x节点。
2. 如果x的叔父节点u为黑色,且x为右孩子:以xp左旋(由于x和xp都是红色,不影响黑高),将x设置为xp。此时,x为左孩子。
3. 如果x的叔父节点u为黑色,且x为左孩子:xp和xpp的颜色互换,并且,以xpp做右旋,平衡黑高
算法导论截图:
父节点为左孩子
步骤2和步骤3解决的问题:
叔父节点为黑色

插入节点x的父节点xp是右孩子

迭代下面操作,直到x或xp为根节点:
1. 如果x的叔父节点u为红色:将x的祖父节点xpp的黑色属性赋予给它的两个孩子,xpp设置为x节点。
2. 如果x的叔父节点u为黑色,且x为左孩子:以xp右旋(由于x和xp都是红色,不影响黑高),将x设置为xp。此时,x为右孩子。
3. 如果x的叔父节点u为黑色,且x为右孩子:xp和xpp的颜色互换,并且,以xpp做左旋,平衡黑高

Java代码实现

    @Override
    public boolean insert(E e) {
        // 1、关联插入位置
        RBTreeNode<E> newNode = createRBTreeNode(e);
        RBTreeNode<E> parent = null; // 插入元素的父结点
        if (root == null) {
            root = newNode;
            root.red = false;
        } else {
            RBTreeNode<E> current = root;
            while (current != null) {
                if (e.compareTo(current.e) < 0) {
                    parent = current;
                    current = current.left;
                } else if (e.compareTo(current.e) > 0) {
                    parent = current;
                    current = current.right;
                } else {
                    return false;
                }
            }
            if (e.compareTo(parent.e) < 0) {
                parent.left = newNode;
            } else {
                parent.right = newNode;
            }
        }
        newNode.parent = parent;
        // 2、保持红黑树性质
        root = this.balanceInsertion(root, newNode);
        size++;
        return true;
    }
    /**
     * 平衡插入后的树
     *
     * @param root 根结点
     * @param x 插入结点
     */
    public <E> RBTreeNode<E> balanceInsertion(RBTreeNode<E> root, RBTreeNode<E> x) {
        // 1.遍历结点必为红结点
        x.red = true;
        for (RBTreeNode<E> xp, xpp, xppl, xppr; ; ) {
            // 2-1.空树
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            // 2-2.xp为黑结点 || xp为根结点
            else if (!xp.red || (xpp = xp.parent) == null) {
                return root;
            }
            // 2-3-1.xp is left-child
            // case1: a -> b
            if (xp == (xppl = xpp.left)) {
                // 2-3-1-1.x uncle is red
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 2-3-1-2.x uncle is black
                else {
                    // x is right-child
                    // case2: b -> c
                    if (x == xp.right) {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // x is left-child
                    // case3: c -> d
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            }
            // 2-3-2.xp is right-child
            else {
                // 2-3-2-1.x uncle is red
                if ((xppl = xpp.left) != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 2-3-2-2.x uncle is black
                else {
                    // x is left-child
                    if (x == xp.left) {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // x is right-child
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

总结

当插入节点的叔父节点为黑色的时候,x和xp转换为同侧,即:(xpp.left=xp & xp.left=x)或(xpp.right=xp & xp.right = x)
GitHub查看源码

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值