平衡二叉搜索树——红黑树

一、背景

二叉搜索树具体较高的运行效率,其查找的时间复杂度为log(N)。但是在极端情况下,假如插入一组从大到小的数据,如图所示,则其查找的时间复杂度变为N

这里写图片描述

红黑树是在二叉搜索树的基础上,加入红黑性质,使其成为一颗平衡二叉树,从而保证其查找的时间复杂度为log(N),被广泛应用于java的集合框架中,如TreeMap,TreeSetConcurrentHashMap

二、红黑性质

一颗红黑树是满足下面红黑性质的二叉搜索树:
1)每个节点非黑即红。
2)根节点是黑色的。
3)空的叶子节点默认为黑色。
4)父节点是红色,其子节点必须为黑色。
5)从任意节点到其叶节点的每条路径上,都含有相同个数的黑色节点。
这里写图片描述

三、 基本操作

3.1. 左旋

这里写图片描述

3.2. 右旋

这里写图片描述

四、 插入

将一个新的节点按照下面顺序插入红黑树中:
1)将新节点按照二叉搜索树的插入方式插入
2)将新节点着为红色
如果插入的是节点默认为是黑色,不一定会引发冲突;反过来插入节点默认是红色也不一定只会违反“红色节点的子节点必须是黑色的”这一个条件;只是相对来说默认为红色冲突的可能较少。
3)将整棵树进行修正,使其符合红黑性质
因为插入的节点是红色节点,所以修正过程主要关注“红色节点的子节点必须是黑色的”这一特性即可,具体修正过程见4.1-4.4。

4.1. 当插入节点为根节点
直接将其着为黑色即可,如图所示。
这里写图片描述

4.2. 当插入节点的父节点为黑色
此时无需处理, 如图所示。
这里写图片描述

扫描二维码关注公众号,回复: 11149873 查看本文章

4.3. 当前节点的父节点为红色,其为祖父节点的左子节点

这种场景比较复杂,但其核心是将红色的节点移到根节点,然后将根节点设为黑色。可以细分为以下3种

4.3.1.1. 叔节点是黑色,当前节点是其父节点的左子节点
第1步将父节点设为黑色,将祖父节点设为红色;第2步以祖父节点为支点进行右旋,如图所示。

这里写图片描述

4.3.1.2. 叔节点是黑色,当前节点是其父节点的右子节点
第1步将当前节点5的父节点4进行左旋,然后4设为当前节点;
执行完后,当前节点4的父节点为红色且其位于父节点的左边,符合4.3.1.1的规则,照套即可。
这里写图片描述

4.3.1.3. 叔节点是红色

第1步将父节点和叔节点设为黑色,父节点设为红色,把祖父节点置为当前节点;
执行完后可以节点5的父节点为黑色,满足4.2的规则,照套进行。

这里写图片描述

4.4. 当前节点的父节点为红色,其为祖父节点的右子节点
规则同4,3类似,这里不再详叙,具体可以看代码。

五、 源码分析

5.1. 红黑树的节点

/**
 * @author JayLai
 * @Time 2018.01.22 22:36
 * @param <T>
 */
public class RBTNode <T extends Comparable<T>>{

    boolean color;  //颜色
    T key;  //关键字(键值)
    RBTNode<T> left;    //左孩子
    RBTNode<T> right;   //右孩子
    RBTNode<T> parent; // 父结点

    /**
     * 有参构造函数
     * @param color
     * @param key
     * @param left
     * @param right
     * @param parent
     */
    public RBTNode(boolean color, T key, RBTNode<T> left, RBTNode<T> right, RBTNode<T> parent) {
        super();
        this.color = color;
        this.key = key;
        this.left = left;
        this.right = right;
        this.parent = parent;
    }
}

5.2. 红黑树

/**
 * 红黑树
 * 关建点:将红色的节点移到根节点,然后将根节点设为黑色
 * @author JayLai
 * @Time 2018.01.23 15:41
 * @param <T>
 */
public class RBTree <T extends Comparable<T>>{

    private RBTNode<T> root; //根节点

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

    /**
     * 无参构造函数
     */
    public RBTree(){
        root = null;
    }

