Java实现红黑树(算法导论第13章)

一、红黑树的性质

        红黑树是一种自平衡二叉树,红黑树和AVL树一样都对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。

红黑树需要满足的五条性质: 

性质一:节点是红色或者是黑色; 

在树里面的节点不是红色的就是黑色的,没有其他颜色,要不怎么叫红黑树呢,是吧。 

性质二:根节点是黑色; 

根节点总是黑色的。它不能为红。 

性质三:每个叶节点(NIL或空节点)是黑色; 

实现的时候NIL节点是个空节点用null表示,并且是黑色的

性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点); 

连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色

性质五:从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点;

从根节点到每一个NIL节点的路径中,都包含了相同数量的黑色节点。 

这五条性质就约束了红黑树可以将查找删除维持在对数时间内,算法导论中有证明一颗有n个节点的红黑树的高度至多为2lg(n+1)

《Java实现红黑树(算法导论第13章)》

二、旋转

红黑树相对于普通平衡二叉树一个很重要的操作就是旋转

假设 : 旋转节点为n ,n的左子树叫做leftChild,n的右子树叫做rightChild

1、向左旋转

《Java实现红黑树(算法导论第13章)》

rightChild的左子树作为n的右子树,

将n的右节点作为跟,

最后将n作为rightChild的左节点;

2、向右旋转

《Java实现红黑树(算法导论第13章)》

 

leftChild的右子树作为n节点左子树,

将n的左节点作为跟,

最后将n作为leftChild的左节点

三、插入

        因为要满足红黑树的这五条性质,如果我们插入的是黑色节点,那就违反了性质五,需要进行大规模调整,如果我们插入的是红色节点,那就只有在要插入节点的父节点也是红色的时候违反性质四或者是当插入的节点是根节点时,违反性质二,所以,我们应该把要插入的节点的颜色变成红色。

  • 树为空,我们没必要调整,直接讲新添加的节点作为根,并将颜色置为黑色:
  • 如果添加节点的父节点为黑色,这样并不会违反红黑树的性质,所以不必调整:
  • 当新节点的父节点为红色时,也分两种情况:

        1.叔叔节点存在且为红色,这时只需要将父,叔节点置为黑色,将祖父节点(祖父节点必定存在)置为红色,然后以祖父节点递归上面的调整过程            

         insertFixup(grandParent), 你可以想像成祖父节点为新插入的节点即可;

         2.叔节点为黑色或不存在,就需要进行旋转调整,这里讲述一种情况,其他类同:

《Java实现红黑树(算法导论第13章)》

假如我们对上面的初始图添加一个节点5

 明显现在树不满足性质4,所以需要进行旋转,而且通过一次旋转是不够的,我们需要进行两次旋转:

第一次旋转:以新添加的节点5的父节点6为根进行右旋转后:

《Java实现红黑树(算法导论第13章)》

第二次旋转,先调整颜色,将5置为黑色,1,6置为红色,以节点1进行左旋转:

《Java实现红黑树(算法导论第13章)》

四、删除

首先你要了解普通二叉树的删除操作: 
1.如果删除的是叶节点,可以直接删除; 
2.如果被删除的元素有一个子节点,可以将子节点直接移到被删除元素的位置; 
3.如果有两个子节点,这时候就可以把被删除元素的右支的最小节点(被删除元素右支的最左边的节点)和被删除元素互换,我们把被删除元素右支的最左边的节点称之为后继节点(后继元素),然后在根据情况1或者情况2进行操作。

对于删除操作,我们需要考虑几种情况(来自维基百科):

  • 如果我们删除一个红色节点(此时该节点的儿子将都为叶子节点),它的父亲和儿子一定是黑色的。所以我们可以简单的用它的黑色儿子替换它,并不会破坏属性3和4。通过被删除节点的所有路径只是少了一个红色节点,这样可以继续保证属性5。
  • 另一种简单情况是在被删除节点是黑色而它的儿子是红色的时候。如果只是去除这个黑色节点,用它的红色儿子顶替上来的话,会破坏属性5,但是如果我们重绘它的儿子为黑色,则曾经通过它的所有路径将通过它的黑色儿子,这样可以继续保持属性5。
  • 需要进一步讨论的是在要删除的节点和它的儿子二者都是黑色的时候,这是一种复杂的情况。我们首先把要删除的节点替换为它的儿子,后面代码中讨论。

五、源码实现

/**
 * @author lpf
 * @create 2018-03-22 20:32
 *
 *  红黑树实现:
 *  性质:
 *  1.节点要么红,要么黑;
 *  2.根是黑色;
 *  3.所有叶子都是黑色;(叶子为null节点)
 *  4.每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
 *  5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点
 *
 **/
public class RedBlackTree<T extends Comparable<T>> {
    private Node<T> root; // 树的跟节点
    private int size;  // 树元素个数
    //标志叶子节点表示空节点,颜色为黑色
    private Node<T> NIL = new Node<T>(null, null, null, null, Color.BLACK);
    /**
     * 节点类
     */
    private static class Node<E>{
        E value;
        Node<E> parent;
        Node<E> left;
        Node<E> right;
        Color color;
        public Node(E value, Node<E> parent, Node<E> left, Node<E> right, Color color) {
            this.value = value;
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }
    /**
     * 节点颜色
     */
    private static enum Color{
        RED,
        BLACK
    }

