红黑树讲解(图解概念+源码分析)纯干货!!!

红黑树讲解(图解概念+源码分析)纯干货!!!

b站:小刘老师学习视频笔记+总结

一.树结构入门(树基础、引入红黑树)

1.1树结构常用语

在这里插入图片描述

1.路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为‘’路径”。
2.:树顶端的节点称为根。一棵树只有一个根, 如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。 A是根节点。
3.父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
4.子节点:一个节点含有的子树的节点称为该节点的子节点; F、G是C节点的子节点。
5.兄弟节点:具有相同父节点的节点互称为兄弟节点; F、 G节点互为兄弟节点。
6.叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、 G都是叶子节点。
7.**子树:**每个节点都可以作为子树的根,它和它所有的子节点子节点的子节点等都包含在子树中。
8.节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
9.深度:对于任意节点n,n的深度为从根到n的唯一路径长, 根的深度为0(从 上往下看)。
10.高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0(从下往上看)。

1.2二叉搜索树

1.2.1查找节点

查找某个节点,我们必须从根节点开始查找。
①、查找值比当前节点值大,则搜索右子树;
②、查找值等于当前节点值,停止搜索(终止条件) ;
③、查找值小于当前节点值,则搜索左子树;

1.2.2插入节点

要插入节点,必须先找到插入的位置。与查找操作相似,由于二叉搜索树的特殊性,
待插入的节,点也需要从根节点开始进行比较,小于根节则与根节点左子树比较,
反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置。

1.2.3遍历

前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点
层次遍历:从上到下,从左到右。

1.2.4查找最大值、最小值算法

要找最小值,先找根的左节点,然后一直找这个左节点的左节点,直到找到没有左节点的节点,那么这个节点就是最小值。
同理要找最大值,一直找根节点的右节点, 直到没有右节点,则就是最大值。

1.2.5删除节点

1.删除没有子节点的节点

​ 要删除叶节点,只需要改变该节点的父节点引用该节点的值,即将其引用改为null即可。

在这里插入图片描述

2.删除有一个子节点的节点

​ 删除有一个子节点的节点,我们只需要将其父节点原本指向该节点的引用,改为指向该节点的子节点即可

在这里插入图片描述

3.删除有两个子节点的节点

​ 当删除的节点存在两个子节点,那么删除之后,两个子节点的位置我们就没办法处理了。既然处理不了,我们就想到一种办法,用另一个节点来代替被删除的节点,那么用哪一个节点来代替呢?
​ 我们知道二叉搜索树中的节点是按照关键字来进行排列的,某个节点的关键字次高节点是它的中序遍历后继节点。用后继节点,点来代替删除的节点,显然该二叉搜索树还是有序的。

在这里插入图片描述

在这里插入图片描述

4.删除有必要吗?

​ 通过上面的删除分类讨论,我们发现删除其实是挺复杂的,那么其实我们可以不用真正的删除该节点,只需要在Node类中增加一一个标识字段isDelete,当该字段为true时,表示该节点已经删除,反之则没有删除。这样删除节点就不会改变树的结构了。影响就是查询时需要判断一下节点是否已被删除。

1.3二叉搜索树的效率

在这里插入图片描述

1.4普通二叉搜索树的致命缺陷

​ 二叉搜索树可能退化成链表,相应的,二叉搜索树的查找操作是和这棵树的高度相关的,而此时这颗树的高度就是这颗树的节点数n,同时二叉搜索树相应的算法全部退化成 O(n) 级别。

在这里插入图片描述

1.5AVL树简介

​ 平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

平衡树基于这种特点可以保证不会出现大量节点偏向于一边的情况!(插入或者删除时,会发生左旋,右旋操作。使这棵树再一次保持左右平衡)

1.6为什么有了AVL树我们还需要红黑树?

​ 虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在O(logn),不过却不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入删除节点的时候,几乎都会破坏平衡树的第二个规则 ,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树! ! !

二.红黑树原理讲解

1.红黑树的性质

1.每个节点要么是黑色,要么是红色。

2.根结点是黑色。

3.每个叶子节点(NIL:空)是黑色。

4.每个红色节点的两个子节点一定都是黑色。

5.任意一节点到每个叶子节点的路径都包含数量相同的黑结点。俗称:黑高