    /**
     * 返回指定节点的父节点
     * @param node
     * @return
     */
    private RBTNode<T> parentOf(RBTNode<T> node){
        return node != null ? node.parent : null;
    }

    /**
     * 返回指定节点的颜色
     * @param node
     * @return
     */
    private boolean colorOf(RBTNode<T> node){
        return node != null ? node.color : BLACK;
    }

    /**
     * 判断指定节点是否为红色
     * @param node
     * @return
     */
    private boolean isRed(RBTNode<T> node){
        return (node != null) && (node.color==RED) ? true : false;
    }

    /**
     * 判断指定节点是否为黑色
     * @param node
     * @return
     */
    private boolean isBlack(RBTNode<T> node){
        return !isRed(node);
    }

    /**
     * 将节点设为黑色
     * @param node
     */
    private void setBlack(RBTNode<T> node){
        if(node != null){
            node.color = BLACK;
        }
    }

    /**
     * 将节点设为红色
     * @param node
     */
    private void setRed(RBTNode<T> node){
        if(node != null){
            node.color = RED;
        }
    }

    /**
     * 设置指定节点的父节点
     * @param node
     * @param parent
     */
    private void setParent(RBTNode<T> node, RBTNode<T> parent){
        if(node != null){
            node.parent = parent;
        }
    }

    /**
     * 设置指定节点的颜色
     * @param node
     * @param color
     */
    private void setColor(RBTNode<T> node,  boolean color){
        if(node != null){
            node.color = color;
        }
    }

    /**
     * 中序遍历
     * @param node
     */
    public void inOrder(RBTNode<T> node) {
        if (node != null) {
            // 中序遍历左子树
            inOrder(node.left);
            // 访问根节点
            System.out.print("键值:" +node.key + " 颜色:" + ((isRed(node) == true ? "Red" : "Black")) + "    ");
            // 中序遍历右子树
            inOrder(node.right);
        }
    }


    /**
     * 从小到达打印整一棵树红黑树
     */
    public void printTree(){
        if(root != null){
            inOrder(root);
        }
        System.out.println();
    }

    /**
     * 对红黑树的节点x进行左旋转
     * @param x
     */
    private void leftRotate (RBTNode<T> x){

        RBTNode<T> y = x.right; //x的右子节点

        x.right = y.left;   //将y的左子节点设为x的右子节点
        if(y.left != null){
            y.left.parent = x;  //将y的左子节点的父亲设为x
        }

        y.parent = x.parent;        // 将x的父节点设为y的父节点
        if(x.parent == null){   //如果x的父节点为空
            this.root = y;
        }else{
            if(x.parent.left == x){ //如果x位于其父节点的左边
                x.parent.left = y;
            }else{      //如果x位于其父节点的右边
                x.parent.right = y;
            }
        }
        y.left = x; //将x设为y的左子节点
        x.parent = y;   //将x的父节点设为y
    }

    /**
     * 对红黑树的节点x进行右旋转
     * 
     * @param x
     */
    public void rightRotate(RBTNode<T> x){
        RBTNode<T> y = x.left;  //x的左子节点

        x.left = y.right;   //将y的右子节点设为x的左子节点
        if(y.right != null){    
            y.right.parent = x; // 将y的右子节点的父亲设为x
        }

        y.parent = x.parent;    // 将x的父节点设为y的父节点
        if(x.parent == null){   //如果x的父节点为空
            this.root = y;
        }else{
            if(x.parent.left == x){ //如果x位于其父节点的左边
                x.parent.left = y;
            }else{      //如果x位于其父节点的右边
                x.parent.right = y;
            }
        }

        y.right = x;    //将x设为y的右子节点
        x.parent = y;   //将x的父节点设为y
    }

    /**
     * 通过添加键值(key)进行节点插入
     * @param key
     */
    public void insert(T key){
        // 将新节点设为红色
        RBTNode<T> newNode = new RBTNode<T>(RED, key, null, null, null);
        if(newNode != null){
            insert(newNode);
        }
    }

