(四)树结构---红黑树的实现

2019-05-09  本文已影响0人  曦夫

导语

1. 2-3树

1. 2-3树定义

2-3树是一种简单的由二节点和三结点组成的绝对平衡的B型树。
 二结点:即该结点有一个元素,有两个空子结点
 三结点:即该结点有两个元素,有三个空子结点
 绝对平衡:类似满二叉树,但是有些结点可以存在两个元素


2-3树基础
2. 2-3树的添加操作
  1. 对于一个空树来说,会构建成一个二节点(后续二节点都是拆分得到)
  2. 对于一个二节点,在添加一个元素后会变成(融合成)三结点
  3. 对于一个三结点,在添加一个元素后会暂时变成(融合成)四结点,之后对其进行拆分,拆分成一个父二节点拥有两个子二节点,若父二节点非根结点,再和其父节点进行向上融合

Ⅱ. (左倾)红黑树

左倾的意思?
1. 红黑树性质
2. 红黑树与2-3树的关系
3. 以2-3树解读红黑树定义性质

如2-3树与红黑树关系图:
 1.黑色结点代表(双子为黑色)二结点或三结点中的右结点
 2.红色结点可以看作三结点中的左结点

根据第一条解释,不管根结点相当于2-3树中的二结点或是三结点中的右结点,都是黑色的

1.若叶子结点不为黑色,而不为空的叶子结点为红色(关系图),则会出现相当于2-3树中四节点的情况,因为红色结点永远与父亲结点融合
2.如上面关系图中红色叶子结点16,其父亲右结点为空,若空结点为红色,则不满足左倾的性质,因为双结点为红色结点需要进行颜色反转

若一个结点为红色,相当于为2-3树中的三结点的左结点;
而三结点有两种类型的孩子,二结点和三结点,二结点准换成红黑树为黑色结点,而三结点转换为红黑树,相当于连接的为三结点的右结点,右结点也必为黑色,因此红色结点的双子必为黑色,如关系图中值为(36-50)三结点和其二结点,三结点孩子,转换成的样子。

因为红黑树只看黑节点即为黑平衡满二叉树,而满二叉树必然经过的结点数目是一样的。

4. 红黑树的添加的所有情况
5. 红黑树的添加操作及其实现

5.1. 红黑树也是基于二分搜索树,因此可以复用二分搜索树的实现,而红黑树比二分搜索树多出了颜色标识,因此增加一个boolean元素,默认规定red = true,black = false

  public class RedBlackTree<K extends Comparable<K>,V> {

    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private class Node{
        public K key;
        public V value;
        public Node left;
        public Node right;
        public boolean color;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            //默认添加的新结点为红色
            color = RED;
        }
    }

    private Node root;
    private int size;

    public RedBlackTree() {
        root = null;
        size = 0;
    }
 }

5.2. 空结点我们默认为黑色,在程序中增加一个私有方法,来判断当前结点是否为红色结点

     /**
     * 判断结点node的颜色
     * @return
     */
    private boolean isRed(Node node){
        if(node == null){
            return BLACK;
        }
        return node.color ;
    }

5.3. 添加元素的操作和二分搜索树的过程过程是一致的,在回溯到过程中维护红黑树的性质,而在每次维护某结点后,会回溯维护其父结点,此时如同2-3树中的向上融合,因此其父结点变为红色,以此回溯维护,最差会维护到根结点,此时根则为红色,而红黑树性质根结点为黑色,因此再回溯维护完整颗红黑树后,维护一次根结点为黑色

 /**
     * 向红黑树中添加一个元素
     * @param key :
     * @param value :
     */
    public void add(K key,V value){
        root = add(root,key,value);
        //回溯整颗红黑树后,根结点维护为黑结点
        root.color = BLACK;
    }

5.4. 上述准备工作已完成,接下来根据例子来实现代码的添加操作

  1. 先往空红黑树中添加第一个元素50,即情况A


    1.png
此时结点并不需要在代码进行任何操作,因为相当于一个2-3树中二结点
  1. 再添加元素36,即情况B-1


    2.png
此时结点并不需要在代码进行任何操作,因为相当于一个2-3树中的三结点
  1. 再添加元素78,即情况C-1


    3.png
此时结点需要代码进行颜色反转操作,即情况C-1
此操作相当于2-3树中对一个三结点再融合一个元素
此时需要拆分成一个父二结点和两个子二结点,其中子二结点为黑色,父二结点需要向上融合,因此为红色
代码如下:
 /**
  * 颜色反转
 */
 private void flipColors(Node node){
     node.color = RED;
     node.left.color = node.right.color = BLACK;
 }

  1. 再添加元素90,此操作即情况B-2(只是父节点非根结点,并不影响)


    4.png
此操作相当于2-3树中对一个结点进行融合,但是我们规定红黑树为左倾的,只支持添加新元素从左边融合结点,所以进行左旋操作
后继结点维持此结点的颜色不变:后继结点继承此结点颜色
而旋转后并没有改变子结点融合父节点的步骤,因此此时的新叶子结点需要融合新父亲结点(后继结点),所以新叶子结点变成红色,相当于本次添加实际上新添加78这个元素

