本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营连接:http://item.jd.com/12299018.htmlhtml
40节介绍了HashMap,41节介绍了HashSet,它们的共同实现机制是哈希表,一个共同的限制是没有顺序,咱们提到,它们都有一个能保持顺序的对应类TreeMap和TreeSet,这两个类的共同实现基础是排序二叉树,为了更好的理解TreeMap/TreeSet,本节咱们先来介绍排序二叉树的一些基本概念和算法。算法
基本概念数据库
先来讲树的概念,现实中,树是从下往上长的,树会分叉,在计算机程序中,通常而言,与现实相反,树是从上往下长的,也会分叉,有个根节点,每一个节点能够有一个或多个孩子节点,没有孩子节点的节点通常称为叶子节点。编程
二叉树是一个树,但,每一个节点最多有两个孩子节点,一左一右,左边的称为左孩子,右边的称为右孩子,咱们看两个例子,以下所示:数组
这两棵树都是二叉树,左边的根节点为5,除了叶子节点外,每一个节点都有两个孩子节点,右边的根节点为7,有的节点有两个孩子节点,有的只有一个。微信
树有一个高度或深度的概念,是从根到叶子节点通过的节点个数的最大值,左边树的高度为3,右边的为5。数据结构
排序二叉树也是二叉树,但,它没有重复元素,并且是有序的二叉树,什么顺序呢?对每一个节点而言:性能
上面的两颗二叉树都是排序二叉树。好比说左边的树,根节点为5,左边的都小于5,右边的都大于5。再看右边的树,根节点为7,左边的都小于7,右边的都大于7,在以3为根的左子树中,其右子树的值都大于3。spa
排序二叉树有什么优势?如何在树中进行基本操做如查找、遍历、插入和删除呢?咱们来看一下基本的算法。htm
基本算法
查找
排序二叉树有一个很好的优势,在其中查找一个元素是很方便、也很高效的,基本步骤为:
这个步骤与在数组中进行二分查找或者说折半查找的思路是相似的,若是二叉树是比较平衡的,相似上图中左边的二叉树,则每次比较都能将比较范围缩小一半,效率很高。
此外,在排序二叉树中,能够方便的查找最小最大值,最小值即为最左边的节点,从根节点一路查找左孩子便可,最大值即为最右边的节点,从根节点一路查找右孩子便可。
遍历
排序二叉树也能够方便的按序遍历,用递归的方式,用以下算法便可按序遍历:
好比,遍历访问下面的二叉树:
从根节点开始,但先访问根节点的左子树,一直到最左边的节点,因此第一个访问的是1,1没有右子树,返回上一层,访问3,而后访问3的右子树,4没有左子树,因此访问4,而后是4的右子树6,依次类推,访问顺序就是有序的:1 3 4 6 7 8 9。
不用递归的方式,也能够实现按序遍历,第一个节点为最左边的节点,从第一个节点开始,依次找后继节点。给定一个节点,找其后继节点的算法为:
文字描述比较抽象,咱们来看个图,以上图为例,每一个节点的后继以下图绿色箭头所示:
对每一个节点,对照算法,咱们再详细解释下:
怎么构建排序二叉树呢?能够在插入、删除元素的过程当中造成和保持。
插入
在排序二叉树中,插入元素首先要找插入位置,即新节点的父节点,怎么找呢?与查找元素相似,从根节点开始往下找,其步骤为:
找到父节点后,便可插入,若是插入元素小于父节点,则做为左孩子插入,不然做为右孩子插入。
咱们来看个例子,依次插入7, 3, 4, 1, 9, 6, 8的过程,这个过程以下图所示:
删除
从排序二叉树中删除一个节点要复杂一些,有三种状况:
咱们分别来看下。
若是节点为叶子节点,则很简单,能够直接删掉,修改父节点的对应孩子为空便可。
若是节点只有一个孩子节点,则替换待删节点为孩子节点,或者说,在孩子节点和父节点之间直接创建连接。好比说,在下图中,左边二叉树中删除节点4,就是让4的父节点3与4的孩子节点6直接创建连接。
若是节点有两个孩子,则首先找该节点的后继(根据以前介绍的后继算法,后继为右子树中最小的节点,这个后继必定没有左孩子),找到后继后,替换待删节点为后继的内容,而后再删除后继节点。后继节点没有左孩子,这就将两个孩子的状况转换为了叶子节点或只有一个孩子的状况。
好比说,在下图中,从左边二叉树中删除节点3,3有两个孩子,后继为4,首先替换3的内容为4,而后再删除节点4。
平衡的排序二叉树
从前面的描述中能够看出,排序二叉树的形状与插入和删除的顺序密切相关,极端状况下,排序二叉树可能退化为一个链表,好比说,若是插入顺序为:1 3 4 6 7 8 9,则排序二叉树形状为:
退化为链表后,排序二叉树的优势就都没有了,即便没有退化为链表,若是排序二叉树高度不平衡,效率也会变的很低。
平衡具体定义是什么呢?有一种高度平衡的定义,即任何节点的左右子树的高度差最多为一。知足这个平衡定义的排序二叉树又被称为AVL树,这个名字源于它的发明者G.M. Adelson-Velsky 和 E.M. Landis,在他们的算法中,在插入和删除节点时,经过一次或屡次旋转操做来从新平衡树。
在TreeMap的实现中,用的并非AVL树,而是红黑树,与AVL树相似,红黑树也是一种平衡的排序二叉树,也是在插入和删除节点时经过旋转操做来平衡的,但它并非高度平衡的,而是大体平衡的,所谓大体是指,它确保,对于任意一条从根到叶子节点的路径,没有任何一条路径的长度会比其余路径长过两倍。红黑树减弱了对平衡的要求,但下降了保持平衡须要的开销,在实际应用中,统计性能高于AVL树。
为何叫红黑树呢?由于它对每一个节点进行着色,颜色或黑或红,并对节点的着色有一些约束,知足这个约束便可以确保树是大体平衡的。
对AVL树和红黑树,它们保持平衡的细节都是比较复杂的,咱们就不介绍了,咱们须要知道的就是,它们都是排序二叉树,都经过在插入和删除时执行开销不大的旋转操做保持了树的高度平衡或大体平衡,从而保证了树的查找效率。
小结
本节介绍了排序二叉树的基本概念和算法。
排序二叉树保持了元素的顺序,并且是一种综合效率很高的数据结构,基本的保存、删除、查找的效率都为O(h),h为树的高度,在树平衡的状况下,h为log2(N),N为节点数,好比,若是N为1024,则log2(N)为10。
基本的排序二叉树不能保证树的平衡,可能退化为一个链表,有不少保持树平衡的算法,AVL树是第一个,能保证树的高度平衡,但红黑树是实际中使用更为普遍的,虽然只能保证大体平衡,但下降了维持树平衡须要的开销,总体统计效果更好。
与哈希表同样,树也是计算机程序中一种重要的数据结构和思惟方式。为了可以快速操做数据,哈希和树是两种基本的思惟方式,不须要顺序,优先考虑哈希,须要顺序,考虑树。除了容器类TreeMap/TreeSet,数据库中的索引结构也是基于树的(不过基于B树,而不是二叉树),而索引是可以在大量数据中快速访问数据的关键。
理解了排序二叉树的基本概念和算法,理解TreeMap和TreeSet就比较容易了,让咱们在接下来的两节中探讨这两个类。
---------------
未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),从入门到高级,深刻浅出,老马和你一块儿探索Java编程及计算机技术的本质。用心原创,保留全部版权。