数据结构 - 红黑树(Red Black Tree)插入详解与实现(Java)

  最终仍是决定把红黑树的篇章一分为二,插入操做一篇,删除操做一篇,由于合在一块儿写篇幅实在太长了,写起来都以为累,况且是阅读并理解的读者。html

      红黑树删除操做请参考 数据结构 - 红黑树(Red Black Tree)删除详解与实现(Java)node

  如今网络上最不缺的就是对某个知识点的讲解博文,各类花样标题百出,更有相似“一文讲懂xxx”,“史上最简单的xxx讲解”,“xxx看了还不懂你打我”之类云云。其中也不乏有些理论甚至是举例都雷同的两篇不一样文章,至于做者是否是真的理解本身所写的内容暂且不说,技术博客这种东西,原本就是提供给你们分享本身学习体会的一个平台,我也不敢说本身写的就足够全面简洁易懂,只能说有些东西确实不是一两篇文章就能理解透彻的,只有多读,多思考,慢慢的就会明了,我也是读了好几我的的博文才读懂的,一些前辈的文章确实很不错,值得参考和学习。仅但愿我所写这两篇关于红黑树的文章能在众多的同类博文中给偶然看到的读者一点点启示。网络

  正文。数据结构

  本文要求懂得二叉搜索树的原理,若是还不理解能够转阅(理解第一篇即可以):ide

    1、数据结构 - 从二叉搜索树说到AVL树(一)之二叉搜索树的操做与详解(Java)函数

    2、数据结构 - 从二叉搜索树说到AVL树(二)之AVL树的操做与详解(Java)学习

  众所皆知,二叉平衡树(Binary Balanced Tree)的出现是为了让一棵二叉搜索树的查找效率尽量的最大化,同时为了构造这么一棵树,在插入和删除的时候也要根据必定的规则进行操做,这些操做在必定状况下也会影响到整棵树的使用效率,因此,咱们想有没有这么一种树,咱们并没必要严格要求这棵树要平衡度很高(好比全部路径的长度差都必须在一个很小的范围以内)以提升插入和删除的效率,同时又不能太影响到查找的效率,已达到一个比较好的使用效果。this

  在此以前,本文图例约定以下:spa

   

  红黑树(Red Black Tree - RB Tree)就是这样一种数据结构,和不少数据结构同样,红黑树也有本身的一套事先规定好的规则,不管在什么状态下,一颗红黑树都必须知足如下五个规则(定义), 破坏任何一条规则都再也不是一颗红黑树。3d

  1. 红黑树的节点不是红色的就是黑色的

  2. 红黑树的根节点永远是黑色的

  3. 全部叶子节点都是黑色的(注意:红黑树的叶子节点是指Nil节点)

  4. 同一路径上不能有相邻两个节点都是红色的

  5. 从任一节点到全部叶子节点所经历的黑色节点个数相同

  以上五个定义即便不能背下来,也要十分熟悉。用以上的定义去实现一颗红黑树,能使全部搜索路径长度相差最大不过一倍。

  定义红黑树节点的数据结构:

public class TreeNode {
    private int elem;
    private TreeNode left, right;
    private TreeNode parent;
    private NodeColor color;
    public TreeNode (int elem) {
        this.elem = elem;
        color = NodeColor.RED;
    }
}

  比普通二叉搜索树多了一个属性表示节点颜色,初始化一个节点的时候,节点颜色设置为红色,由于插入一个红色节点,只要不违反红黑树的规则,插入以后不须要对树进行调整,但若是直接插入一个黑色节点,那确定会违反上面所说的第5个规则,势必要进行调整,因此多一事不如少一事。

  在此以前先讲一些基本操做,而后再讲具体

  红黑树的基本操做包括染色旋转,染色没有什么可说的,根据上面所说的第一条定义,染色无非是把一个节点从黑色染成红色或反之。

  旋转包括右旋和左旋,具体的操做图例和代码从我以前写的一篇文章复制过来就好。

  右旋:

  

  作法是以A节点为轴,节点A的左子树指向其左孩子B的右子树2,而后节点B的左子树指向节点A,而后本来节点A的父节点R对应的子树指向节点B,其余节点不做变化,这边便完成了左旋操做。

  相应的代码以下:以A点为轴进行右旋

    private void rotateRight(TreeNode pivot) {
        TreeNode leftChild = pivot.getLeft();
        TreeNode grandChildRight = leftChild.getRight();
        TreeNode parent = pivot.getParent();
        if (null == parent) {
            this.root = leftChild;
        } else if (pivot == parent.getLeft()) {
            parent.setLeft(leftChild);
        } else {
            parent.setRight(leftChild);
        }
        leftChild.setParent(parent);

        pivot.setLeft(grandChildRight);
        if (null != grandChildRight) {
            grandChildRight.setParent(pivot);
        }

        leftChild.setRight(pivot);
        pivot.setParent(leftChild);
    }
右旋操做

  左旋:

 

  左旋的操做跟右旋同样,可是结构是相反的,以节点A为轴,节点A的右子树指向其有孩子B的左子树2,而后节点B的左子树指向节点A,再使原节点A的父节点对应的子树指向节点B,其余节点不作改变。

  相应的代码以下:以A点为轴进行左旋

    private void rotateLeft(TreeNode pivot) {
        TreeNode rightChild = pivot.getRight();
        TreeNode grandChildLeft = rightChild.getLeft();
        TreeNode parent = pivot.getParent();
        if (null == parent) {
            // pivot node is root
            this.root = rightChild;
        } else if(pivot == parent.getLeft()) {
            parent.setLeft(rightChild);
        } else {
            parent.setRight(rightChild);
        }
        rightChild.setParent(parent);

        pivot.setRight(grandChildLeft);
        if (null != grandChildLeft) {
            grandChildLeft.setParent(pivot);
        }

        rightChild.setLeft(pivot);
        pivot.setParent(rightChild);
    }
