上次小编分享了AVL树

那么这个AVL树呢,由于涉及到比较多的旋转,所以呢,一旦进行了对的树的修改操作

就显得性能较为低下,所以AVL树呢,一般适合静态数据的查找。

那么有没有一种既可以较为高效的查找,且也对树的结构进行操作,但并没有造成性能较为低下的数据结构呢?

那么此时主角登场——

红黑树

那么红黑树是什么呢?

这个红黑树也是一种二叉搜索树,但是呢,在每个节点上增添一个可以表示节点颜色的存储位

,那么可以表示Red、Black,并满足一定的条件来保证树的平衡。

那么可以通过对任何一条根从叶子的路径上结点着色方式的限制,红黑树确保没有一条路径比其他路径长出两倍,因而这是接近平衡的。

这里要值得说明的是,红黑树呢是相对平衡的,

相对平衡:其左右子树高度差与其他节点的高度差保持在一定的范围内

绝对平衡:指的是每个节点的左右子树的高度差不超过1

那么给个图片看看吧

那么此时呢,这个红黑树是具有这样一定的性质的

1.每个节点不是黑色就是红色

2.根节点是黑色的

3.如果一个节点是红色的,则它的两个孩子节点是黑色【没有连续出现的黑色节点】

4.对于每个结点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

5.每个叶子节点都是黑色的(此处的叶子节点指空节点,是黑色的)

除此之外呢,还有一个推导出来的性质

即最长路径最多是最短路径的两倍

那么这又是什么意思呢?

举个例子:

这里是左边这幅图是比较极端情况下的,毕竟也没有说一定不能全是黑色的

那么此时左边这幅图每条路径上是有三个节点的

右边这幅图,最右边的路径达到了6个节点,显然,并没有超出两倍。

那么值得注意的是,一个正常的红黑树,是不会出现全部都是黑色节点,或者一条路径上全部是黑色节点

到这里呢,提出一个有点意思的问题

加入一个红黑树中的黑色节点为X个,那么这个树的总节点N,范围在哪里?

其实刚刚那个上述举的例子中可以看的出来,

全为黑:x个

一黑一红交替出现:2X

范围表示为【x,2x】

那么此时又可以得出一个这样的结论

最短路径长度:logN

最长路径长度:log2N=2*logN

那么可以看到其实,两者相差不了多少的。

那么性质什么的讲完了,接下来讲讲如何实现吧。

实现这里,小编呢讲讲的是插入。

插入

那么上述讲到,这个红黑树也是一颗平衡二叉树。

那么这里,既然是红黑树,有了颜色的标记,所以,我们可以定义一个枚举类,来表示红色、黑色

代码如下:

public enum Color {
    Red,Black;
}

所以我们定义的节点中,可以包含颜色了。

那么这个红黑树定义的节点代码如下:

