zoukankan      html  css  js  c++  java
  • Java_JDK_TreeMap

    (一)TreeMap

    TreeMap使用的是红黑树来实现的,所以重点是红黑树的插入和删除。

    红黑树的3个特性:

    • 根节点和所有外部节点的颜色都是黑色的;
    • 从根节点到外部节点的途中没有连续两个节点的颜色是红色;
    • 所有从根节点到外部节点的路径上都有相同数目的黑色节点。

    java中TreeMap的节点结构:Entry结构,是TreeMap的一个内部类

       static final class Entry<K,V> implements Map.Entry<K,V> {
            K key;
            V value;
            Entry<K,V> left;
            Entry<K,V> right;
            Entry<K,V> parent;
            boolean color = BLACK;
    
            /**
             * Make a new cell with given key, value, and parent, and with
             * {@code null} child links, and BLACK color.
             */
            Entry(K key, V value, Entry<K,V> parent) {
                this.key = key;
                this.value = value;
                this.parent = parent;
            }
    }

    下面来分析插入操作:

    要注意的是:

    • 要插入的节点总是红色的;

    插入共分种情况:

    • 插入的是根节点,则直接插入即可,然后将根节点设置为黑色;
    • 插入的是非根节点,则要看找到应该插入的位置进行插入即可。

    java中TreeMap实现插入算法主要分为两个函数:put(K key, V value), 和fixAfterInsertion(Entry<K, V> x).

    (1)put(K key, V value):

    public V put(K key, V value) {
            Entry<K,V> t = root; //从根节点开始找要插入的位置
            if (t == null) {//如果是一颗空树,直接插入即可
                compare(key, key); // type (and possibly null) check
    
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            Entry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator; //使用指定的排序算法
            if (cpr != null) { 
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value); //t即为要插入的位置
                } while (t != null);
            }
            else { //使用默认的排序算法
                if (key == null)
                    throw new NullPointerException();
                @SuppressWarnings("unchecked")
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            fixAfterInsertion(e); //插入后进行红黑颜色的调整
            size++;
            modCount++;
            return null;
        }

    (2)fixAfterInsertion(Entry<K, V> x)函数:

    其中,使用了红黑树的rotateLeft 和rotateRight方法:

       /** From CLR */
        private void rotateLeft(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> r = p.right;
                p.right = r.left;
                if (r.left != null)
                    r.left.parent = p;
                r.parent = p.parent;
                if (p.parent == null)
                    root = r;
                else if (p.parent.left == p)
                    p.parent.left = r;
                else
                    p.parent.right = r;
                r.left = p;
                p.parent = r;
            }
        }
    
        /** From CLR */
        private void rotateRight(Entry<K,V> p) {
            if (p != null) {
                Entry<K,V> l = p.left;
                p.left = l.right;
                if (l.right != null) l.right.parent = p;
                l.parent = p.parent;
                if (p.parent == null)
                    root = l;
                else if (p.parent.right == p)
                    p.parent.right = l;
                else p.parent.left = l;
                l.right = p;
                p.parent = l;
            }
        }

    下面来分析删除操作:

    删除其实是非常麻烦的一个操作,但是这里Java并不是直接删除节点,而是找到一个最方便删除的节点替换至要删除的节点,然后删除即可。

    什么意思呢?

    简而言之,删除操作共分三种情况:

    • 要删除的节点p是根叶子节点:
      • 根据特性1,我们知道所有外部节点都是黑色的,故p一定是黑色的。但是这样冒然删去p一定会导致某条路径上少一个黑色节点,所以需要进行红黑调整;
    • 要删除的节点p的左孩子或者右孩子中有一个是空的:
      • 这种情况也很简单,直接修改指针删除p就可以了;
      • 同样,如果删除的p是黑色的也要进行红黑调整;
    • 要删除的节点p的左孩子和右孩子都不为空:
      • 这时需要找到p的前驱,即左孩子的最右边或者右孩子的最左边;
      • 然后将找到的前驱节点替换p,然后删除其前驱节点就OK啦~

    在java中,删除主要分两个部分,一个是找到要删除的节点,是由deleteEntry(Entry<K, V> p)函数实现的;一个是调整颜色,是由fixAfterDeletion(Entry<K, V> x)实现的。

    下面分别进行分析:

    (1)deleteEntry函数:这里就是将上面三种情况考虑好,然后找到要删除的节点进行删除,中间调用了fixAfterDeletion函数。

     /**
         * Delete node p, and then rebalance the tree.
         */
        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) { //如果左右子树都不为空,则找到p的前驱(右子树的最左节点或者左子树的最右节点)s,然后用s来代替p,p指向s,则最终删除s即可。
                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.
       //如果上一个if不满足,则说明至少有一个子树时空的,另replacement指向不空的那个子树的根节点,直接删除p即可
            Entry<K,V> replacement = (p.left != null ? p.left : p.right);
    
            if (replacement != null) {
                // Link replacement to parent
                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.left = p.right = p.parent = null;
    
                // Fix replacement,删除p之后,需要保持红黑树的特性,所以要调节他们的颜色
                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. 
                        //  p为叶子节点,可以直接删除,但如果是黑色节点需要调整
                if (p.color == BLACK)
                    fixAfterDeletion(p);
    
                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;
                }
            }
        }        

     (2)fixAfterDeletion()函数:

       /** From CLR */
        private void fixAfterDeletion(Entry<K,V> x) {
            while (x != root && colorOf(x) == BLACK) {
                if (x == leftOf(parentOf(x))) {
                    Entry<K,V> sib = rightOf(parentOf(x));
    
                    if (colorOf(sib) == RED) {
                        setColor(sib, BLACK);
                        setColor(parentOf(x), RED);
                        rotateLeft(parentOf(x));
                        sib = rightOf(parentOf(x));
                    }
    
                    if (colorOf(leftOf(sib))  == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                        setColor(sib, RED);
                        x = parentOf(x);
                    } else {
                        if (colorOf(rightOf(sib)) == BLACK) {
                            setColor(leftOf(sib), BLACK);
                            setColor(sib, RED);
                            rotateRight(sib);
                            sib = rightOf(parentOf(x));
                        }
                        setColor(sib, colorOf(parentOf(x)));
                        setColor(parentOf(x), BLACK);
                        setColor(rightOf(sib), BLACK);
                        rotateLeft(parentOf(x));
                        x = root;
                    }
                } else { // symmetric
                    Entry<K,V> sib = leftOf(parentOf(x));
    
                    if (colorOf(sib) == RED) {
                        setColor(sib, BLACK);
                        setColor(parentOf(x), RED);
                        rotateRight(parentOf(x));
                        sib = leftOf(parentOf(x));
                    }
    
                    if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                        setColor(sib, RED);
                        x = parentOf(x);
                    } else {
                        if (colorOf(leftOf(sib)) == BLACK) {
                            setColor(rightOf(sib), BLACK);
                            setColor(sib, RED);
                            rotateLeft(sib);
                            sib = leftOf(parentOf(x));
                        }
                        setColor(sib, colorOf(parentOf(x)));
                        setColor(parentOf(x), BLACK);
                        setColor(leftOf(sib), BLACK);
                        rotateRight(parentOf(x));
                        x = root;
                    }
                }
            }
    
            setColor(x, BLACK);
        }
    View Code

    具体的分析见下图:

    还有一种对称的情况,不再额外进行分析。

    完整的fixAfterDeletion如下:

       /** From CLR */
        private void fixAfterDeletion(Entry<K,V> x) {
            while (x != root && colorOf(x) == BLACK) {
                if (x == leftOf(parentOf(x))) {
                    Entry<K,V> sib = rightOf(parentOf(x));
    
                    if (colorOf(sib) == RED) {
                        setColor(sib, BLACK);
                        setColor(parentOf(x), RED);
                        rotateLeft(parentOf(x));
                        sib = rightOf(parentOf(x));
                    }
    
                    if (colorOf(leftOf(sib))  == BLACK &&
                        colorOf(rightOf(sib)) == BLACK) {
                        setColor(sib, RED);
                        x = parentOf(x);
                    } else {
                        if (colorOf(rightOf(sib)) == BLACK) {
                            setColor(leftOf(sib), BLACK);
                            setColor(sib, RED);
                            rotateRight(sib);
                            sib = rightOf(parentOf(x));
                        }
                        setColor(sib, colorOf(parentOf(x)));
                        setColor(parentOf(x), BLACK);
                        setColor(rightOf(sib), BLACK);
                        rotateLeft(parentOf(x));
                        x = root;
                    }
                } else { // symmetric
                    Entry<K,V> sib = leftOf(parentOf(x));
    
                    if (colorOf(sib) == RED) {
                        setColor(sib, BLACK);
                        setColor(parentOf(x), RED);
                        rotateRight(parentOf(x));
                        sib = leftOf(parentOf(x));
                    }
    
                    if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                        setColor(sib, RED);
                        x = parentOf(x);
                    } else {
                        if (colorOf(leftOf(sib)) == BLACK) {
                            setColor(rightOf(sib), BLACK);
                            setColor(sib, RED);
                            rotateLeft(sib);
                            sib = leftOf(parentOf(x));
                        }
                        setColor(sib, colorOf(parentOf(x)));
                        setColor(parentOf(x), BLACK);
                        setColor(leftOf(sib), BLACK);
                        rotateRight(parentOf(x));
                        x = root;
                    }
                }
            }
    
            setColor(x, BLACK);
        }

     

  • 相关阅读:
    网络基础
    关于actionscript中新建一个sprite,设置大小(宽高)的问题。
    Android SDK无法更新问题解决 ---- 还可解决无法上google的问题
    android apk简单反编译
    Flash的坑之ExternalInterface.call只返回null值的解决办法
    Flash Socket简单调试工具
    进制转换 正进制
    Codeforces Div3 #498 A-F
    UVa10082
    div与div之间的拖拽
  • 原文地址:https://www.cnblogs.com/little-YTMM/p/5569485.html
Copyright © 2011-2022 走看看