红黑树【实现插入操作加验证一颗树为红黑树,附图,详解】

红黑树

前篇文章讲AVL树,如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树;但是由于AVL树在新增/减少结点的时候会进行旋转以保持AVL树的高度平衡,所以如果要对AVL树做一些结构修改的操作,性能非常低下,所以一个结构经常修改,就不太适合用AVL树来存储。
因此引入一个数据结构,也就是本文的重点——红黑树,红黑树通过维护结点的颜色来维持树的【相对平衡】,这里的相对平衡指的是红黑树的平衡没有AVL树那样严格,所以相对旋转的次数也会减少

红黑树性质

满足下面条件就是红黑树:

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的【没有2个连续的红色节点】
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    红黑树还有一条性质,是根据上面的性质推出来的,那就是:最长路径最多也就是最短路径的两倍

因为有【对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 】和【没有2个连续的红色节点】这两个性质就能推出【最长路径最多也就是最短路径的两倍】,因为其实最短路径就是一条路径上全是黑色的结点,最长路径就是一条路径上红黑交替的结点;假设最短路径上黑色结点为N个;那么最长路径由于是红黑交替,最短路径有N个黑色结点,那么最长路径也应该有N个黑色结点,加在上N个红色结点交替,所以最长路径最长也就2N

那么接下来我们来看一下红黑树节点的定义,首先要有一个枚举来表示颜色

public enum COLOR {
    
    
    RED,BLACK
}

红黑树节点:

 static class RBTreeNode {
    
    
        public RBTreeNode left;
        public RBTreeNode right;
        public RBTreeNode parent;
        public COLOR color;//结点颜色;
        public int val;

        public RBTreeNode(int val) {
    
    
            this.val = val;
            this.color = RED;
        }
    }

可以看到上面我顺便把构造方法提供了,那么可以看到为什么默认新的要插入的结点是颜色是红色呢,是随便写的吗?当然不是啊,接着往下看:
原因有两点:

  • 首先,我们往红黑树插入一个新的节点,它本身已经是一个红黑树,那么它就满足这个性质【对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 】,但是此时我们如果插入的是一个黑色的结点,那么就会破坏这个性质,它就不是红黑树了
  • 但是往红黑树插入一个红色的结点也是可能会破坏一个性质【没有2个连续的红色节点】,那么,为什么还是要默认新插入的结点为黑色呢,那是因为我可以通过调整把它再次变成红黑树;(新插入结点的颜色为红色比新插入结点的颜色是黑色好调整,所以选择默认是红色)
    红黑树插入一个新结点跟AVL树一样有三个步骤:
  • 寻找插入位置
  • 插入结点
  • 调整红黑树,保证不破坏性质
    由于前两个步骤都是一样的,所以直接给代码:
public boolean insert(int val) {
    
    
        RBTreeNode node = new RBTreeNode(val);
        if(root == null) {
    
    
            root = node;
            root.color = COLOR.BLACK;
            return true;
        }
        //寻找插入位置
        RBTreeNode parent = null;
        RBTreeNode 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;
            }
        }
        //到这里cur = null
        if(parent.val>val) {
    
    
            parent.left = node;
        } else {
    
    
            parent.right = node;
        }
        node.parent = parent;
        cur = node;

我们定义几个变量看下图:
在这里插入图片描述

    这几个变量后面会经常用,大家先记住

接下来进入正题,由于插入前这棵树本来就是红黑树,而我们新插入的结点默认是红色的,所以当新插入的结点的parent是黑色是,这时候是不会破坏红黑树的性质的,此时不用调整;只有当parent为红色结点的时候,我们才需要从cur结点开始往根节点向上调整【检查并调整树的颜色或结构】
所以外层的while循环的条件就出来了

while(parent!=null&&parent.color== RED) 

那么既然只有当parent为红色时才会进入循环,所以parent不可能是根节点,因此grandparent一定存在

//parent是红色的,所以parent必有父亲节点
            RBTreeNode grandparent = parent.parent;