    /**
     * 获取叔叔节点
     * @param n 当前节点
     * @return 其叔节点
     */
    private Node<T> uncle(Node<T> n){
        Node<T> gp = grandParent(n);
        if (gp == null){
            return null;
        }
        if (n.parent == gp.left){ //若其父节点在其祖父节点左边
            return gp.right;
        } else {
            return gp.left;
        }
    }

    /**
     * 获取祖父节点
     * @param n 当前节点
     * @return 其祖父节点
     */
    private Node<T> grandParent(Node<T> n){
        if (n.parent == null) return null;
        return n.parent.parent;
    }

    /**
     * 返回最小元素
     * @return 获取某节点为根的树的最小元素
     */
    public T min(Node<T> n) {
        Node<T> min = minN(n);
        return min == NIL ? null : min.value;
    }


    /**
     * 返回最小节点
     * @param n 树根节点
     * @return 最小节点
     */
    private Node<T> minN(Node<T> n) {
        Node<T> min = n;
        while (min.left != NIL) {
            min = min.left;
        }
        return min == NIL ? null : min;
    }

    /**
     * 获取某节点为根的树的最大元素
     * @return 最大元素, 没有返回null
     */
    public T max(Node<T> n) {
        Node<T> max = maxN(n);
        return max == NIL ? null : max.value;
    }

    /**
     * 获取某节点为根的树的最大节点
     * @return 最大节点, 没有返回null
     */
    public Node<T> maxN(Node<T> n) {
        Node<T> max = n;
        while (max.right != NIL) {
            max = max.right;
        }
        return max == NIL ? null : max;
    }

    /**
     * 左旋以n节点为根的子树:
     * 1.将rightChild的左子树作为n的右子树
     * 2.将rightChild作为根
     * 3.将n节点作为rightChild的左孩子
     */
    private void leftRotate(Node<T> n){
        Node<T> rightChild = n.right;
        //1.将rightChild的左子树作为n的右子树
        //将rightChild的左子树接到n的右边
        n.right = rightChild.left;
        if(rightChild.left != NIL) rightChild.left.parent = n;

        //2.将rightChild作为根
        rightChild.parent = n.parent;
        if (n.parent == null){ //若n为树根
            root = rightChild;
        } else if (n.parent.left == n){ //若n为父亲的左孩子
            n.parent.left = rightChild;
        } else { //若n为父亲的右孩子
            n.parent.right = rightChild;
        }

        //3.将n节点作为rightChild的左孩子
        rightChild.left = n;
        n.parent = rightChild;
    }

    /**
     * 右旋以n节点为根的子树:
     *     1.将leftChild的右子树作为n的左子树
     *     2.将leftChild作为根
     *     3.将n节点作为leftChild的右孩子
     */
    private void rightRotate(Node<T> n){
        Node<T> leftChild = n.left;

        //1.将leftChild的右子树作为n的左子树
        n.left = leftChild.right;
        if (leftChild.right != NIL){
            leftChild.right.parent = n;
        }

        //2.将leftChild作为根
        leftChild.parent = n.parent;
        if (n.parent == null){ //n为树根
            root = leftChild;
        } else if (n == n.parent.left){ //n为父节点点左孩子
            n.parent.left = leftChild;
        } else{ //n为父节点右孩子
            n.parent.right = leftChild;
        }

        //3.将n节点作为leftChild的右孩子
        leftChild.right = n;
        n.parent = leftChild;
    }

    /**
     * 调整树以满足红黑树性质
     * @param n 新添加的节点
     */
    private void insertFixup(Node<T> n) {
        //若是树根
        if (n.parent == null){
            n.color = Color.BLACK;
            return;
        }

        //父节点为黑色,无须调整
        if (n.parent.color == Color.BLACK){
            return;
        }

        Node<T> u = uncle(n);
        Node<T> g = grandParent(n);
        // 1.父节点及叔节点都为红色
        if (u != null && u.color == Color.RED){
            //将parent和uncle颜色置BLACK
            n.parent.color = Color.BLACK;
            u.color = Color.BLACK;
            //将grand parent置RED
            g.color = Color.RED;
            //递归调整 grand parent, 这时可想像grand parent为新添加的红色节点
            insertFixup(g);
        } else { //父节点P是红色而叔节点是黑色或缺少
            if (n == n.parent.right && n.parent == g.left){ //n为父节点右孩子,且父节点为祖父节点的左孩子
                //以父左旋
                leftRotate(n.parent);
                n = n.left;
            } else if(n == n.parent.left && n.parent == g.right){ //n为父节点左孩子,且父节点为祖父节点右孩子
                //以父右旋
                rightRotate(n.parent);
                n = n.right;
            }
            n.parent.color = Color.BLACK; //parent颜色置为黑色
            g.color = Color.RED;
            if (n == n.parent.left && n.parent == g.left){ //n节点为父节点的左孩子,且父节点为祖父节点的左孩子
                //以祖父右旋
                rightRotate(g);
            } else{ //n节点为父节点的右孩子,且父节点为祖父节点的右孩子
                //以祖父左旋
                leftRotate(g);
            }
        }
    }


