5.红黑树代码实现-PUT

        上文我们成功构建了红黑树,接下来我们将对红黑树进行代码实现。在代码实现前,我们需要图像添加过程规律进行总结,方便我们进行代码实现。

红黑树性质:

1.每个结点要么是红的要么是黑的。

2.根结点是黑的。

3.如果一个结点是红的,那么它的两个儿子都是黑的。(红色不相连)

4.对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

1.PUT规律

通过构建过程可以看出我们所以的操作还是围绕红黑树与234树的节点对应关系进行操作的,所以我们还是回归到对于2节点,3节点和4节点添加过程进行规律总结。

 2节点 新增 : 

 二节点+新增节点:上黑下红,不需要调整

3节点新增:

 三节点+新增节点:根据基本性质,只有中间3黑2、4红满足,其他形态都需要通过旋转变色后才能满足。3黑2、4红也是才是正真的稳定态。

4节点新增:分裂态

分裂态 :只需要将对应节点变色便可以平衡

对红黑树的添加元素其实质就是对于2节点,3节点和4节点添加,通过观察总结以下规律:

1.新增节点时,先以红色节点挂载到红黑树上,在进行调整操作

2.只有父节点为红色节点时才会进行调整

3.2节点+新增节点情况,不需要进行调整。 

4.3节点+新增节点。当新增后无叔叔节点时需要进行调整,且2,4状态时需要先调整父节点后才方便调整到稳定态

5.裂变态+新增节点:新增后有叔叔节点,变色调整。但是这里由于3元素节点进行了变色,3元素之前的平衡可能给打破,这里需要向上递归来进行平衡校验

添加过程的总体操作是:先添加 在调整

2节点添加:添加后不处理

3节点添加:需要处理,需要判断什么情况是3节点-》没有叔叔节点则是3节点

4节点添加:裂变太需要处理,如何判断4节点 -》存在叔叔节点则是4节点

2.代码实现

1.红黑树节点:节点添加是双向的,所以需要指向父节点

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

        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;
        }

        public boolean isColor() {
            return color;
        }

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

        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 RBNode(K key, V value, boolean color, RBNode parent, RBNode left, RBNode right) {
            this.key = key;
            this.value = value;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

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

    }

2.put

 public void put(K key, V value) {
        //判断根为空不
        RBNode target = this.root;
        if (target == null) {
            root = new RBNode(key, value, null);
            return;
        }
        //命名双亲节点,记录上一个节点是谁,因为要遍历到null节点

        RBNode parent;
        int cmp;
        do {
            parent = target;
            cmp = key.compareTo((K) target.key);
            if (cmp > 0) {
                target = target.right;
            } else if (cmp < 0) {
                target = target.left;
            } else {
                target.setValue(value);
                return;
            }

        } while (target != null);
        RBNode<K, V> e = new RBNode<>(key, value, parent);

        if (cmp > 0) {
            parent.right = e;
        } else if (cmp < 0) {
            parent.left = e;
        }

        //添加后调整
        fixAfterPut(e);
    }

调整:

 /**
     *
     *                             pr    pr
     *        p        p          /      /
     *         \       \         p      p
     *          pr      pr      /        \
     *           \      /     pl          pr
     *           rr     rl
     * @param p
     */
    //注意以上四种形态需要进行旋转调整位置
    /**
     * 2节点+新增元素:新红父黑 --不需要调整
     *
     *3节点+新增元素:
     *红色节点+上黑下红 =》排序后中间节点为黑色,两边节点为红色
     *
     * 4节点+新增元素:开始分裂,中间元素升级为父节点,新增元素于剩下其中一个合并
     * 红黑树:新增红色节点+爷爷节点黑,父节点和叔叔节点都是红色  =》爷爷节点变红,父亲和叔叔节点变黑  如果爷爷是根节点,则爷爷变黑
     *
     *
     */
    private void fixAfterPut(RBNode x) {
        //当前节点不为空 ,根节点不是根节点,x的父节点为红色
        while (x != null && x != root && x.parent.color == RED) {
            //1.处理左三
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                //叔叔节点为空和不为空(
                RBNode uncle = rightOf(parentOf(parentOf(x)));
                //(4节点情况)  这里没有必要判断uncle为空,因为空为黑色
                if (colorOf(uncle) == RED) {
                    setRedColor(parentOf(parentOf(x)));
                    setBlackColor(parentOf(x));
                    setBlackColor(uncle);
                    //这里涉及到分裂的情况,分裂后爷爷之前的也许打破了平衡,
                    // 所以需要把x指向爷爷,然后向上递归
                    x = parentOf(parentOf(x));
                } else {
                    //(3节点情况)
                    //判断是不是左右(变种)
                    if (x == rightOf(parentOf(x))) {
                        //x= parentOf(x); //x的父亲为x  ,不会打破平衡,所以不需要递归
                        rotateLeft(parentOf(x)); //将结构改为左左
                    }

                    //变色
                    setBlackColor(parentOf(x));
                    setRedColor(parentOf(parentOf(x)));

                    //针对爷爷you旋转
                    rotateRight(parentOf(parentOf(x)));

                }
            } else {
                //2.处理右三
                //叔叔节点为空和不为空(
                RBNode uncle = leftOf(parentOf(parentOf(x)));
                //(4节点情况)  这里没有必要判断uncle为空,因为空为黑色
                if (colorOf(uncle) == RED) {
                    setRedColor(parentOf(parentOf(x)));
                    setBlackColor(parentOf(x));
                    setBlackColor(uncle);
                    //这里涉及到分裂的情况,分裂后爷爷之前的也许打破了平衡,
                    // 所以需要把x指向爷爷,然后向上递归
                    x = parentOf(parentOf(x));
                } else {
                    //(3节点情况)
                    //判断是不是右左(变种)
                    if (x == leftOf(parentOf(x))) {
                        //x= parentOf(x); //x的父亲为x   ,不会打破平衡,所以不需要递归
                        rotateRight(parentOf(x)); //将结构改为右右
                    }
                    //变色
                    setBlackColor(parentOf(x));
                    setRedColor(parentOf(parentOf(x)));
                    //针对爷爷zuo旋转
                    rotateLeft(parentOf(parentOf(x)));
                }

            }
        }
        //将根节点变黑
        setBlackColor(root);
    }