既然granparent存在,那我们的大的情况就有两种parent是grandparent的左孩子和parent是grandparent的右孩子;而这两种情况里面又有三种小情况
我们先把代码总体框架给出

 while(parent!=null&&parent.color== RED) {
    
    
            //parent是红色的,所以parent必有父亲节点
            RBTreeNode grandparent = parent.parent;
            //情况1.0 parent是grandparent的左孩子
            if(parent==grandparent.left) {
    
    
                RBTreeNode uncle = grandparent.right;
                //情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
               
                } else {
    
    
                    //情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    if(cur==parent.right) {
    
    
                      
                        //然后就变成情况1.2
                    }
                    //情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                   
                }
            } else {
    
    
                //情况2.0 parent是grandparent的右孩子 parent == grandparent.right
                RBTreeNode uncle = grandparent.left;
                //情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
                if(uncle!=null&&uncle.color== RED) {
    
    
                    ;
                } else {
    
    
                    //情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    if(cur == parent.left) {
    
    
                        
                    }
                    //情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                 
                }
            }
        }
        //最后将根节点修改为黑色
        root.color = COLOR.BLACK;
        return true;

情况 1.0

parent是grandparent的左孩子

情况 1.1

cur为红,parent为红,grandparent为黑,uncle存在且为红
解决方法:将parent和uncle变成黑色,grandparent变成红色。
在这里插入图片描述

我相信把parent和uncle变成黑色大家看图后就能理解,当为什么要把grandparent变成红色呢?
因为grandparent有可能是有父亲节点的,而且grandparent的父亲节点有可能是黑的也可能是红的
如果grandparent的父亲节点是黑色的,就会造成各路径上的黑色结点数就不相等了,
所以我们把grandparent改成红色就可以解决这种情况
如果grandparent的父亲节点是红色的,那么此时就出现了两个红色结点,那该怎么办呢?
解决方法:让cur = grandparent,parent = grandparent.parent;然后再次进入循环,向上调整,看图:
在这里插入图片描述

那如果grandparent没有父亲结点,我们还把grandparent改成红色的,不就不满足红黑树了吗?解决这种方式也很简单,如果granparent没有父亲节点,那说明它就是根节点,此时就退出循环,把它修改成黑色就解决了
在这里插入图片描述
代码如下:

 //情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
                if(uncle!=null&&uncle.color== RED) {
    
    
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandparent.color = RED;//先变成红色,然后不是根节点,继续向上调整,如果为根节点
                    cur =grandparent;
                    parent = cur.parent;
                } 

情况1.2

cur为红,parent为红,uncle不存在或者为黑,cur是parent的左孩子
解决方式:对grandparent右旋,然后将parent改为黑色,将grandparent改为红
这种情况是在调整中的状态,因为你看下图,如果不是调整中才会出现的情况,它根本就不算是一颗红黑树(在插入cur之前,它也不是红黑树,因为黑色结点数量不对)
在这里插入图片描述
那么下图就是在调整中出现的情况1.2
在这里插入图片描述
由于左旋右旋前面一篇AVL树细节已经讲过了,这里就偷懒了放个链接:AVL树
代码如下:

//情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    //对grandparent右旋
                    rotateRight(grandparent);
                    //在修改颜色
                    grandparent.color = RED;
                    parent.color = COLOR.BLACK;


情况1.3

cur为红,parent为红,uncle不存在或者为黑,cur是parent的右孩子
看着好像跟情况1,2好像,但是你仔细看cur是parent的右孩子,这里不一样
解决方式:左旋,交换cur和parent的引用,就变成情况1.2了,然后用情况1.2的解决方法来进行调整,看图:
在这里插入图片描述
在这里插入图片描述
以1.2情况进行调整
在这里插入图片描述
到这里情况1.3也就讲完了

//情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    if(cur==parent.right) {
    
    
                        //先左旋
                        rotateLeft(parent);
                        //交换cur和parent的引用
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                        //然后就变成情况1.2
                    }
                    //情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    //对grandparent右旋
                    rotateRight(grandparent);
                    //在修改颜色
                    grandparent.color = RED;
                    parent.color = COLOR.BLACK;

情况2.0

parent是grandparent的右孩子
情况2.0和情况1.0基本一样,就是改了个方向

情况2.1

cur为红,parent为红,grandparent为黑,uncle存在且为红
解决方法:将parent和uncle变成黑色,grandparent变成红色。
为什么把grandparent变成红色前面已经细讲了,这里就直接看图不再赘述
在这里插入图片描述

//情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
                if(uncle!=null&&uncle.color== RED) {
    
    
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandparent.color = RED;
                    cur = grandparent;
                    parent = cur.parent;
                } 

情况2.2

