深度解析红黑树

一、AVL树VS红黑树

增加的最坏时间复杂度 删除的最坏时间复杂度 查询的最坏时间复杂度
AVL树 O(log N) + O(1) O(log N) + O(log N) O(N) + O(1)
红黑树 O(log N) O(log N) O(log N)


       工欲善其事必先利其器,要想搞懂红黑树,还要先学习B树。红黑树和四阶B树是完全对应的



一、B树

1、简述及特点

       B树其实就是多路的搜索树,常用于文件系统,数据库实现(一般是好几百阶),Linux进程调度等等。

Alt
       假设一个结点存储的元素个数是x,那么有以下结论:
              根结点:1 < x < m-1
              非根结点:m/2向上取整 - 1 < x < m-1
              如果有子结点,则子结点个数为x + 1


       仔细观察上面的5阶B树,其实它也是一棵多叉搜索树,结点之间大小关系是固定的,同一结点内部也是从小到大从左往右排的。



2、添加情况

       添加的元素都是添加到叶子结点的

       由于B树每个结点存储的元素有上限,添加的时候可能会造成上溢,如果没有上溢就是在结点里面找到对应位置插入即可。发生上溢后的处理方式很简单:将最中间的结点拉到父结点上面合并,两端分裂为独立的子结点,合并完后指向分裂出来的子结点。不过这么做可能会导致一直上溢到根结点。如下图:

Alt


3、删除情况

       由于B树每个结点存储的元素有下限,删除的时候可能会造成下溢,如果没有下溢就是找到元素位置直接删除即可。发生下溢后的处理方式很简单:先看看自己的兄弟结点能不能借一个结点出来。如果兄弟存储的元素比较多可以借,则执行相应的旋转操作;如果借不了,将父结点最中间的元素拉下来合并。不过这么做也许会导致一直下溢到根结点。如下图:

Alt



二、红黑树(心中有B树)

1、简述及特点

       红黑树是一种特殊的二叉搜索树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。

Alt


       红黑树有以下特点:
              结点必须是黑色或者红色
              根结点必须是黑色
              叶子结点和外部结点都是黑色的
              红色结点的孩子都是黑色结点,红色结点的父结点都是黑色结点
              从根结点到叶子结点的所有路径上不能包括两个连续的红色结点
              从任意结点到叶子结点的所有路径包含相同数量的黑色结点


       红黑树和四阶B树的等价转换:

Alt


2、添加情况

       ------父结点为黑色,直接添加即可。
       ------父结点为红色:

              ------叔叔结点为红色,添加会上溢,参考B树解决(解决后上层可能还会上溢,递归搞定)。
              ------叔叔节点为黑色,将与B树等价转换后的超级结点执行相应的旋转操作即可。


3、删除情况

       ------被删除的结点为红色,直接删除即可。
       ------被删除的结点为黑色:

              ------被删除的结点所在的与B树等价转换后的超级结点中有红色结点,直接染色即可。
              ------被删除的结点所在的与B树等价转换后的超级结点中没有红色结点:

                     ------兄弟结点为红色,执行相应的旋转操作把兄弟结点变为黑色,再按照兄弟结点为黑色的情况处理即可。
                     ------兄弟结点为黑色:

                            ------兄弟结点可以借出一个红色结点,执行相应的旋转操作即可。
                            ------兄弟结点借不出红色结点,将父结点中拉一个结点下来合并(可能会造成连续下溢,递归解决)。



4、注意点

写代码要搞明白以下几点:

① 红黑树的颜色划分?
新建的结点默认用红色会快速平衡。红黑树的空结点也是有颜色的,是黑色!

② 删除某个结点之后,父亲结点的指向?
结点已经被删除了,自然不能用父结点的left或right来获取该结点的兄弟结点。可以反其道而行之,直接判断那边为空就表示删除的是哪一边,那么兄弟结点必然在非空的一边。

③ 下溢解决时,递归调用函数问题?
递归调用的时候,其实只是假设那个父结点要被删除,事实上没有被删除,此时再用问题2的解决方法就会有问题了。此时正解应该用parent.left == node进行判断再来获取兄弟结点。