从性质5又可以推出:如果一个节点存在黑子节点,那么该结点肯定有两个子节点。
在这里插入图片描述

红黑树并不是一个完美平衡叉查找树,从图上可以看到,根结点P的左子树显然比右子树高,但左子树和右子树的黑结点的层数是相等的,也即任意一个结点到到每 个叶子结点的路径都包含数量相同的结点(性质5)。所以我们叫红黑树这种平衡为黑色完美平衡

2.前面讲到红黑树能自平衡,它靠的是什么?

三种操作:左旋、右旋和变色。

1.变色:结点的颜色由红变黑或由黑变红。
2.左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
3.右旋:以某个结点作为支点(旋转结点),左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变

左旋、右旋示意图

在这里插入图片描述

在这里插入图片描述

(ps:本人理解 我爹当我儿子,我儿子去当我爹儿子hhhh)

3.红黑树的查找

在这里插入图片描述

自行举例,比较简单不在概述。

4.红黑树的插入

插入操作包括两部分工作:

1.查找插入的位置

2.插入后自平衡

注意:插入节点,必须为红色,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。
但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。(即黑色一定破坏,而红树可能破坏可能不破坏

红黑树插入节点情景分析

情景1:红黑树为空树

最简单的一种情景,直接把插入结点作为根结点就行
注意:根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。

情景2:插入结点的Key已存在

处理:更新当前节点的值,为插入节点的值

在这里插入图片描述

情景3:插入结点的父结点为黑结点

由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。

在这里插入图片描述

情景4:插入节点的父节点为红色

再次回想下红黑树的性质2:根结点是黑色。如果插入节点的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这一点很关键,因为后续的旋转操作肯定需要祖父结点的参与。

插入情景4.1:叔叔结点存在并且为红结点
依据红黑树性质4可知,红色节点不能相连==>祖父结点肯定为黑结点;
因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红
处理:
1.将P和U节点改为黑色
2将PP改为红色
3.将PP设置为当前节点,进行后续处理

在这里插入图片描述

可以看到,我们把PP结点设为黑色了,如果PP的父结点是黑色,那么无需再做任何处理;
但如果PP的父结点是红色,则违反红黑树性质了。所以需要将PP设置为当前节点,继续做插入操作自平衡处理,直到平衡为止。

插入情景4.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
注意:单纯从插入前来看,叔叔节点非红即空(NIL节点) ,否则的话破坏了红黑树性质5,此路径会比其它路径多一个黑色节点。

在这里插入图片描述

插入情景4.2.1:新插入节点,为其父节点的左子节点(LL红色情况)

在这里插入图片描述

处理:

1.变颜色:将p设置为黑色,将pp设置为红色。

2.对pp节点进行右旋。

插入情景4.2.2:新插入节点,为其父节点的右子节点(LR红色情况)

在这里插入图片描述

处理:

  1. 对P进行左旋。
  2. 将P设置为当前节点,得到LL红色情况。
  3. 按照LL红色情况处理(1.变颜色2.右旋PP)

在这里插入图片描述

插入情景4.3:叔叔结点不存在或者为黑结点,并且插入结点的父亲结点是祖父结点的右子结点。

该情景对应4.2情景,只不过方向相反,这里不在赘述。

在这里插入图片描述

三.手写红黑树

①创建RBTree,定义颜色
②创建RBNode
③辅助方法定义: parentOf(node),isRed\black(node), setRed(node), setBlack(node), inOrderPrint()
④左旋方法定义: leftRotate(node)
⑤右旋方法定义: rightRotate(node)
⑥公开插入接口方法定义: insert(K key, V value);
⑦内部插入接口方法定义: insert(RBNode node);
⑧修正插入导致红黑树失衡的方法定义: insertFIxUp(RBNode node);
⑨测试红黑树正确性

public class RBTree<K extends Comparable<K>,V >{
    
    
    private static final boolean RED=true;
    private static final boolean BLACK=false;
    //树根
    private RBNode root;

    public RBNode getRoot() {
    
    
        return root;
    }

    public void setRoot(RBNode root) {
    
    
        this.root = root;
    }

    /*
    * 获取当前节点的父节点
    * @param node
    * */
    private RBNode parentOf(RBNode node){
    
    
        if (node!=null){
    
    
            return node.parent;
        }
        return null;
    }

    /*
    * 节点是否为红色
    * @param node
    * */
    private boolean isRed(RBNode node){
    
    
        if (node!=null){
    
    
            return node.color==RED;
        }
        return false;
    }

    /*
     * 节点是否为黑色
     * @param node
     * */
    private boolean isBlack(RBNode node){
    
    
        if (node!=null){
    
    
            return node.color==BLACK;
        }
        return false;
    }

    /*
    * 设置节点为红色
    * @param node
    * */
    private void setRed(RBNode node){
    
    
        if (node!=null){
    
    
            node.color=RED;
        }
    }

    /*
     * 设置节点为黑色
     * @param node
     * */
    private void setBlack(RBNode node){
    
    
        if (node!=null){
    
    
            node.color=BLACK;
        }
    }

    /*
    * 中序打印二叉树
    * */
    public void  inOrderPrint(){
    
    
        inOrderPrint(this.root);
    }
    //重载
    public void  inOrderPrint(RBNode node){
    
    
        if (node!=null){
    
    
            inOrderPrint(node.left);
            System.out.println("key:"+node.key+"value"+node.value);
            inOrderPrint(node.right);
        }

    }

    /*
    * 左旋
    * 1.将x的右子节点指向y的左子节点(LY),y的左子节点的父节点更新为x。
    * 2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点指定子树(当前x的子树位置)指定为y
    * 3.将x的父节点更新为y,将y的左子节点更新为x
    * */
    private void leftRotate(RBNode x){
    
    
        RBNode y=x.right;
        //1.将x的右子节点指向y的左子节点(LY),y的左子节点的父节点更新为x。
        x.right=y.left;
        if (y.left!=null){
    
    
            y.left.parent=x;
        }

        //2.当x的父节点(不为空时),更新y的父节点为x的父节点,并将x的父节点指定子树(当前x的子树位置)指定为y
        if (x.parent!=null){
    
    
            y.parent=x.parent;
            if (x==x.parent.left){
    
    
                x.parent.left=y;
            }else {
    
    
                x.parent.right=y;
            }
        }else {
    
    //说明x为根结点,此时需要更新y为根结点
            this.root=y;
            this.root.parent=null;
        }

        //3.将x的父节点更新为y,将y的左子节点更新为x
        x.parent = y;
        y.left = x;
    }

    /*
    * 右旋
    * 1.将y的左子结点指向y的右子节点,并且更新x的右子节点的父节点为y
    * 2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前的位置)为x
    * 3.更新x的父节点为x,然后x的右子节点为y。
    * */
    private void rigthRotate(RBNode y){
    
    
        RBNode x=y.left;
        //1.将y的左子结点指向y的右子节点,并且更新x的右子节点的父节点为y
        y.left=x.right;
        if (x.right!=null){
    
    
            x.right.parent=y;
        }

        //2.当y的父节点不为空时,更新x的父节点为y的父节点,更新y的父节点的指定子节点(y当前的位置)为x
        if (y.parent!=null){
    
    
            x.parent=y.parent;
            if (y==y.parent.left){
    
    
                y.parent.left=x;
            }else {
    
    
                y.parent.right=x;
            }
        }else {
    
    
            this.root=x;
            this.root.parent=null;
        }

        //更新x的父节点为x,然后x的右子节点为y。
        y.parent=x;
        x.right=y;
    }

    /*
    * 公开的插入方法
    * @param key
    * @param value
    * */
    public void insert(K key,V value){
    
    
        RBNode node =new RBNode();
        node.setKey(key);
        node.setValue(value);
        //新节点一定是红色!
        node.setColor(RED);
        insert(node);
    }
    private  void insert(RBNode node){
    
    
        //1.查找当前node的父节点
        RBNode parent=null;
        RBNode x=this.root;
        while (x!=null){
    
    
            parent =x;
            //cmp>0 说明node.key 大于x.key 需要到x的右子树查找
            //cmp==0 说明node.key 等于x.key 说明需要进行替换
            //cmp < 0 说明node.key 小于x.key 说明需要到x的左子树查找
            int cmp=node.key.compareTo(x.key);
            if(cmp>0 ){
    
    
                x=x.right;
            } else if (cmp==0) {
    
    
                x.setValue(node.getValue());
                return;
            }else {
    
    
                x=x.left;
            }
        }
        node.parent=parent;

        if (parent!=null) {
    
    
            //判断node与parent的 key 谁大
            int cmp = node.key.compareTo(parent.key);
            if (cmp>0){
    
    
                //当前node的key比parent的key大,需要把node放入parent的右子节点
                parent.right=node;
            }else {
    
    
                //当前node的key比parent的key小,需要把node放入parent的左子节点
                parent.left=node;
            }
        }else {
    
    
            this.root=node;
        }
        //需要调用  修复红黑树的平衡的方法
        insertFixUp(node);
    }

    /*
    * 插入后修复红黑树平衡的方法
    * */
    private void insertFixUp(RBNode node){
    
    
        //情况一
        this.root.setColor(BLACK);

        RBNode parent = parentOf(node);
        RBNode gparent=parentOf(parent);
        //情景4:插入父节点为红色
        if ( parent!=null&&isRed(parent)){
    
    
            //如果父节点存在,则一定存在爷爷节点,因为根结点不可能是红色
            RBNode uncle=null;
            if (parent==gparent.left){
    
    
                uncle=gparent.right;
                //情景4.1:叔叔节点存在,并且为红色(父-叔 双红)
                if(uncle!=null&&isRed(uncle)){
    
    
                    //将爸爸和叔叔染为黑色,爷爷染为红色,并且再以爷爷节点为当前节点,进行下一轮处理
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFixUp(gparent);
                    return;
                }
                //情景4.2:叔叔节点不存在,或者为黑色
                if (uncle==null||isBlack(uncle)){
    
    
                     //情景4.2.1:插入节点为其父节点的左子节点(LL情况),将爸爸染为黑色
                    //爷爷染为红色,然后以爷爷右旋
                    if (node==parent.left){
    
    
                        setBlack(parent);
                        setRed(gparent);
                        rigthRotate(gparent);
                        return;
                    }
                    //情景4.2.2
                    if (node==parent.right){
    
    
                        leftRotate(parent);
                        insertFixUp(parent);
                        return;
                    }
                }
            }else{
    
    
                uncle=gparent.left;
                //父节点为爷爷 节点的右子树
                if(uncle!=null&&isRed(uncle)){
    
    
                    //将爸爸和叔叔染为黑色,爷爷染为红色,并且再以爷爷节点为当前节点,进行下一轮处理
                    setBlack(parent);
                    setBlack(uncle);
                    setRed(gparent);
                    insertFixUp(gparent);
                    return;
                }
                //情景4.3叔叔节点不存在
                if (uncle==null||isBlack(uncle)){
    
    
                    //4.3.1
                    if (node==parent.right){
    
    
                        setBlack(parent);
                        setRed(gparent);
                        leftRotate(gparent);
                        return;
                    }
                    //4.3.2
                    if (node==parent.left){
    
    
                        rigthRotate(parent);
                        insertFixUp(parent);
                        return;
                    }
                }
            }
        }

    }



    static class RBNode<K extends Comparable<K>,V >{
    
    
        private RBNode parent;
        private RBNode left;
        private RBNode right;
        private boolean color;
        private K key;
        private V value;

        public RBNode() {
    
    
        }

        public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
    
    
            this.parent = parent;
            this.left = left;
            this.right = right;
            this.color = color;
            this.key = key;
            this.value = value;
        }

        public RBNode getParent() {
    
    
            return parent;
        }

        public void setParent(RBNode parent) {
    
    
            this.parent = parent;
        }

        public RBNode getLeft() {
    
    
            return left;
        }

        public void setLeft(RBNode left) {
    
    
            this.left = left;
        }

        public RBNode getRight() {
    
    
            return right;
        }

        public void setRight(RBNode right) {
    
    
            this.right = right;
        }

        public boolean isColor() {
    
    
            return color;
        }

        public void setColor(boolean color) {
    
    
            this.color = color;
        }

        public K getKey() {
    
    
            return key;
        }

        public void setKey(K key) {
    
    
            this.key = key;
        }

        public V getValue() {
    
    
            return value;
        }

        public void setValue(V value) {
    
    
            this.value = value;
        }
    }
}

总结不易,三连支持一下叭~

猜你喜欢

转载自blog.csdn.net/m0_46494737/article/details/115275927
今日推荐