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

Posted x_k

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java实现数据结构——红黑树相关的知识,希望对你有一定的参考价值。

红黑树定义

相比二叉查找树,红黑树中的节点多个颜色属性。通过颜色属性,确保了从根节点到每个叶子节点的简单路径,没有一条路径超过其他路径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查看源码

以上是关于Java实现数据结构——红黑树的主要内容,如果未能解决你的问题,请参考以下文章

红黑树

红黑树底层实现原理分析及JAVA代码实现

高阶数据结构(贰)——红黑树的基本概念和插入的实现

高阶数据结构(贰)——红黑树的基本概念和插入的实现

教你轻松理解红黑树的实现及原理

删除红黑树的整个子树会保留其属性吗?