java集合框架(四):TreeMap

        TreeMap的数据结构与HashMap、LinkedHashMap不一样,其整个结构就是一颗红黑树,因此咱们要研究TreeMap就得先从红黑树开始。对于红黑树的算法,我在本文章不详细展开,有兴趣的同窗能够点击这里学习。本文主要是剖析红黑树的原理,以及解读TreeMap是如何运用红黑树实现的。html

        红黑树是什么?咱们能够从《数据结构与算法分析》这本书找到解析:红黑树是具备着色性质的二叉查找树,是AVL树(自平衡二叉查找树)的一个变种。接下来我从它的基本定义中来说解红黑树。java

1、二叉查找树

        二叉查找树,也能够叫作二叉排序树,他具备排序的功能。二叉树要成为二叉查找树需知足如下条件:算法

  1. 若左子树不空,则左子树上全部节点的值均小于父节点的值。
  2. 若右子树不空,则右子树上全部节点的值均大于父节点的值。
  3. 左、右子树也分别为二叉查找树。

        由以上条件可知,下图左边为二叉查找树,右边为普通二叉树。二叉查找树的平均深度为O(log2 n)。深度是指对于任何一个节点,根节点到其自己的惟一路径长。根节点的深度为0,以下图所示,节点值为2的深度是1,节点值为4的深度是2。api

2、AVL树(自平衡二叉查找树

        二叉查找树的平均深度是为O(log2 n), 可是也会出现一些极端的状况,若是插入的节点集自己就是有序的,要么从大到小排序,要么从小到大排序,就会出现以下图的结果。在这种状况下,排序二叉树就变成了普通链表,其查找效率就会不好。数据结构

        为了解决这种极端状况,两位科学家 G.M. Adelson-Velsky 和 E.M. Landis就提出了自平衡的二叉查找树,AVL树也就来自于他们两个的名字组合。AVL树能保持自平衡的条件是二叉查找树每一个节点的左子树和右子树的高度最多差1。其中高度是指一个节点到叶子节点的最长路径,所以任何一个叶子节点的高度为0.以下图所示,左图根节点的左子树的高度为2,右子树的高度为1,相差为1,因此是AVL树。右图根节点左子树的高度为2,右子树高度为0,相差为2,因此不是AVL树。app

                        左图.AVL树                                        右图.非AVL树ide

        AVL树可以保证树的深度为O(log2 n),所以它可以提供较好的查找性能。它的缺点是为了保持平衡,添加或者删除节点时须要进行复杂的平衡操做,须要很大的性能开销。函数

3、红黑树

        红黑树是二叉查找树,它不追求“彻底平衡”,只要求部分达到平衡,其在添加或者删除节点时不须要太多复杂的旋转操做就能够保持平衡,任何不平衡在三次旋转内解决。红黑树可以以O(log2 n)的时间复杂度进行搜索、插入、删除操做。性能

        红黑树就是牺牲了高度平衡的性质换取了较高性能的插入和删除操做,为了保持其自身的部分平衡,着色须要知足下面条件:学习

  1. 每个节点要么着成红色,要么着成黑色。
  2. 根是黑色的。
  3. 若是一个节点时红色的,那么它的子节点必须是黑色的。
  4. 从一个节点到一个null引用的每一条路径必须包含相同数目的黑色节点。

        上图为红黑树的例图。当插入或者删除节点时,红黑树是经过变色和旋转来同时保证4个条件知足的,读者若是有兴趣研究他们的变换原理,能够点击此处在线构建红黑树。

4、类的定义

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable{}
public interface NavigableMap<K,V> extends SortedMap<K,V> {}

        TreeMap实现了NavigableMap(导航map,里面定义了一些接口,不多用到),而NavigableMap则继承了SortMap接口(要求实现类须要对key排序),故TreeMap间接实现了SortMap,也就会实如今SortMap中定义的对key排序的接口。类关系图以下所示:

5、构造器

public TreeMap() {
    // 比较器为空
    comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
     // 对比较器赋值,插入节点到红黑树中会用到这个比较器来比较key的大小,提早是key要实现这个比较器
     // 对key的排序规则能够经过这个比较器来指定(大到小或者小到大)
     this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) {
    comparator = null;
    // 把外面传进来的key-value加入到红黑树
    putAll(m);
}
// 该构造器是把已经排序好的SortMap节点从新构形成红黑树,并把SortMap的比较器赋值给TreeMap
public TreeMap(SortedMap<K, ? extends V> m) {
        // 使用SortMap的比较器
        comparator = m.comparator();
        try {
            // 构造红黑树
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
}

6、存储的实现

1.put方法

        put方法就是把节点加到红黑树中,从源码中能够看到,key不能为空。为了插入红黑树时对key做比较,key要么实现Comparator接口,要么实现Comparable接口

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            // 对key判空和类型检查
            compare(key, key);

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        Comparator<? super K> cpr = comparator;
        // 比较器不为空就使用Comparator来对key作比较
        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);
            } while (t != null);
        }
        // 使用Comparable接口做比较
        else {
            // key不能为空,不然抛出异常
            if (key == null)
                throw new NullPointerException();
            // 这里作了强转型,若是key没有实现Comparable接口会抛异常
            @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++;
        // fast-fail机制
        modCount++;
        return null;
}