cur为红,parent为红,uncle不存在或者为黑,cur是parent的右孩子
解决方式:对grandparent左旋,然后将parent改为黑色,将grandparent改为红
看图:
在这里插入图片描述
代码:

  //情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    //对grandparent左旋
                    rotateLeft(grandparent);
                    //修改颜色
                    grandparent.color = RED;
                    parent.color = BLACK;

情况2.3

cur为红,parent为红,uncle不存在或者为黑,cur是parent的左孩子
解决方式:对parent进行右旋,交换parent和cur的引用,然后就变成情况2.2了,之后就以情况2.2处理即可
看图:
在这里插入图片描述
在这里插入图片描述
代码

 //情况2.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    if(cur == parent.left) {
    
    
                        //先右旋
                        rotateRight(parent);
                        //交换cur和parent的引用
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                    }
                    //情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    //对grandparent左旋
                    rotateLeft(grandparent);
                    //修改颜色
                    grandparent.color = RED;
                    parent.color = BLACK;

完整代码

package RBTree;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ling
 * Date: 2022-11-23
 * Time: 19:34
 */

import AVLTree.AVLTree;
import com.sun.org.apache.regexp.internal.RE;

import static RBTree.COLOR.BLACK;
import static RBTree.COLOR.RED;

/**
 * 红黑树实现
 */
public class RBTree {
    
    
    static class RBTreeNode {
    
    
        public RBTreeNode left;
        public RBTreeNode right;
        public RBTreeNode parent;
        public COLOR color;//结点颜色;
        public int val;

        public RBTreeNode(int val) {
    
    
            this.val = val;
            this.color = RED;
        }
    }
    public RBTreeNode root;
    public boolean insert(int val) {
    
    
        RBTreeNode node = new RBTreeNode(val);
        if(root == null) {
    
    
            root = node;
            root.color = COLOR.BLACK;
            return true;
        }
        //寻找插入位置
        RBTreeNode parent = null;
        RBTreeNode 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;
            }
        }
        //到这里cur = null
        if(parent.val>val) {
    
    
            parent.left = node;
        } else {
    
    
            parent.right = node;
        }
        node.parent = parent;
        cur = node;
        //到这里【向上调整颜色】
        //新插入的结点为红色的,如果父亲节点也是红色,就需要调整
        while(parent!=null&&parent.color== RED) {
    
    
            //parent是红色的,所以parent必有父亲节点
            RBTreeNode grandparent = parent.parent;
            //情况1.0 parent是grandparent的左孩子
            if(parent==grandparent.left) {
    
    
                RBTreeNode uncle = grandparent.right;
                //情况1.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
                if(uncle!=null&&uncle.color== RED) {
    
    
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandparent.color = RED;//先变成红色,然后不是根节点,继续向上调整,如果为根节点
                    cur =grandparent;
                    parent = cur.parent;
                } else {
    
    
                    //情况1.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    if(cur==parent.right) {
    
    
                        //先左旋
                        rotateLeft(parent);
                        //交换cur和parent的引用
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                        //然后就变成情况1.2
                    }
                    //情况1.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    //对grandparent右旋
                    rotateRight(grandparent);
                    //在修改颜色
                    grandparent.color = RED;
                    parent.color = COLOR.BLACK;
                }
            } else {
    
    
                //情况2.0 parent是grandparent的右孩子 parent == grandparent.right
                RBTreeNode uncle = grandparent.left;
                //情况2.1 cur为红色,parent为红色 grandparent为黑色,uncle存在且为红色,
                if(uncle!=null&&uncle.color== RED) {
    
    
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandparent.color = RED;
                    cur = grandparent;
                    parent = cur.parent;
                } else {
    
    
                    //情况2.3 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent左孩子
                    if(cur == parent.left) {
    
    
                        //先右旋
                        rotateRight(parent);
                        //交换cur和parent的引用
                        RBTreeNode tmp = parent;
                        parent = cur;
                        cur = tmp;
                    }
                    //情况2.2 cur为红,parent为红,grandparent为黑色,uncle不存在或者uncle为黑色,cur为parent右孩子
                    //对grandparent左旋
                    rotateLeft(grandparent);
                    //修改颜色
                    grandparent.color = RED;
                    parent.color = BLACK;
                }
            }
        }
        //最后将根节点修改为黑色
        root.color = COLOR.BLACK;
        return true;
    }

    /**
     * 左旋
     * @param parent
     */
    private void rotateLeft(RBTreeNode parent) {
    
    
        RBTreeNode subR = parent.right;
        RBTreeNode subRL = subR.left;
        //记录一下parent的父亲节点
        RBTreeNode pParent = parent.parent;
        parent.right = subRL;
        //检查有没有subRL
        if(subRL!=null) {
    
    
            subRL.parent = parent;
        }
        subR.left = parent;
        parent.parent = subR;
        //检查当前是不是根节点
        if(parent==root) {
    
    
            root = subR;
            root.parent = null;
        } else {
    
    
            if(pParent.left == parent) {
    
    
                pParent.left = subR;
            } else {
    
    
                pParent.right = subR;
            }
            subR.parent = pParent;
        }

    }

    /**
     * 右旋
     * @param parent
     */
    private void rotateRight(RBTreeNode parent) {
    
    
        RBTreeNode subL = parent.left;
        RBTreeNode subLR=subL.right;
        //记录一下parent的父亲节点
        RBTreeNode pParent = parent.parent;
        parent.left = subLR;
        //检查有没有subLR
        if(subLR!=null) {
    
    
            subLR.parent = parent;
        }
        subL.right = parent;
        parent.parent = subL;
        //检查当前是不是根节点
        if(parent == root) {
    
    
            root = subL;
            root.parent = null;
        } else {
    
    
            //不是根节点,判断这棵子树是左子树还是右子树
            if(pParent.left == parent) {
    
    
                pParent.left = subL;
            } else {
    
    
                pParent.right = subL;
            }
            subL.parent = pParent;
        }

    }

  
}

