TreeMap是基于红黑树结构的一种Map,要分析TreeMap的实现首先要了解红黑树这种数据结构。
二叉树、红黑树简介
先简单总结一下数组,链表,Hash表以及树的优缺点:
数组
- 优点:随机访问效率高(根据下标查询);搜索效率较高(可使用折半方法)
- 缺点:内存连续且固定,存储效率低;插入和删除效率低(可能会进行数组扩容或者拷贝)
链表
- 优点:不要求连续内存,存储效率高;插入和删除效率高(只需要改变指针指向)
- 缺点:不支持随机访问;搜索效率低(需要遍历)
哈希表
- 优点:搜索效率高;增删效率高
- 缺点:内存利用率低(基于数组);存在散列冲突
二叉树
- 优点:查询效率高;增删效率高;存储效率高;
- 缺点:算法复杂
二叉树
二叉树的特点:
- 若左子树不为空,则左子树上所有结点的值均小于它的根结点的值
- 若右子树不为空,则右子树上所有结点的值均大于它的根结点的值;
- 左右子树也分别为二叉查找树
- 没有键值相等的节点
按照二叉查找树存储的数据,对元素的搜索效率是非常高的,比如上图中如果要查找值为48的节点,只需要遍历4个节点就能完成。理论上,一颗平衡的二叉查找树的任意节点平均查找效率为树的高度h,即O(lgn)。但是如果二叉查找树的失去平衡(元素全在一侧),搜索效率就退化为O(n),因此二叉查找树的平衡是搜索效率的关键所在。而红黑树就是靠红黑规则来维持二叉查找树的平衡性。
红黑树
红黑树的红黑规则:
- 节点是红色或黑色
- 根节点是黑色
- 每个叶子节点(NIL节点,空节点)是黑色的
- 每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任意节点到其每个叶子的所有路径都包含相同数目的黑色节点
第5条规则到底是什么情况,下面简单解释下,比如图中红8到1左边的叶子节点的路径包含两个黑色节点,到6下面的叶子节点的路径也包含两个黑色节点。
但是在在添加或删除节点后,红黑树就发生了变化,可能不再满足上面的5个特性,为了保持红黑树的以上特性,就有了三个动作:左旋、右旋、着色。
下面来看下什么是红黑树的左旋和右旋:
对x进行左旋,意味着将x变成一个左节点。
对y进行右旋,意味着将y变成一个右节点。
TreeMap签名
|
|
HashMap是无序的,TreeMap是有序的
接口NavigableMap
|
|
发现NavigableMap继承了SortedMap,说明这个Map是有序的。这个顺序一般是指由Comparable接口提供的keys自然序,或者也可以在窗口SortedMap时,指定一个Comparator来决定。
Comparator和Comparable的区别
- Comparable一般表示类的自然序,比如定义一个Student类,学号为默认排序
- Comparator一般表示类在某种场合下的特殊分类,需要定制化排序。比如现在想按照Student类的age来排序。
插入SortedMap中的key的类都必须继承Comparable类(或指定一个comparator),这样才能确定如何比较(通过k1.compareTo(k2)或comparator.compare(k1, k2))两个key,否则,在插入时,会报ClassCastException的异常。
此外,SortedMap中key的顺序性应该与equals方法保持一致。也就是说k1.compareTo(k2)或comparator.compare(k1, k2)为true时,k1.equals(k2)也应该为true。
介绍完了SortedMap,再来回到我们的NavigableMap上面来。
NavigableMap是JDK1.6新增的,在SortedMap的基础上,增加了一些“导航方法”(navigation methods)来返回与搜索目标最近的元素。例如下面这些方法:
- lowerEntry,返回所有比给定Map.Entry小的元素
- floorEntry,返回所有比给定Map.Entry小或相等的元素
- ceilingEntry,返回所有比给定Map.Entry大或相等的元素
- higherEntry,返回所有比给定Map.Entry大的元素
源码解析
红黑树的算法还没有理解深刻,暂时挖个坑
因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、get、remove的时间复杂度都为log(n)
总结
- TreeMap的key是有序的,增删改查操作的时间复杂度为O(log(n)),为了保证红黑树平衡,在必要时会进行旋转
- HashMap的key是无序的,增删改查操作的时间复杂度为O(1),为了做到动态扩容,在必要时会进行resize。
参考文章: