继上篇文章介绍完了HashMap,这篇文章开始介绍Map系列另一个比较重要的类TreeMap。 大家也许能感觉到,网络上介绍HashMap的文章比较多,但是介绍TreeMap反而不那么多,这里面是有原因:一方面HashMap的使用场景比较多;二是相对于HashMap来说,TreeMap所用到的数据结构更为复杂。 废话不多说,进入正题。
签名(signature)
1
2
3
|
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
|
可以看到,相比HashMap来说,TreeMap多继承了一个接口NavigableMap,也就是这个接口,决定了TreeMap与HashMap的不同:
HashMap的key是无序的,TreeMap的key是有序的
接口NavigableMap
首先看下NavigableMap的签名
1
|
public interface NavigableMap<K,V> extends SortedMap<K,V>
|
发现NavigableMap继承了SortedMap,再看SortedMap的签名
SortedMap
1
|
public interface SortedMap<K,V> extends Map<K,V>
|
SortedMap
就像其名字那样,说明这个Map是有序的。这个顺序一般是指由Comparable接口提供的keys的自然序(natural ordering),或者也可以在创建SortedMap实例时,指定一个Comparator来决定。 当我们在用集合视角(collection views,与HashMap一样,也是由entrySet、keySet与values方法提供)来迭代(iterate)一个SortedMap实例时会体现出key的顺序。 这里引申下关于Comparable与Comparator的区别(参考这里):
- 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大的元素
设计理念(design concept)
红黑树(Red–black tree)
TreeMap是用红黑树作为基础实现的,红黑树是一种二叉搜索树,让我们在一起回忆下二叉搜索树的一些性质
二叉搜索树
先看看二叉搜索树(binary search tree,BST)长什么样呢?
二叉搜索树相信大家对这个图都不陌生,关键点是:
左子树的值小于根节点,右子树的值大于根节点。
二叉搜索树的优势在于每进行一次判断就是能将问题的规模减少一半,所以如果二叉搜索树是平衡的话,查找元素的时间复杂度为log(n)
,也就是树的高度。 我这里想到一个比较严肃的问题,如果说二叉搜索树将问题规模减少了一半,那么三叉搜索树不就将问题规模减少了三分之二,这不是更好嘛,以此类推,我们还可以有四叉搜索树,五叉搜索树……对于更一般的情况:
n个元素,K叉树搜索树的K为多少时效率是最好的?K=2时吗?
K 叉搜索树
如果大家按照我上面分析,很可能也陷入一个误区,就是
三叉搜索树在将问题规模减少三分之二时,所需比较操作的次数是两次(二叉搜索树再将问题规模减少一半时,只需要一次比较操作)
我们不能把这两次给忽略了,对于更一般的情况:
n个元素,K叉树搜索树需要的平均比较次数为
k*log(n/k)
。
对于极端情况k=n时,K叉树就转化为了线性表了,复杂度也就是O(n)
了,如果用数学角度来解这个问题,相当于:
n为固定值时,k取何值时,
k*log(n/k)
的取值最小?
k*log(n/k)
根据对数的运算规则可以转化为ln(n)*k/ln(k)
,ln(n)
为常数,所以相当于取k/ln(k)
的极小值。这个问题对于大一刚学高数的人来说再简单不过了,我们这里直接看结果
当k=e时,
k/ln(k)
取最小值。
自然数e的取值大约为2.718左右,可以看到二叉树基本上就是这样最优解了。在Nodejs的REPL中进行下面的操作
1
2
3
4
5
6
7
8
9
|
function foo(k) { return k/Math.log(k);}
> foo( 2 )
2.8853900817779268 > foo( 3 )
2.730717679880512 > foo( 4 )
2.8853900817779268 > foo( 5 )
3.1066746727980594 |
貌似k=3时比k=2时得到的结果还要小,那也就是说三叉搜索树应该比二叉搜索树更好些呀,但是为什么二叉树更流行呢?后来在万能的stackoverflow上找到了答案,主旨如下:
现在的CPU可以针对二重逻辑(binary logic)的代码做优化,三重逻辑会被分解为多个二重逻辑。
这样也就大概能理解为什么二叉树这么流行了,就是因为进行一次比较操作,我们最多可以将问题规模减少一半。 好了这里扯的有点远了,我们再回到红黑树上来。
红黑树性质
先看看红黑树的样子:
红黑树示例上图是从wiki截来的,需要说明的一点是:
叶子节点为上图中的NIL节点,国内一些教材中没有这个NIL节点,我们在画图时有时也会省略这些NIL节点,但是我们需要明确,当我们说叶子节点时,指的就是这些NIL节点。
红黑树通过下面5条规则,保证了树是平衡的:
- 树的节点只有红与黑两种颜色
- 根节点为黑色的
- 叶子节点为黑色的
- 红色节点的字节点必定是黑色的
- 从任意一节点出发,到其后继的叶子节点的路径中,黑色节点的数目相同
满足了上面5个条件后,就能够保证:根节点到叶子节点的最长路径不会大于根节点到叶子最短路径的2倍
。 其实这个很好理解,主要是用了性质4与5,这里简单说下:
假设根节点到叶子节点最短的路径中,黑色节点数目为B,那么根据性质5,根节点到叶子节点的最长路径中,黑色节点数目也是B,最长的情况就是每两个黑色节点中间有个红色节点(也就是红黑相间的情况),所以红色节点最多为B-1个。这样就能证明上面的结论了。
红黑树操作
红黑树旋转示例(没有画出NIL节点)关于红黑树的插入、删除、左旋、右旋这些操作,我觉得最好可以做到可视化,文字表达比较繁琐,我这里就不在献丑了,网上能找到的也比较多,像v_July_v的《教你透彻了解红黑树》。我这里推荐个swf教学视频(视频为英文,大家不要害怕,重点是看图??),7分钟左右,大家可以参考。 这里还有个交互式红黑树的可视化网页,大家可以上去自己操作操作,插入几个节点,删除几个节点玩玩,看看左旋右旋是怎么玩的。
源码剖析
由于红黑树的操作我这里不说了,所以这里基本上也就没什么源码可以讲了,因为这里面重要的算法都是From CLR
,这里的CLR是指Cormen, Leiserson, Rivest,他们是算法导论的作者,也就是说TreeMap里面算法都是参照算法导论的伪代码。 因为红黑树是平衡的二叉搜索树,所以其put(包含update操作)、get、remove的时间复杂度都为log(n)
。
总结
到目前为止,TreeMap与HashMap的的实现算是都介绍完了,可以看到它们实现的不同,决定了它们应用场景的不同:
- TreeMap的key是有序的,增删改查操作的时间复杂度为
O(log(n))
,为了保证红黑树平衡,在必要时会进行旋转 - HashMap的key是无序的,增删改查操作的时间复杂度为
O(1)
,为了做到动态扩容,在必要时会
http://www.importnew.com/16679.html
相关推荐
Java源码解析TreeMap简介 TreeMap是Java中的一种常用的排序树,实现了NavigableMap接口。下面是对TreeMap的详细介绍: 1. TreeMap的实现机制 TreeMap是基于Red-Black树的数据结构,红黑树是一种自平衡的二叉查找...
在"java集合源码-analysis:java集合源码解析"这个项目中,我们主要探讨的是对Java集合框架的源码进行深入理解。下面将详细阐述Java集合框架的基本概念、重要类和接口,以及其源码分析的重要性。 Java集合框架主要...
《疯狂Java讲义》是李刚先生撰写的一本深度解析Java编程语言的教材,由新东方教育科技集团出版。这本书以其深入浅出的讲解方式,深受广大Java学习者喜爱。通过对压缩包文件“疯狂java讲义”的分析,我们可以提炼出一...
Java项目学习库与SpringBoot源码解析是一套深入学习Java编程和SpringBoot框架的资源集合。这个库旨在帮助开发者从初级到高级逐步掌握Java技术栈,并深入理解SpringBoot的核心机制。下面将对Java和SpringBoot的相关...
源码解析能帮助你理解日期时间计算的复杂性。 6. **反射**: java.lang.reflect包中的类(如Class、Constructor、Method)用于运行时获取类的信息和动态调用方法。源码分析有助于理解Java的元数据机制。 7. **...
java所有集合类底层源码解析汇总,包括ArrayList、HashMap、HashSet、LinkedList、TreeMap、HashSet、ConcurrentHashMap等集合框架的底层实现源码大白话解读。
1. **基础类库解析**:JDK 1.8源码中包含了大量Java基础类库,如`java.lang`、`java.util`、`java.io`等。这些类库提供了大量的工具类和接口,如`String`、`ArrayList`、`HashMap`等,通过阅读源码,我们可以了解到...
源码中可以学习到它如何解析输入并转换为不同数据类型。 7. `Comparator` 接口:这个接口用于自定义比较逻辑,常用于`Collections.sort()`方法中。理解其工作原理可以帮助我们实现复杂的数据排序需求。 8. `Date` ...
《疯狂Java讲义》是Java编程领域的一本经典教材,其源码分为多个部分,这里主要探讨的是第六部分,涵盖了第11至13章的内容。这三章涉及了Java高级特性和面向对象编程的深入理解,对于进阶Java开发者来说至关重要。...
《疯狂JAVA讲义》是Java编程领域的一本经典教材,其源码的全面解析将为我们深入理解Java语言提供宝贵的资源。这份"疯狂JAVA讲义源码(全)"包含了第二版的所有实例代码,旨在帮助读者通过实践来巩固理论知识,提升...
《深入解析Core Java——达内培训源码解析》 在Java编程领域,Core Java是学习的基础,涵盖了Java语言的核心概念和技术。"达内科技"作为知名的IT培训机构,提供了丰富的Java培训资源,包括源码,旨在帮助学员深入...
这些类都是Java集合框架的重要组成部分,下面将逐一解析它们的核心概念和工作原理。 1. **ArrayList**:`ArrayList`是基于动态数组实现的列表,它允许在任何位置插入和删除元素。内部通过一个`Object[] elementData...
JAVA中的HashMap、TreeMap等数据结构可以用于构建倒排索引,其中关键词作为键,指向包含该词的文档列表。Trie树则可以用于快速查找和建议输入。 3. **查询解析**:用户输入的查询需要被解析为搜索引擎能理解的形式...
接下来,我们将深入解析这些源码中蕴含的知识点。 1. **Java基础** - **变量与数据类型**:Java中的基本数据类型如int、char、boolean等,以及引用数据类型如类、接口、数组等的使用。 - **运算符**:包括算术...
【Java通讯录源码解析】 本项目是一个基于Java语言实现的通讯录系统,它提供了基本的联系人管理功能,如添加、删除、修改和查询联系人信息。通过这个源码,我们可以深入理解Java编程在实际应用中的核心概念和技术。...
12. **Java虚拟机(JVM)**:JVM是Java程序运行的平台,负责类的加载、验证、解析、执行以及垃圾回收。 13. **垃圾回收(Garbage Collection)**:Java自动管理内存,垃圾回收器会定期清理不再使用的对象,防止内存...