    /**
     * 删除元素
     * 类似二叉查找树的删除
     * @param t 待删除节点
     * @return 删除成功返回true, 反之返回false
     */
    public boolean remove(T t) {
        boolean removed = false;
        Node<T> n = getN(t); // 获取要删除的节点
        Node<T> replace = null; // 用于替换节点n
        Node<T> child = null; // 后继节点next的孩子节点
        if (n != null) {
            if (n.left == NIL || n.right == NIL) { // 若有最多一个非空孩子
                replace = n;
            } else { // 若有2个非空孩子, 则找其后继节点
                replace = locateNextN(n);
            }
            // 获取替换节点replace的孩子, 有可能为NIL
            child = replace.left != NIL ? replace.left : replace.right;
            // 删除节点replace, 连接replace父节点-->child节点
            child.parent = replace.parent;
            if (replace.parent == null) { // 根节点
                root = child;
            } else if (replace == replace.parent.left) { // replace为其父节点左孩子
                replace.parent.left = child;
            } else { // replace为其父节点右孩子
                replace.parent.right = child;
            }

            // 替换n节点的值为replace节点
            if (replace != n) {
                n.value = replace.value;
            }
            // 若后继节点为黑色, 则需做调整, 因为删除红色replace节点对红黑树性质无影响
            if (replace.color == Color.BLACK) {
                removeFixup(child);
            }
            removed = true;
        }
        return removed;
    }


    /**
     * 由于删除节点而做调整
     * @param n 删除节点的后继节点的孩子
     */
    private void removeFixup(Node<T> n) {
        while (n != root && n.color == Color.BLACK) {
            if (n == n.parent.left) { // n为其父节点的左孩子
                Node<T> rightBrother = rightBrother(n);
                if (rightBrother.color == Color.RED) { // 兄弟颜色为红
                    rightBrother.color = Color.BLACK;
                    n.parent.color = Color.RED;
                    leftRotate(n.parent); // 以父左旋
                    rightBrother = n.parent.right;
                }
                // 右兄弟的两个孩子都为黑色
                if (rightBrother.left.color == Color.BLACK
                        && rightBrother.right.color == Color.BLACK) {
                    rightBrother.color = Color.RED;
                    n = n.parent;
                } else if (rightBrother.right.color == Color.BLACK) { // 右兄弟右孩子为黑色
                    rightBrother.left.color = Color.BLACK;
                    rightBrother.color = Color.RED;
                    rightRotate(rightBrother);
                    rightBrother = n.parent.right;
                } else { // 右兄弟右孩子为红色或其他情况,比如为空叶子节点NIL
                    rightBrother.color = n.parent.color;
                    n.parent.color = Color.BLACK;
                    rightBrother.right.color = Color.BLACK;
                    leftRotate(n.parent);
                    n = root;
                }
            } else { // n为其父节点的右孩子
                Node<T> leftBrother = leftBrother(n);
                if (leftBrother.color == Color.RED) { // 左兄弟为红色
                    leftBrother.color = Color.BLACK;
                    n.parent.color = Color.RED;
                    rightRotate(n.parent);
                    leftBrother = n.parent.left;
                }
                if (leftBrother.left.color == Color.BLACK
                        && leftBrother.right.color == Color.BLACK) { // 左兄弟左孩子和右孩子都为黑色
                    leftBrother.color = Color.RED;
                    n = n.parent;
                } else if (leftBrother.left.color == Color.BLACK) { // 仅左兄弟左孩子为黑色
                    leftBrother.color = Color.RED;
                    leftBrother.right.color = Color.BLACK;
                    leftRotate(leftBrother);
                    leftBrother = n.parent.left;
                } else { // 左兄弟左孩子为红色
                    leftBrother.color = n.parent.color;
                    n.parent.color = Color.BLACK;
                    leftBrother.left.color = Color.BLACK;
                    rightRotate(n.parent);
                    n = root;
                }
            }
        }
        n.color = Color.BLACK;
    }

    /**
     * 获取节点的右兄弟
     * @param n 当前节点
     * @return 节点的右兄弟
     */
    private Node<T> rightBrother(Node<T> n) {
        return n == null ? null : (n.parent == null ? null : n.parent.right);
    }

    /**
     * 获取节点的左兄弟
     * @param n 当前节点
     * @return 节点的右兄弟
     */
    private Node<T> leftBrother(Node<T> n) {
        return n == null ? null : (n.parent == null ? null : n.parent.left);
    }


}

 

参考资料

算法导论第十三章

https://blog.csdn.net/sun_tttt/article/details/65445754

https://my.oschina.net/indestiny/blog/213439

    原文作者:红黑树
    原文地址: https://my.oschina.net/u/3737136/blog/1649433
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