左旋操做

  一样,请紧紧记住这个旋转规则,当须要的时候能够信手拈来,不要卡在这种基础操做上。

  上面已经说到初始化一个新的节点N(New)的时候,节点的颜色设置为红色,而后根据插入的状况能够分为如下两种:

  1、插入节点的父节点P(Parent)是黑色节点

  这种状况很舒服,插入一个红色节点,而父节点又刚好是黑色的,不违反以上某一条定义,插入结束。

  2、插入的节点父节点P是红色节点

  这种状况插入时直接违反了上面第四条定义,从这个条件接下去细分,观察插入节点的叔叔节点U(Uncle)

    ① 若是节点U是红色的,作法是把祖父节点GP(Grandparent)染为红色,并把父节点P和叔叔节点U染为黑色。有人有疑问说那若是祖父节点GP原本就是红色的怎么办,GP节点不可能为红色,由于若是GP节点为红色,那插入以前就违反了第四条定义。不管在什么状况下,请确保插入前的树是一颗合格的红黑树!

   

    若是N为右子树也同理(注意图中省略了Nil节点)

     ② 若是节点U是黑色的(其实就是Nil节点,由于若是若是U不为Nil节点,那N所在的位置原本就应该是一个不为Nil的黑色节点,不然从GP节点下来就会出现两条黑色节点数不一样的路径,与第五条定义相悖),且节点N为节点U的远侄子节点,此时的调整作法是把节点P染为黑色,把节点GP染为红色,并以GP节点根据实际状况作相应的旋转(若节点U为GP的右子树,则以GP为轴作右旋操做,若节点U为GP的左子树,则以GP为轴作左旋操做)。

    

 

    若此时节点N是节点U的近侄子节点,作法是以节点P为轴作相应的旋转操做(若N为P的左子树,则以P为轴作右旋操做,若N为P的右子树,则以P为轴作左旋操做),旋转以后转为上面的状况①,再按照状况①的操做进行调整。

    

 

 

 

 

  这样操做以后确保从GP下来的黑色节点数目在调整先后保持不变,若是此时GP节点不是根节点,那若是GP节点的父节点也是红色的,那此时要把GP当作新插入的节点继续向上调整,调整规则与上面①②一致,直到遇到黑色节点或者根节点为止(主要针对①状况,由于②状况调整以后当前子树的根节点就已是黑色的不会影响整棵树的结构),每次插入结束后若是根节点不是黑色的,根据第二条定义,把根节点设置为黑色。

 

  全部状况处理好以后,开始写代码

 

  写一个插入新元素的公共方法:

 

    public boolean insert(int elem) {
        TreeNode node = new TreeNode(elem);
        boolean inserted = false;
        if (null == this.root) {
            this.root = node;
            inserted = true;
        } else {
            inserted = insertNode(this.root, node);
        }
        setRootBlack(); //the root must be always black
        return inserted;
    }

  

  子方法 private boolean insertNode(TreeNode node, TreeNode newNode) 表示把newNode插入到node的子树当中,插入成功返回true,元素已经存在则返回false,方法体以下:

 

    private boolean insertNode(TreeNode node, TreeNode newNode) {
        if (node.getElem() == newNode.getElem()) {
            return false; // the element already exist
        } else if (node.getElem() < newNode.getElem()) {
            if (null == node.getRight()) {
                node.setRight(newNode);
                newNode.setParent(node);
                insertFixUp(newNode);
                return true;
            } else {
                return insertNode(node.getRight(), newNode);
            }
        } else {
            if (null == node.getLeft()) {
                node.setLeft(newNode);
                newNode.setParent(node);
                insertFixUp(newNode);
                return true;
            } else {
                return insertNode(node.getLeft(), newNode);
            }
        }
    }

 

  插入以后就是调整啦,根据上面的调整规则编写函数 private void insertFixUp(TreeNode node)  表示从node开始向上调整红黑树

 

    private void insertFixUp(TreeNode node) {
        TreeNode parent = node.getParent();
        while (null != parent && parent.getColor() == NodeColor.RED) {
            // parent should not be root for root node must be black
            boolean uncleInRight = parent.getParent().getLeft() == parent;
            TreeNode uncle = uncleInRight ? parent.getParent().getRight() : parent.getParent().getLeft();
            if (null == uncle) {
                // uncle is Nil and could not be black node
                if (uncleInRight) {
                    if (node == parent.getLeft()) {
                        // case 1
                        parent.setColor(NodeColor.BLACK);
                        parent.getParent().setColor(NodeColor.RED);
                        rotateRight(parent.getParent());
                        break;
                    } else {
                        // case 2
                        rotateLeft(parent);
                        node = node.getLeft(); // convert to case 1
                    }
                } else {
                    if (node == parent.getRight()) {
                        // case 3
                        parent.setColor(NodeColor.BLACK);
                        parent.getParent().setColor(NodeColor.RED);
                        rotateLeft(parent.getParent());
                        break;
                    } else {
                        // case 4
                        rotateRight(parent);
                        node = node.getRight(); // convert to case 3
                    }
                }
            } else {
                // uncle node is red
                parent.setColor(NodeColor.BLACK);
                uncle.setColor(NodeColor.BLACK);
                parent.getParent().setColor(NodeColor.RED);
                node = parent.getParent();
            }
            parent = node.getParent();
        }
    }

  

  至此红黑树插入操做结束,步骤也是相对简单,但愿对你们的理解有所帮助。

 

 

 

 

 

 

 

  尊重知识产权,引用转载请通知做者!

相关文章
相关标签/搜索