4、代码实现

	public class RedBlackTree<E> extends BBST<E> {
	
	    private static final int RED = 1;
	    private static final int BLACK = 0;
	
	    private class RBNode<E> extends Node<E> {
	        int color = RED;
	
	        public RBNode(Node parent, E element) {
	            super(parent, element);
	        }
	
	        public void ToBlack() {
	            this.color = BLACK;
	        }
	
	        public void ToRed() {
	            this.color = RED;
	        }
	
	        public void ToColor(int color) {
	            this.color = color;
	        }
	
	        @Override
	        public String toString() {
	            StringBuilder str = new StringBuilder();
	            if (this.color == RED)
	                str.append("Red_");
	            else
	                str.append("Black_");
	            str.append(this.element);
	            return str.toString();
	        }
	    }
	
	    private boolean isBlack(Node<E> node) {
	        return node == null ? true : ((RBNode<E>) node).color == BLACK;  //节点为空默认为黑色呀
	    }
	
	    private boolean isRed(Node<E> node) {
	        return node == null ? false : ((RBNode<E>) node).color == RED;
	    }
	
	    private void ToBlack(Node<E> node) {
	        ((RBNode<E>) node).ToBlack();
	    }
	
	    private void ToRed(Node<E> node) {
	        ((RBNode<E>) node).ToRed();
	    }
	
	    private void ToColor(Node<E> node, int color) {
	        ((RBNode<E>) node).ToColor(color);
	    }
	
	    @Override
	    protected Node<E> createNode(Node<E> parent, E element) {
	        return new RBNode<>(parent, element);
	    }
	
	    /**
	     * 红黑树增加结点后保持红黑树特性的方法
	     *
	     * @param node 新增的结点
	     */
	    @Override
	    protected void afterAdd(Node<E> node) {     //红黑树添加分为两大类: 1、父结点为黑色则直接添加; 2、父结点为红色再次分类
	
	        if (root == node) {                 //根结点直接刷成黑色即可
	            ToBlack(node);
	            return;
	        }
	
	        Node<E> parent = node.parent;
	        Node<E> grandparent = parent.parent;
	        if (grandparent == null)            //祖父结点为空只可能是父亲是根结点且为黑色, 那么直接插入即可
	            return;
	
	        if (isBlack(parent))                //父结点是黑色直接添加(对应情况1)
	            return;
	
	
	        //父结点为红色, 又分为两类: 1、叔叔结点为红色即超级结点状态为红黑红; 2、叔叔结点为黑色即超级结点状态为红黑或黑红
	        Node<E> uncle = grandparent.right == parent ? grandparent.left : grandparent.right;
	        if (isRed(uncle)) {                 //叔叔结点为红色, 上溢(对应情况1)
	            ToBlack(parent);
	            ToBlack(uncle);
	            ToRed(grandparent);
	            afterAdd(grandparent);          //祖父结点作为新插入的结点继续递归调用并返回
	            return;
	        }
	
	        if (grandparent.left == parent) {   //叔叔结点为黑色, 超级结点内部旋转(对应情况2)
	            if (parent.left == node) {      //LL
	                ToBlack(parent);
	                ToRed(grandparent);
	                rotateRight(grandparent);
	            } else {                        //LR
	                ToBlack(node);
	                ToRed(grandparent);
	                rotateLeft(parent);
	                rotateRight(grandparent);
	            }
	        } else {
	            if (parent.left == node) {      //RL
	                ToBlack(node);
	                ToRed(grandparent);
	                rotateRight(parent);
	                rotateLeft(grandparent);
	            } else {                        //RR
	                ToBlack(parent);
	                ToRed(grandparent);
	                rotateLeft(grandparent);
	            }
	        }
	
	    }
	
	    /**
	     * 红黑树删除结点后保持红黑树特性的方法
	     *
	     * @param node        已经删除的结点
	     * @param replacement 替代的结点
	     */
	    @Override
	    protected void afterRemove(Node<E> node, Node<E> replacement) {     //红黑树删除分为两大类: 1、要删除的结点为红色直接删除; 2、要删除的结点为黑色再次分类
	
	        if (node.parent == null)            //被删除结点是根结点直接删掉
	            return;
	
	        if (isRed(node))                    //删除的是红色结点也是直接删除即可(对应情况1)
	            return;
	
	
	        //要删除的结点为黑色再次分类: 1、黑色节结点所在的超级结点中有红色替代结点; 2、没有红色替代结点
	        if (isRed(replacement)) {
	            ToBlack(replacement);           //将替代结点刷成黑色即可(对应情况1)
	            return;
	        }
	
	
	        //删除的相当于是黑色的叶子结点(对应情况2)
	        Node<E> parent = node.parent;
	        if (parent == null)    //只存在两种情况: 1、只有一个黑色根结点(前面已处理); 2、一个根结点+一个替代结点(前面已经将替代染黑), 所有直接返回即可
	            return;
	
	        //因为该结点已经被删除了, 不能再用parent.left或者parent.right指向该结点了
	        boolean left = parent.left == null || parent.left == node;          //递归删除的时候并没有断掉指向的结点, 所以添加一个条件
	        Node<E> sibling = left ? parent.right : parent.left;
	        Node<E> grandparent = parent.parent;
	
	
	        //删除的是黑色的叶子结点再分两类: 1、兄弟结点为黑色; 2、兄弟结点为红色
	        if (left) {                                                         //被删除结点在左边
	            if (isBlack(sibling)) {     //兄弟结点为黑色(对应情况1)
	                if (isBlack(sibling.left) && isBlack(sibling.right)) {      //兄弟结点没有左右孩子(下溢), 从父亲那里拿一个下来合并
	                    if (isRed(parent)) {                                    //父结点是红色, 直接拉下来
	                        ToBlack(parent);
	                        ToRed(sibling);
	                    } else if (isBlack(parent)) {                           //父结点是黑色, 又会下溢
	                        ToBlack(parent);
	                        ToRed(sibling);
	                        afterRemove(parent, null);             //祖父结点佯装为新删除的结点继续递归调用并返回
	                    }
	
	                } else if (isRed(sibling.right)) {                          //兄弟结点有右孩子(都用RR来处理)
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling, pColor);                               //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
	                    ToBlack(parent);
	                    ToBlack(sibling.right);
	                    rotateLeft(parent);
	                } else if (isRed(sibling.left)) {                           //兄弟结点有左孩子(用RL来处理)
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling.left, pColor);                          //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
	                    ToBlack(parent);
	                    ToBlack(sibling);
	                    rotateRight(sibling);
	                    rotateLeft(parent);
	                }
	            } else if (isRed(sibling)) {     //兄弟结点为红色, 先变为黑色再处理(对应情况2)
	                ToBlack(sibling);
	                ToRed(parent);
	                rotateLeft(parent);
	                sibling = parent.right;      //旋转后要改变对应的兄弟结点
	
	                //又变为兄弟结点为黑色的情况了
	                if (isBlack(sibling.left) && isBlack(sibling.right)) {      //兄弟结点状态: 黑
	                    if (isRed(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                    } else if (isBlack(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                        afterRemove(parent, null);
	                        return;
	                    }
	                } else if (isRed(sibling.right)) {                          //兄弟结点状态: 黑红或者红黑红,  统一处理为RR
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling, pColor);
	                    ToBlack(sibling.right);
	                    ToBlack(parent);
	                    rotateLeft(parent);
	                } else if (isRed(sibling.left)) {                           //兄弟结点状态: 红黑,  统一处理为RL
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling.left, pColor);
	                    ToBlack(sibling);
	                    ToBlack(parent);
	                    rotateRight(sibling);
	                    rotateLeft(parent);
	                }
	            }
	        } else if (!left) {                                                 //被删除结点在右边(和左边删除情况完全对称)
	            if (isBlack(sibling)) {
	                if (isBlack(sibling.left) && isBlack(sibling.right)) {
	                    if (isRed(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                    } else if (isBlack(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                        afterRemove(parent, null);
	                    }
	
	                } else if (isRed(sibling.left)) {                          //兄弟结点有左孩子(都用LL来处理)
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling, pColor);                              //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
	                    ToBlack(parent);
	                    ToBlack(sibling.left);
	                    rotateRight(parent);
	                } else if (isRed(sibling.right)) {                         //兄弟结点有左孩子(都用LR来处理)
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling.right, pColor);                        //叔叔结点跟随父结点的颜色, 子结点都刷成黑色
	                    ToBlack(parent);
	                    ToBlack(sibling);
	                    rotateLeft(sibling);
	                    rotateRight(parent);
	                }
	            } else if (isRed(sibling)) {     //兄弟结点为红色, 先变为黑色再处理(对应情况2)
	                ToBlack(sibling);
	                ToRed(parent);
	                rotateRight(parent);
	                sibling = parent.left;      //旋转后要改变对应的兄弟结点
	
	                //又变为兄弟结点为黑色的情况了
	                if (isBlack(sibling.left) && isBlack(sibling.right)) {      //兄弟结点状态: 黑
	                    if (isRed(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                    } else if (isBlack(parent)) {
	                        ToBlack(parent);
	                        ToRed(sibling);
	                        afterRemove(parent, null);
	                    }
	                } else if (isRed(sibling.left)) {                           //兄弟结点状态: 红黑或者红黑红,  统一处理为LL
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling, pColor);
	                    ToBlack(sibling.left);
	                    ToBlack(parent);
	                    rotateRight(parent);
	                } else if (isRed(sibling.right)) {                          //兄弟结点状态: 黑红,  统一处理为LR
	                    int pColor = ((RBNode<E>) parent).color;
	                    ToColor(sibling.right, pColor);
	                    ToBlack(sibling);
	                    ToBlack(parent);
	                    rotateLeft(sibling);
	                    rotateRight(parent);
	                }
	            }
	        }
	    }
	}
发布了54 篇原创文章 · 获赞 5 · 访问量 4609

猜你喜欢

转载自blog.csdn.net/cj1561435010/article/details/104574736