3.HashMap源码

当HashMap桶中的元素个数超过一定数量时,就会将链表转化为红黑树的结构,具体源码如下:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ...省略部分代码...
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //当桶中元素个数超过阈值(8)时就进行树化
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
         ...省略部分代码...
    }

    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                //将节点替换为TreeNode
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)            //hd指向头结点
                    hd = p;
                else {
                    //这里其实是将单链表转化成了双向链表(方便向tl的前一个位置插入)
                  //tl是p的前驱,每次循环更新指向双链表的最后一个元素,用来和p相连,p是当前节点
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                //将链表进行树化
                hd.treeify(tab);
        }
    }  

从代码中可以看到,在treeifyBin函数中,先将所有节点替换为TreeNode,然后再将单链表转为双链表,方便之后的遍历和移动操作,。而最终的操作,实际上是调用TreeNode的方法treeify进行的。

final void treeify(Node<K,V>[] tab) {
            //树的根节点
            TreeNode<K,V> root = null;
            //x是当前节点,next是后继
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                //如果根节点为null,把当前节点设置为根节点
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    //这里循环遍历,进行二叉搜索树的插入
                    for (TreeNode<K,V> p = root;;) {
                        //p指向遍历中的当前节点,x为待插入节点,k是x的key,h是x的hash值,ph是p的hash值,dir用来指示x节点与p的比较,-1表示比p小,1表示比p大,不存在相等情况,因为HashMap中是不存在两个key完全一致的情况。
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        //如果hash值相等,那么判断k是否实现了comparable接口,如果实现了comparable接口就使用compareTo进行进行比较,如果仍旧相等或者没有实现comparable接口,则在tieBreakOrder中比较
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {//找到插入点
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;                 //进行插入平衡处理
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }       //确保给定节点是桶中的第一个元素
            moveRootToFront(tab, root);
        }    
     //这里不是为了整体排序,而是为了在插入中保持一致的顺序
     static int tieBreakOrder(Object a, Object b) {
            int d;
            //用两者的类名进行比较,如果相同则使用对象默认的hashcode进行比较
            if (a == null || b == null ||
                (d = a.getClass().getName().
                 compareTo(b.getClass().getName())) == 0)
                d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
                     -1 : 1);
            return d;
        }  

这里的逻辑其实不复杂,仅仅是循环遍历当前树,然后找到可以该节点可以插入的位置,依次和遍历节点比较,比它大则跟其右孩子比较,小则与其左孩子比较,依次遍历,直到找到左孩子或者右孩子为null的位置进行插入。

balanceInsertion函数,将红黑树进行插入平衡处理,保证插入节点后仍保持红黑树的性质。

 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true;
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {                
                if ((xp = x.parent) == null) {  //插入节点为根节点,变色即可
                    x.red = false;
                    return x;
                }         
                else if (!xp.red || (xpp = xp.parent) == null) //父节点是黑色,不需要调整
                    return root;          
                if (xp == (xppl = xpp.left)) {  // 其父节点是祖父节点的左节点           
                    if ((xppr = xpp.right) != null && xppr.red) {// 裂变态 插入的节点父节点和祖父节点都存在,变色即可
                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;  //变色后 x位置到爷爷位置,向上递归
                    }            
                    else {              
                        if (x == xp.right) { //3节点新增 左右态   插入节点有父亲右边 ,,需要在父节点旋转成 左左
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }              
                        if (xp != null) {  //3节点新增 左左态 
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }  
                        
                else { // 其父节点是祖父节点的右节点,和左节点类似,这里不做详细分析           
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }           
                    else {·              
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }              
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

这个函数稍后在TreeNode的插入中进行介绍,这里先看看moveRootToFront,这个函数是将root节点移动到桶中的第一个元素,也就是链表的首节点,这样做是因为在判断桶中元素类型的时候会对链表进行遍历,将根节点移动到链表前端可以确保类型判断时不会出现错误。

/**
 * 把给定节点设为桶中的第一个元素
 */        
    static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
            int n;
            if (root != null && tab != null && (n = tab.length) > 0) {
                int index = (n - 1) & root.hash;
                //first指向链表第一个节点
                TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
                if (root != first) {
                    //如果root不是第一个节点,则将root放到第一个首节点位置
                    Node<K,V> rn;
                    tab[index] = root;
                    TreeNode<K,V> rp = root.prev;
                    if ((rn = root.next) != null)
                        ((TreeNode<K,V>)rn).prev = rp;
                    if (rp != null)
                        rp.next = rn;
                    if (first != null)
                        first.prev = root;
                    root.next = first;
                    root.prev = null;
                }
                //这里是防御性编程,校验更改后的结构是否满足红黑树和双链表的特性
                //因为HashMap并没有做并发安全处理,可能在并发场景中意外破坏了结构
                assert checkInvariants(root);
            }
        }    

由此我们变明白红黑树的整个插入过程是如何进行的。但是正真有难度的是红黑树的删除,但是明白红黑树节点等级关系后,删除也不是什么难题。后续我们会进一步讲解红黑树的删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值