JDK TreeMap 红黑树学习

通过分析JDK源代码研究TreeMap以及红黑树实现

JDK容器类源码学习好地方


按照学习习惯:

1. what

    红黑树是什么? 排序二叉树的一种,针对排序二叉树在有序输入的情况下会成为链表,添加了5个性质,构造出来的一种最长路径不会超过最短路径的2倍,所以在最坏情况下检索,插入和删除的时间复杂度是 O(lgn).  五个性质分别是什么:1 . 根节点必须是黑.   2. 叶子节点是黑而且是null  3.红节点的子节点必须是黑。 4. 从同一个节点出发的每条路径的黑色高度相同。5. 要么红要么黑

    红黑的删除操作:上面的JDK源码研究了红黑树的插入,下面的说明了删除

   

2. why

  为什么红黑树可以做到前人(排序树)做不到的事情!下面说明原因:

   (1)每次插入的节点都标为红色

    (2) 从根节点出发的所有路径的黑色高度相同

     (3)红节点的子节点必须是黑

    (4) 根节点必须是黑

      从第2点和第3点,就可以得到最长路径不会超过最短路径的2倍,所以能实现O(lgn)

3. where

    (1)JDK Collection TreeMap

     (2)关联数组

    (3) 字典实现

    (4)内存中缓存的(区块-数据)

      红黑树应用实例

4. example

  下面研究JDK的TreeMap源码:

   首先有个comparator定制的比较器

   有个 Entry<K,V>

   size 和  modCount

  

   Entry<K,V>的结构:

   六个成员变量, key , value, parent, left , right , color

   Entry<K,V> 的构造方式很简单,主要是看看内部成员

   TreeMap有四种构造器:

   1. 空

   2. 一个comparator

   3. 一个Map

    4. SortedMap带有一个comparator把他给这个TreeMap的comparator

  

  

final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
       // 从root开始往下循环,使用key(SortedMap的key必须实现comparable接口)的compareTo
       while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }
   

  

    final Entry<K,V> getEntryUsingComparator(Object key) {
        K k = (K) key;
        // 这里的Comparator必须是key的K的父类或者父接口
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
              // 使用comparator的compare method来比较,原理其实和上面的compareTo method一样
               int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }


   这里有TreeMap的一篇文章: TreeMap源码分析  

    private void fixAfterInsertion(Entry<K,V> x) {  
        x.color = RED;  
        // 这里就可以判断 祖父是红
        while (x != null && x != root && x.parent.color == RED) {  
            // 此情况符合1个条件:1. 父亲是祖父的左孩子        
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {                 
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));  
                // 此情况符合2个条件:1. 父亲是祖父的左孩子 
                //                 2. 叔叔是红
                if (colorOf(y) == RED) {  
                    setColor(parentOf(x), BLACK);  
                    setColor(y, BLACK); 
                    setColor(parentOf(parentOf(x)), RED);  
                    /*  x的祖父是新的 x 知道更新到 循环条件不成立。
                      能直接进行循环的原因是通过上述可知不会造成黑色高度有变 */
                    x = parentOf(parentOf(x));  
                } 
                // 此情况符合2个条件:1. 父亲是祖父的左孩子 
                //                 2. 叔叔是黑
                else {  
                    // 此情况符合3个条件:1. 父亲是祖父的左孩子 
                    //                 2. 叔叔是黑
                    //                3. 儿子是父亲的右孩子
                    if (x == rightOf(parentOf(x))) {  
                        x = parentOf(x);  
                        rotateLeft(x);  
                    }  
                    
                    // 此情况符合3个条件:1. 父亲是祖父的左孩子 
                    //                 2. 叔叔是黑
                    //                3. 儿子是父亲的左孩子
                    setColor(parentOf(x), BLACK);  
                    setColor(parentOf(parentOf(x)), RED);  
                    rotateRight(parentOf(parentOf(x)));  
                }  
            } 
            // 此情况符合1个条件:1. 父亲是祖父的右孩子
            else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));  
                // 此情况符合2个条件:1. 父亲是祖父的右孩子
                //                 2. 叔叔是红
                if (colorOf(y) == RED) {  
                    setColor(parentOf(x), BLACK);  
                    setColor(y, BLACK);  
                    setColor(parentOf(parentOf(x)), RED);  
                    x = parentOf(parentOf(x));  
                } 
                // 此情况符合2个条件:1. 父亲是祖父的右孩子
                //                 2. 叔叔是黑
                else {  
                    // 此情况符合2个条件:1. 父亲是祖父的右孩子
                    //                 2. 叔叔是黑
                    //                 3. 儿子是父亲的左孩子
                    if (x == leftOf(parentOf(x))) {  
                        x = parentOf(x);  
                        rotateRight(x);  
                    }  
                    // 此情况符合2个条件:1. 父亲是祖父的右孩子
                    //                 2. 叔叔是黑
                    //                 3. 儿子是父亲的右孩子
                    setColor(parentOf(x), BLACK);  
                    setColor(parentOf(parentOf(x)), RED);  
                    rotateLeft(parentOf(parentOf(x)));  
                }  
            }  
        }  
        root.color = BLACK;  
    }  

 

  前提: 1. 插入节点的父亲是红色,进入调整函数的循环部分

  其实可以说出个流程: 条件有三: 1. 父亲是祖父的左/右孩子

                                                                2.叔叔是红/黑

                                                               3. 儿子是父亲的左/右孩子

  注意点: 1. 叔叔是红不用旋转,但是可能会影响到祖父一代的黑色高度。

                2. 叔叔是黑,把父亲和儿子旋转到同一阵线。以祖父为轴旋转



下面研究删除:

	//返回后继节点,t是待删除节点,返回的是他的后继节点
	static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
		if (t == null)
            return null;
		//  待删除节点有右孩子
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            //遍历直到右孩子的一个没有左孩子的节点。
            while (p.left != null)
                p = p.left;
            //这就是他的后继,返回
            return p;
        } else {
        	//无右孩子
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            //实际上还是找后继,二叉搜索树找后继很麻烦
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

要研究删除首先要考虑二叉搜索树的后继,不过这个不是重点。

这里需要注意,后继只可能有右子树

这里有一个前提:只有当删除的节点是黑色的时候才会需要调用调整函数

private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        // 恩,明白了。
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
        	// replacement向上移动,p被删除
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            // 正式删除p节点
            p.left = p.right = p.parent = null;

            // Fix replacement
            // 如果删除的后继节点p是黑色的话
            // 如果是红色,那么他的子几点必定是黑色,黑的子节点对性质4和5是无影响的。
            if (p.color == BLACK)
            	// 通过旋转和重涂调整
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
            	// 当删除的节点是黑色的时候才需要fix
                fixAfterDeletion(p);

            //下面是通过直接set空来处理
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

下面是 删除调整函数:

前提:代替节点为黑才能进入调整函数的循环体内

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值