红黑树的检验

检验一颗树是否为红黑树,只要检查满不满足它的性质即可

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的【没有2个连续的红色节点】
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    这个【每个叶子结点都是黑色的(此处的叶子结点指的是空结点)】性质就不用检验了,因为这里叶子结点是空节点,我们默认它是黑色的就行了,然后直接看代码
 /**
     * 检查一颗树是否为红黑树
     * @return
     */
    public boolean isRBTree() {
    
    
        if(root == null) {
    
    
            //如果一颗树是空树,那么这颗树就是红黑树
            return true;
        }
        if(root.color!=BLACK) {
    
    
            System.out.println("违反了性质;根节点必须是黑色的");
        }
        //计算最左边路径上的黑色结点个数
        int blackNum = 0;
        RBTreeNode cur = root;
        while(cur!=null) {
    
    
            if(cur.color==BLACK) {
    
    
                blackNum++;
            }
            cur = cur.left;
        }


        // 检查有没有连续出现两个红色的结点&&检查每条路径上的黑色结点是否相同
        return checkRedColor(root)&&checkBlackNum(root,0,blackNum);
    }

    /**
     * 检查有没有连续出现两个红色的结点
     * @param root
     * @return
     */
    private boolean checkRedColor(RBTreeNode root) {
    
    
        if(root == null) {
    
    
            return true;
        }
        if(root.color == RED) {
    
    
            RBTreeNode parent = root.parent;
            if(parent.color==RED) {
    
    
                System.out.println("违反了性质,连续出现了两个红色的结点");
                return false;
            }
        }
        return checkRedColor(root.left)&&checkRedColor(root.right);
    }

    /**
     * 检查每条路径上的黑色结点是否相同
     * pathBlackNum:每次递归是,计算黑色节点的个数
     * blackNum:事先计算好的一条路径上的黑色结点
     * @param root
     * @param pathBlackNum
     * @param blackNum
     * @return
     */
    private boolean checkBlackNum(RBTreeNode root,int pathBlackNum,int blackNum) {
    
    
        if(root==null) {
    
    
            return true;
        }
        if(root.color==BLACK) {
    
    
            pathBlackNum++;
        }
        if(root.left==null&&root.right==null) {
    
    
            if(pathBlackNum!=blackNum) {
    
    
                System.out.println("违反了性质,某条路径上黑色的结点个数不一样");
                return false;
            }
        }
        return checkBlackNum(root.left,pathBlackNum,blackNum)&&checkBlackNum(root.right,pathBlackNum,blackNum);
    }
    

这里提供一个测试用例

int[] array = {
    
    4,2,6,1,3,5,15,7,16,14};
        RBTree rbTree = new RBTree();
        for(int i=0;i<array.length;i++) {
    
    
            rbTree.insert(array[i]);
        }
        System.out.println(rbTree.isRBTree());
        rbTree.inorder(rbTree.root);
    

猜你喜欢

转载自blog.csdn.net/HBINen/article/details/128009490