代码实现:
 /**
     *           node                                x
     *          /  \                               /   \
     *        T4    x          向左旋转(y)       node    Z
     *            /   \      --------------->   /  \
     *          T3     Z                       T4  T3
     *
     * 左旋转
     * @param node:
     * @return :
     */
    private Node leftRotate(Node node){
        //后继结点为该结点的右儿子
        Node successor = node.right;

        //旋转
        //node结点的右儿子变成后继结点的左子树
        node.right = successor.left;
        //后继结点的左子树为node结点
        successor.left = node;

        //重新上色
        //后继结点颜色继承此结点
        successor.color = node.color;
        //新的叶子结点为红色
        node.color = RED;

        return successor;
    }

  1. 再添加元素95,即 情况C-1 和 情况B-2


    5.png
此过程相当于上述添加78和90的结合,,因此两种变换方式并非是互斥的,是满足条件即触发
  1. 再添加元素48,即 情况B-2


    6.png
此操作与情况B-2一样
  1. 再添加元素40,即情况C-3转换成情况C-2(转换的过程即情况B-2,父节点为黑是二结点添加,为红则是三结点添加,本质是一样的),最后成为情况C-1


    7.png
在本过程中,颜色转换和左旋之前介绍过,右旋和左旋同理,右旋相当于2-3树中对一个三结点融合一个元素,需要拆分成三个二结点,其中父二结点可能和其父亲结点再次发生融合
   /**
     * 右旋转:
     *         node                              x
     *        /  \                            /     \
     *       x    T4      向右旋转(y)         Z     node
     *     /   \        --------------->           /   \
     *    Z    T3                                 T3   T4
     * @param node :
     * @return :
     */
    private Node rightRotate(Node node){
        Node successor = node.left;

        //旋转
        node.left = successor.right;
        successor.right = node;

        //重新上色
        successor.color = node.color;
        node.color = RED;

        return successor;
    }

5.5. 添加操作总结

在新增结点A为红色结点,空结点为黑色结点的前提下,设新增的结点A的值为a,且与结点F,值为f有位置添加关系,且F若存在非空左儿子,则为C,值为c

红黑树操作 添加位置 处理情况 对F来说的情况 对应2-3树添加关系
左旋 A结点为F结点的右儿子,即a > f 父节点任意颜色,但左孩子为黑色 A = red && C = balck 对一个二结点融合新元素,将其转换成左边融合(左倾红黑树,不允许融合的元素大于二结点的值)
右旋 A结点为C结点的左儿子时,即a < c 新增结点的父节点为红色 A = red && C = red(C为A的父亲) 即对一个三结点融合新元素,暂时成为一个四结点
颜色反转 A结点为F结点的右儿子,即a > f 父节点为黑色,但左孩子为红色 A = red && C = red (A,C为兄弟) 对一个添加新结点的三结点暂时融合的四节点,进行拆分,拆分成三个二结点

三种操作维护时机和关系(图来源于慕课网数据结构讲解):


三种操作维护时机与关系
 /**
     * 向以node为根的红黑树中添加一个元素,
     * 并返回插入新结点后,红黑树的根
     * @param node :
     * @param key :
     * @param value :
     */
    private Node add(Node node, K key, V value) {

        if(node == null){
            size++;
            //默认插入红色结点
            return new Node(key,value);
        }

        if(key.compareTo(node.key) < 0){
            node.left = add(node.left,key,value);
        }else if (key.compareTo(node.key) > 0){
            node.right = add(node.right,key,value);
        }else {
            node.value = value;
        }

        //维护红黑树性质

        //1.当前结点右孩子是否为红色,且左孩子不为红色 --> 左旋转
        if(isRed(node.right) && !isRed(node.left)){
            node = leftRotate(node);
        }

        //2.当前结点的左孩子为红结点,左孩子的左孩子为红结点 --> 右旋转
        if(isRed(node.left) && isRed(node.left.left)){
            node = rightRotate(node);
        }

        //3.如果当前结点的左孩子和右孩子都是红色结点 ---> 反转颜色
        if(isRed(node.right) && isRed(node.left)){
            flipColors(node);
        }

        return node;
    }
6. 测试
 /**
     * 输出红黑树形状:
     *      以中序遍历形式输出红黑树
     *      以根结点为深度0,其子结点深度+1
     *      红结点以 R-结点值  形式
     *      黑结点以 B-结点值  形式
     *
     * @return :
     */
    @Override
    public String toString() {

        StringBuilder str  = new StringBuilder();
        getRBTreeStructure(root,str,0);
        return str.toString();
    }

    /**
     *  中序遍历以node为根结点的红黑树,描述红黑树形状和颜色
     * @param node: 根结点
     * @param str :
     * @param depth : 结点深度
     * @return :
     */
    private void getRBTreeStructure(Node node, StringBuilder str, int depth) {
        if(node == null){
            return;
        }
        getRBTreeStructure(node.left,str,depth + 1);
        str.append(getRBTreeValue(node,depth)+"\n");
        getRBTreeStructure(node.right,str,depth + 1);
    }

    private String getRBTreeValue(Node node, int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("— — —");
        }
        if(isRed(node)){
            sb.append("R+");
        }else {
            sb.append("B+");
        }
        sb.append(node.key);
        return sb.toString();
    }

上一篇下一篇

猜你喜欢

热点阅读