    /**
     * 将节点插到红黑树中
     * @param node
     */
    public void insert(RBTNode<T> node){
        RBTNode<T> parent = null;
        RBTNode<T> current = this.root;
        int ret;

        //1. 将节点插入到二叉树中
        while(current != null){
            parent = current;
            ret = node.key.compareTo(current.key);
            if(ret > 0){
                current = current.right;
            }else{
                current = current.left;
            }
        }

        node.parent = parent;

        if(parent == null){
            this.root = node;
        }else{
            ret = node.key.compareTo(parent.key);
            if(ret < 0){
                parent.left = node;
            }else{
                parent.right = node;
            }
        }

        //2. 将其修正为红黑树
        insertFixUp(node);
    }



    /**
     * 将插入节点的二叉树修正为红黑树
     * @param node
     */
    private void insertFixUp(RBTNode<T> node){
        RBTNode<T> parent, gparent;

        // 当前节点的父节点是红色
        while(((parent = parentOf(node))!=null) && isRed(parent)){
            gparent = parentOf(parent);

            //1父节点是祖父节点的左子节点
            if(parent == gparent.left){
                //case 1.1条件:叔叔节点是红色
                //处理方法: 将父节设为黑色,将叔节点设为黑色,将祖父节点设为红色,将祖父节点设为当前节点
                RBTNode<T> uncle = gparent.right;
                if((uncle != null) && isRed(uncle)){
                    setBlack(uncle);
                    setBlack(parent);
                    setRed(gparent);
                    node = gparent; 
                    continue;
                }

                //case 1.2条件:叔叔节点是黑色,当前节点是其父节点的右子节点
                //叔节点若未为空也是黑色
                //处理方法:将父点作为新的当前节点,以新的当前节点为支点进行左旋
                if(parent.right == node){
                    RBTNode<T> tmp;
                    leftRotate(parent);
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }
                //case 1.3条件:叔叔是黑色,且当前节点是左孩子
                //处理方法: 将父节点点设为黑色,将祖父节点设为红色。以祖父节点为支点进行右旋。
                setBlack(parent);
                setRed(gparent);
                rightRotate(gparent);
            //2 父节点是祖父节点的左子节点
            } else{
                // Case 2.1条件:叔叔节点是红色
                RBTNode<T> uncle = gparent.left;
                if ((uncle!=null) && isRed(uncle)) {
                    setBlack(uncle);
                    setBlack(parent);
                    setRed(gparent);
                    node = gparent;
                    continue;
                }

                // Case 2.2条件:叔叔是黑色,且当前节点是左子节点
                if (parent.left == node) {
                    RBTNode<T> tmp;
                    rightRotate(parent);
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }

                // Case 2.3条件:叔叔是黑色,且当前节点是右子节点
                setBlack(parent);
                setRed(gparent);
                leftRotate(gparent);
            }
        }
        //将根节点设为黑色
         setBlack(this.root);
    }
}

5.3.测试代码

public class RBTreeApp {

public static void main(String[] args) {
int arr1[] = { 10 };
int arr2[] = { 10, 8};
int arr3[] = { 10, 8, 6};
int arr4[] = { 10, 8, 6, 4};
int arr5[] = { 10, 8, 6, 4, 5 };

RBTree<Integer> tree1 = new RBTree<Integer>();
RBTree<Integer> tree2 = new RBTree<Integer>();
RBTree<Integer> tree3 = new RBTree<Integer>();
RBTree<Integer> tree4 = new RBTree<Integer>();
RBTree<Integer> tree5 = new RBTree<Integer>();

for (int i = 0; i < arr1.length; i++) {
tree1.insert(arr1[i]);
}
for (int i = 0; i < arr2.length; i++) {
tree2.insert(arr2[i]);
}
for (int i = 0; i < arr3.length; i++) {
tree3.insert(arr3[i]);
}
for (int i = 0; i < arr4.length; i++) {
tree4.insert(arr4[i]);
}
for (int i = 0; i < arr5.length; i++) {
tree5.insert(arr5[i]);
}

tree1.printTree();
tree2.printTree();
tree3.printTree();
tree4.printTree();
tree5.printTree();
}
}

5.4. 测试结果

**这里写图片描述**

原创文章 32 获赞 12 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_35469756/article/details/79188118