2.get操做

        get操做是比较容易理解的,它从根节点查找key,找到就返回value。它分开了两种比较器查找过程。

public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
        // 用Comparator比较器获取
        if (comparator != null)
            return getEntryUsingComparator(key);
        // key不能为空
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        // 从根节点开始查找
        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;
}
// 用Comparator比较器查找
final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            K k = (K) key;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            Entry<K,V> p = root;
            while (p != null) {
                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;
}

7、遍历的实现

        TreeMap的遍历操做是在内部抽象类PrivateEntryIterator中完成的,其调用外部类的successor方法对红黑树进行遍历,根据key值大小的顺序(大到小或者小到大,由比较器决定)遍历,其实就是从左往右遍历红黑树。在使用中,若是咱们有根据key值大小的顺序遍历map的需求,可使用TreeMap,可是key必须实现Comparator或者Comparable接口

abstract class PrivateEntryIterator<T> implements Iterator<T> {
        Entry<K,V> next;
        Entry<K,V> lastReturned;
        int expectedModCount;
        // 构造器参数first是红黑树最左边的节点,也就是key最小的节点
        PrivateEntryIterator(Entry<K,V> first) {
            expectedModCount = modCount;
            lastReturned = null;
            next = first;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            // fast-fail机制
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            // 这个方法就是遍历红黑树的
            next = successor(e);
            lastReturned = e;
            return e;
        }
}
// 返回红黑树的下一个节点,t为当前遍历的节点。遍历的key是从小到大
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;
        }
}

8、排序的例子

1.升序排序:

public static void main(String[] args) {

        InnerClass inner1 = new InnerClass(1);
        // 把比较器传到构造函数
        Map<InnerClass,String> map = new TreeMap<InnerClass, String>(inner1);

        InnerClass inner2 = new InnerClass(2);
        InnerClass inner3 = new InnerClass(3);
        InnerClass inner4 = new InnerClass(4);
        InnerClass inner5 = new InnerClass(5);

        map.put(inner1,"1");
        map.put(inner2,"2");
        map.put(inner3,"3");
        map.put(inner4,"4");
        map.put(inner5,"5");

        for(Map.Entry<InnerClass,String> entry:map.entrySet()){
            System.out.print(entry.getValue() + ",");
        }

    }

    // 内部类实现比较器
    static class InnerClass implements Comparator{
        int num = 0;
        public InnerClass(int num) {
            this.num = num;
        }

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public int compare(Object o1, Object o2) {
            InnerClass inner1 = (InnerClass)o1;
            InnerClass inner2 = (InnerClass)o2;
            // 对key升序排
            if(inner1.getNum() > inner2.getNum()){
                return 1;
            }else if (inner1.getNum() < inner2.getNum()){
                return -1;
            }else {
                return 0;
            }
        }
    }

升序排序结果:1,2,3,4,5,

2.降序排序

// 内部类实现比较器
    static class InnerClass implements Comparator{
        int num = 0;
        public InnerClass(int num) {
            this.num = num;
        }

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public int compare(Object o1, Object o2) {
            InnerClass inner1 = (InnerClass)o1;
            InnerClass inner2 = (InnerClass)o2;
            // 对key降序排
            if(inner1.getNum() < inner2.getNum()){
                return 1;
            }else if (inner1.getNum() > inner2.getNum()){
                return -1;
            }else {
                return 0;
            }
        }
    }

降序排序结果:5,4,3,2,1,

9、总结

  1. TreeMap里面还有不少方法在本文中没有说起,我以为只要把TreeMap的结构和原理理解清楚了,之后使用中若是有操做TreeMap方面的须要能够到jdk的api或者到源码中找。
  2. TreeMap的数据结构就是红黑树,红黑树的高度最可能是2log2(n+1),它牺牲了avl树的高度平衡特性,换取了高效的插入、删除、搜索性能(时间复杂度都是O(log2n)),构建红黑树的时候任何不知足红黑树条件最多3次旋转变色会解决。
  3. TreeMap相对于HashMap而言,插入节点的时候不用考虑扩容,消除了扩容的性能开销。
  4. TreeMap能够根据key值的大小顺序遍历节点,在对key-value遍历有作顺序要求的场合能够考虑使用TreeMap。
  5. TreeMap的key值不能为空,且key必须实现Comparator接口或者Comparable接口,不然使用的时候会抛出异常。

以上就是我对红黑树的理解,若有不足之处,请批评和指正!

参考资料:

        博客园:http://www.cnblogs.com/wzyxidian/p/5204879.html

相关文章
相关标签/搜索