public class RBTree {
    static class Node{
        public int val;
        public Node left;
        public Node right;
        public Node parent;
        //此时用到一个枚举类的颜色,默认红色
        //默认为红色,插入节点只需调节颜色,默认为黑色,就要插入无关的节点
        public Color color;
        public Node(int val){
            this.val=val;
           this.color=Color.Red;
        }
    }

那么这里需要解释下为什么插入节点默认的颜色为红色。

因为,我们要是插入新的节点颜色为黑色,比如以下这个例子:

如若在p中插入节点,为了保证红黑树中这个性质:每条路径上的黑色节点必须是相同数目

此时,也要往根节点的右树插入两个黑色节点,以保证这个数目是相同的。

那么这个平衡二叉树的插入前些部分操作和之前实现的AVL树是类似的

代码如下:

public Node root;
public boolean insert(int val){
        //插入的方式和AVL树中部分类似,即调整平衡因子前的代码,可以迁移过来
        Node newnode=new Node(val);
        if(root==null){
            root=newnode;
            root.color=Color.Black;
            return true;
        }
        Node parent=null;
        Node cur=root;
        while (cur!=null){
            if(cur.val<val){
                parent=cur;
             cur=cur.right;
            }else if(cur.val>val){
                parent=cur;
               cur=cur.left;
            }else {
                return false;
            }
        }
        if(parent.val<val){
            parent.right=newnode;
        }else {
            parent.left=newnode;
        }
       newnode.parent=parent;
        cur=newnode;
        //此时要进行颜色调节

值得注意的是,我们的根节点是黑色的,所以当插入第一个节点的时候,要手动设置为黑色

那么我们插入节点的时候就会遇到两个节点是红色的,所以此时违反了红黑树的性质,那么就要进行颜色的调节

进行调节之前,先约定一些节点定义

cur为当前节点、parent为cur的父亲节点、gandfacter为parent的父亲节点、uncle为叔叔节点

后面图片中标记时,逐一简写,c、p、g、u

当插入的节点cur的父亲节点在左边为一种情况,在右边又是一种情况。

cur的父亲节点在左边

情况一:cur为红色、p为红色,u存在且为红色

思路:

把p颜色标记为黑色,u的节点标记为黑色

完了?还没有!

g也有父亲节点,且g的父亲节点也可能为红色和黑色

比如下面的情况

情况一分支一:

那么此时我们单纯的将其p、u颜色设置为黑色是不行的,这样会导致每条路径上的黑色节点数目不一致,所以还是需要将g的节点设置为红色

修改后:

情况一分支二:

那么此时将p、u的颜色设置为黑色,如何将这个g设置为红色后,

那么此时又发现违反了性质了,那么继续向上调整

cur=g,p=cur.parent

此时直到parent节点为null,调整就结束了

那么这里是有点小问题还没有解答,万一它就只有一个根节点,此时就是为黑色的呢,那么这时候呢,所有情况调整完后,再次把它设置为黑色,所以在这之前,情况一,统一设置为红色,对于这个根节点来说。

情况一代码:

 while (parent!=null&&parent.color==Color.Red){
            //连接两个红色节点的时候,就要调整,
            //即检查父亲节点是否为红色节点即可
           //首先定义祖父节点这个点先
            Node grandFacter=parent.parent;
            if(parent==grandFacter.left){
                //情况一:cur为红,p为红,g为黑,u存在且为红
                Node uncle=grandFacter.right;
                if(uncle!=null && uncle.color==Color.Red){
                parent.color=Color.Black;
                uncle.color=Color.Black;
                grandFacter.color=Color.Red;
                //此处将grandFacter设置为红,是怕存在grandFacter的父亲节点,
                //如若没有,后续统一设置为黑色
                //为了防止当前的grandFacter存在父亲节点,即向上调整
                    cur=grandFacter;
                    parent=cur.parent;

那么情况二又是什么呢?

上述说到uncle是红色,且存在的,那么这里呢,就是说u是不存在或者是黑色的

情况二:cur为红色,parent为红色,g为黑色,u不存在/为黑色

举个例子:

此时,我们发现,这里很奇怪,正常来说,这里黑色节点不符合其性质,而且又有两个红色节点在这,那么,怎么得来的情况呢?

这个情况就是调整过程中出现的

出现此过程如下:



这里的调整是按照情况一那样进行调整的。

情况是符合了,那么解决方案是?

思路:左边高,进行右单旋,然后修改parent和grandFacter的颜色

为什么要修改颜色,不修改颜色的话,此时每条路的黑色节点数目不一致了,

然后这个右单旋,AVL树中分享过了,小编呢,在这不做过多赘述

那么,情况二的代码先不放出来,接着介绍情况三,到时候会有个惊奇的发现

情况三:cur为红,parent为红,gandfacter为黑,u为黑/不存在

这里怎么感觉和情况二一样呢?

上图先:

那么此时可以发现,不一样的地方,新插入的cur节点在这个红色节点的右边了,情况二在左边的,所以这里是不一样的地方。

那么显然,正常来说,不能是无缘无故到这个情况的,那么这个也是调整过程中出现的

调整过程中:

思路:

右树变高了,左单旋

然后不修改颜色了?

当然不是,且看旋转后的结果

你会发现,似曾相识

这不就是和情况二差不多嘛,只不过是,cur和parent的位置不同而已,

所以这时候,我们执行cur和parent调换操作,然后再次执行情况二的操作就行

所以在代码方面呢,我们先把情况三处理了,将情况三处理成情况二后,再次执行情况二的操作即可

代码:

}else {
                    //即uncle不存在或者为黑色,和情况二情况三的有点类似
                    //情况三: cur为红,p为红,g为黑,u不存在/u为黑,此时cur在parent的右边
                    //先处理情况三,使其变成情况二,
                    if(parent.right==cur){
                        rotateLeft(parent);
                        //交换节点位置,使其变成情况二
                        Node temp=parent;
                        parent=cur;
                        cur=temp;
                    }
                    //此时处理情况二 cur为红,p为红, g为黑,u不存在/u为黑,
                    //右转,因为左边高
                    rotateRight(grandFacter);
                    grandFacter.color=Color.Red;
                    parent.color=Color.Black;
                }
}

到这里出现情况的讲解完成了

此时呢,我们还得处理cur的父亲节点在右边的情况

cur的父亲节点在右边

那么这种情况中,其里面的遇到一些情况一、二、三是和cur的父亲节点在左边(即上述刚刚说的)是差不多的,只是要修改几个值而已,所以下面呢就给出图片,思路不做解释了,毕竟思路和刚刚上面是差不多的

情况一:

情况一分支一:

情况一分支二:

情况二:

情况三:

此时我们发现,cur的parent在右边的情况中,仅仅是以下这些有改变

uncle改变,为grandFacter.left

后面的对应旋转,从情况三的左单旋,变成右单旋

情况二中的右单旋,变成左单旋。

那么直到这里,红黑树的插入操作讲解完毕了

那么这个删除操作,就不做讲解了,这里分享其他博主写的两篇博客,大家有兴趣的可以看看

删除

一步一图一代码,一定要让你真正彻底明白红黑树_红黑书排序-CSDN博客

红黑树 - Never - 博客园

那么问题又来了,如何判断我们输入的红黑树,它就是一定符合其性质的呢?

判断树是否符合红黑树的性质

刚刚我们上述讲到的性质如下:

所以,在这里呢,我们仅需判断我们输入的数据,是否符合性质2、3、4即可,

因为性质1、性质5是在判断符合2、3、4性质的时候,包含了此判断。

判断是否符合性质2

思路:如若根节点不是黑色,那么打印对应的信息,然后返回false。当然这个false我这里放到最后返回,因为其他两个性质没有判断

判断是否符合性质3

思路:递归,类似于之前的二叉树遍历,遇到当前节点的颜色为红色,然后呢,判断其父亲节点是否为红色,若是,即不符合性质3

判断是否符合性质四:

思路:我们这里呢,先寻找到其中一条路径中黑色节点的个数——blackNum,然后将其保存下来

接着,去递归红黑树的每一条路径,这里的递归还是类似于二叉树的遍历操作,所以

一旦发现blackNum和递归每一条路径时,所计算的黑色节点不一致,就返回false

那么代码如下:

public boolean isRBTree(){
        if(root==null){
            //空树也是一颗红黑树
            return true;
        }
        if(root.color!=Color.Black){
            System.out.println("违反性质:根节点必须为黑色");
        }
        int blackNum=0;
        Node cur=root;
        while (cur!=null){
            if(cur.color==Color.Black){
                blackNum++;
            }
            cur=cur.left;
        }
        return checkColor(root)&&checkNum(root,0,blackNum);
    }

    /**
     *
     * @param root
     * @param pathNum 这里是当前路径的节点
     * @param blackNum 这个是isRBTree()方法中给定的,计算出一条路径上有多少个节点
     */
    public boolean checkNum(Node root,int pathNum,int blackNum){
        if(root==null){
            return true;
        }
        if(root.color==Color.Black){
            pathNum++;
        }
        if(root.left==null&&root.right==null){
            if(pathNum!=blackNum){
                System.out.println("违法性质:每条路径上的黑色节点不一致");
                return false;
            }
        }
        return checkNum(root.left,pathNum,blackNum) && checkNum(root.right,pathNum,blackNum);
    }
    public boolean checkColor(Node root){
     if(root==null){
         return true;
     }
      if(root.color==Color.Red){
          Node parent=root.parent;
          if(parent.color ==Color.Red){
              System.out.println("违反性质:不能有两个连续的红色节点");
              return false;
          }
      }
      return checkColor(root.left) && checkColor(root.right);
    }

源码:

Color类:

public enum Color {
    Red,Black;
}

RBTree类:

public class RBTree {
    static class Node{
        public int val;
        public Node left;
        public Node right;
        public Node parent;
        //此时用到一个枚举类的颜色,默认红色
        //默认为红色,插入节点只需调节颜色,默认为黑色,就要插入无关的节点
        public Color color;
        public Node(int val){
            this.val=val;
           this.color=Color.Red;
        }
    }
    public Node root;
    public boolean insert(int val){
        //插入的方式和AVL树中部分类似,即调整平衡因子前的代码,可以迁移过来
        Node newnode=new Node(val);
        if(root==null){
            root=newnode;
            root.color=Color.Black;
            return true;
        }
        Node parent=null;
        Node cur=root;
        while (cur!=null){
            if(cur.val<val){
                parent=cur;
             cur=cur.right;
            }else if(cur.val>val){
                parent=cur;
               cur=cur.left;
            }else {
                return false;
            }
        }
        if(parent.val<val){
            parent.right=newnode;
        }else {
            parent.left=newnode;
        }
       newnode.parent=parent;
        cur=newnode;
        //此时要进行颜色调节
        while (parent!=null&&parent.color==Color.Red){
            //连接两个红色节点的时候,就要调整,即检查父亲节点是否为红色节点即可
           //首先定义祖父节点这个点先
            Node grandFacter=parent.parent;
            if(parent==grandFacter.left){
                //情况一:cur为红,p为红,g为黑,u存在且为红
                Node uncle=grandFacter.right;
                if(uncle!=null && uncle.color==Color.Red){
                parent.color=Color.Black;
                uncle.color=Color.Black;
                grandFacter.color=Color.Red;
                //此处将grandFacter设置为红,是怕存在grandFacter的父亲节点,如若没有,后续统一设置为黑色
                //为了防止当前的grandFacter存在父亲节点,即向上调整
                    cur=grandFacter;
                    parent=cur.parent;
                }else {
                    //即uncle不存在或者为黑色,和情况二情况三的有点类似
                    //情况三: cur为红,p为红,g为黑,u不存在/u为黑,此时cur在parent的右边
                    //先处理情况三,使其变成情况二,
                    if(parent.right==cur){
                        rotateLeft(parent);
                        //交换节点位置,使其变成情况二
                        Node temp=parent;
                        parent=cur;
                        cur=temp;
                    }
                    //此时处理情况二 cur为红,p为红, g为黑,u不存在/u为黑,
                    //右转,因为左边高
                    rotateRight(grandFacter);
                    grandFacter.color=Color.Red;
                    parent.color=Color.Black;
                }
            }else {
                //这里的情况是uncle在左边了,那么情况呢上面是相反的,其实是差不多的
                Node uncle=grandFacter.left;
                if(uncle!=null&&uncle.color==Color.Red){
                    parent.color=Color.Black;
                    uncle.color=Color.Black;
                    grandFacter.color=Color.Red;
                    cur=grandFacter;
                    parent=cur.parent;
                }else {
                    //情况三
                    if(parent.left==cur){
                        rotateRight(parent);
                        Node temp=parent;
                        parent=cur;
                        cur=temp;
                    }
                    //情况二:
                    rotateLeft(grandFacter);
                    grandFacter.color=Color.Red;
                    parent.color=Color.Black;
                }
            }
        }
        root.color=Color.Black;
        return true;
    }
    public void rotateLeft(Node parent){
        //首先定义两个节点
        Node subR=parent.right;
        Node subRL=subR.left;
        parent.right=subRL;
        subR.left=parent;
        //subRL此时有可能为空,需要判断下
        if(subRL!=null){
            subRL.parent=parent;
        }
        //此时有可能parent的parent不为空,所以修改parent.parent指向之前,保存一下
        Node pParent=parent.parent;
        parent.parent=subR;
        //到这里要修改下parent的parent的指向了
        if(root==parent){
            root=subR;
            root.parent=null;
        }else {
            if(pParent.left==parent){
                pParent.left=subR;
            }else {
                pParent.right=subR;
            }
            subR.parent=pParent;
        }

    }
    private void rotateRight(Node parent){
        //首先定义两个节点
        Node subL=parent.left;
        Node subLR=subL.right;
        parent.left=subLR;
        subL.right=parent;
        //有可能subL的左边节点为空,那么就要判断一下
        if(subLR!=null){
            subLR.parent=parent;
        }
        Node pParent=parent.parent;
        //修改parent.parent的节点之前要保存一下parent.parent,有可能当前的parent上面还有一个节点
        parent.parent=subL;
        //下面修改subL的指向
        if(parent==root){
            //parent就是头节点
            root=subL;
            root.parent=null;
        }else {
            if (pParent.left == parent) {
                //如果parent的父亲节点左边等于parent,那么代表着parent左边要连接subL
                pParent.left = subL;
            } else {
                pParent.right = subL;
            }
            //无论怎么样,subL的父亲节点永远都是pParent
            subL.parent=pParent;
        }

    }
    public boolean isRBTree(){
        if(root==null){
            //空树也是一颗红黑树
            return true;
        }
        if(root.color!=Color.Black){
            System.out.println("违反性质:根节点必须为黑色");
        }
        int blackNum=0;
        Node cur=root;
        while (cur!=null){
            if(cur.color==Color.Black){
                blackNum++;
            }
            cur=cur.left;
        }
        return checkColor(root)&&checkNum(root,0,blackNum);
    }

    /**
     *
     * @param root
     * @param pathNum 这里是当前路径的节点
     * @param blackNum 这个是isRBTree()方法中给定的,计算出一条路径上有多少个节点
     */
    public boolean checkNum(Node root,int pathNum,int blackNum){
        if(root==null){
            return true;
        }
        if(root.color==Color.Black){
            pathNum++;
        }
        if(root.left==null&&root.right==null){
            if(pathNum!=blackNum){
                System.out.println("违法性质:每条路径上的黑色节点不一致");
                return false;
            }
        }
        return checkNum(root.left,pathNum,blackNum) && checkNum(root.right,pathNum,blackNum);
    }
    public boolean checkColor(Node root){
     if(root==null){
         return true;
     }
      if(root.color==Color.Red){
          Node parent=root.parent;
          if(parent.color ==Color.Red){
              System.out.println("违反性质:不能有两个连续的红色节点");
              return false;
          }
      }
      return checkColor(root.left) && checkColor(root.right);
    }
    //中序遍历
    public void inorder(Node root){
        if(root==null){
            return;
        }
        inorder(root.left);
        System.out.print(root.val+" ");
        inorder(root.right);
    }
}

那么到这里,红黑树的分享到此完毕。

文章作者: 南汐
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 www.phblog.cloud
数据结构 数据结构
喜欢就支持一下吧