从数据结构到Java常用集合

从数据结构到Java常用集合
jwang从数据结构到Java常用集合
- 常用的数据结构可根据数据访问的特点分为线性结构和非线性结构。线性结构包括常见的链表、栈、队列等,非线性结构包括树、图等。数据结构种类繁多,本篇内容将通过图解的方式对常用的数据结构以及在java中的实现进行介绍
数据结构组织图

Java集合组织图

数组
数组可以说是最基本最常见的数据结构。数组一般用来存储相同类型的数据,可通过数组名和下标进行数据的访问和更新。数组中元素的存储是按照先后顺序进行的,同时,在内存中也是按照这个顺序进行连续存放。数组相邻元素之间的内存地址的间隔一般就是数组数据类型的大小。
ArrayList概述
ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。
该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。
ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。
另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。
ArrayList和Collection的关系:
ArrayList的数据结构
- 底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。
ArrayList源码分析
继承结构和层次关系
为什么要先继承AbstractList,而让AbstractList先实现List
?而不是让ArrayList直接实现List ? - 这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList实现接口中一些通用的方法,而具体的类,如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来,先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用。
类中的属性
public class ArrayList<E> extends AbstractList<E> |
构造方法
无参构造方法
/* |
有参构造函数一
public ArrayList(int initialCapacity) { |
- arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。
核心方法
add()方法
boolean add(E);//默认直接在末尾添加元素
/** |
- ensureCapacityInternal(xxx); 确定内部容量的方法
//用于确定数组容量 |
- ensureExplicitCapacity(xxx);
//判断是否需要扩容 |
- grow(xxx); arrayList核心的方法,能扩展数组大小的真正秘密。
/** |
- hugeCapacity();
//这个就是上面用到的方法,很简单,就是用来赋最大值。 |
void add(int,E);在特定位置添加元素,也就是插入元素
/** |
- rangeCheckForAdd(index)
private void rangeCheckForAdd(int index) { |
remove()方法
- 其中fastRemove(int)方法是private的,是提供给remove(Object)这个方法用的。
remove(int):删除指定位置上的元素
public E remove(int index) { |
remove(Object):这个方法可以看出来,arrayList是可以存放null值得。
//通过元素来删除该元素,就依次遍历,如果有这个元素,就将该元素的索引传给fastRemobe(index),使用这个方法来删除该元素, |
clear():将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear
public void clear() { |
set()方法
public E set(int index, E element) { |
- 设定指定下标索引的元素值
indexOf()方法
// 从首开始查找数组里面是否存在指定元素 |
- 从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。
get()方法
public E get(int index) { |
- get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下:
E elementData(int index) { |
- 返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。
总结
- arrayList可以存放null。
- arrayList本质上就是一个elementData数组。
- arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是grow()方法。
- arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全删除集合中的元素。
- arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
- arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
链表
链表类型
单向链表:
element:用来存放元素
next:用来指向下一个节点元素
通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
单向循环链表
element、next 跟前面一样
在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环
双向链表
element:存放元素
pre:用来指向前一个元素
next:指向后一个元素
双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
双向循环链表
element、pre、next 跟前面的一样
第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。
链表和数组对比
- 单链表和数组的区别进行了对比和总结。
| 数组 | 链表 | |
|---|---|---|
| 内存地址 | 连续的内存空间 | 非连续的内存空间 |
| 数据长度 | 长度固定,一般不可动态扩容 | 长度可动态变化 |
| 增删效率 | 低,需要移动被修改元素之后的所有元素 | 高,只需要修改指针指向 |
| 查询效率 | 高,可用过数组名和下标直接访问 | 低,只能通过遍历节点依次查询 |
| 数据访问方式 | 随机访问 | 顺序访问 |
LinkedList
- 是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的。
- 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
- 实现 List 接口,能对它进行队列操作。
- 实现 Deque 接口,即能将LinkedList当作双端队列使用。
- 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
- 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
- 是非同步,线程不安全的。
数据结构
- 如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作。
特性
- 异步,也就是非线程安全
- 双向链表。由于实现了list和Deque接口,能够当作队列来使用。
- 链表:查询效率不高,但是插入和删除这种操作性能好。
- 是顺序存取结构
源码分析
类的属性
// 实际元素个数,链表长度 |
- LinkedList的属性非常简单,一个头结点、一个尾结点、一个表示链表中实际元素个数的变量。
构造方法
- 空参构造函数
public LinkedList() { } |
- 有参构造函数
//将集合c中的各个元素构建成LinkedList链表。 |
内部类(Node)
private static class Node<E> { |
- 内部类Node就是实际的结点,用于存放实际元素的地方。
add()方法
- add(E)
public boolean add(E e) { |
- LinkLast()
/** |
- 举例:
List<Integer> lists = new LinkedList<Integer>(); |
- 一开始,first和last都为null,此时链表什么都没有,当第一次调用该方法后,first和last均指向了第一个新加的节点E1:
- 第二次调用该方法,加入新节点E2。首先,将last引用赋值给l,接着new了一个新节点E2,并且E2的prve指向l,接着将新节点E2赋值为last
- 接着判断l==null? 所以走的else语句,将l的next引用指向新节点E2
- 最后,size+1,modCount+1,退出该方法,局部变量l销毁
remove(Object o)
//首先通过看上面的注释,我们可以知道,如果我们要移除的值在链表中存在多个一样的值 |
- unlink(xxxx)
/** |
get(index)
- get(index)查询元素的方法
//这里没有什么,重点还是在node(index)中 |
- node(index)
/** |
indexOf(Object o)
//这个很简单,就是通过实体元素来查找到该元素在链表中的位置。跟remove中的代码类似,只是返回类型不一样。 |
总结
- linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构。
- 能存储null值
- 跟arrayList相比较,就真正的知道了,LinkedList在删除和增加等操作上性能好,而ArrayList在查询的性能上好
- 从源码中看,它不存在容量不足的情况
- linkedList不光能够向前迭代,还能像后迭代,并且在迭代的过程中,可以修改值、添加值、还能移除值。
- linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口。
栈
概述
栈是一种比较简单的数据结构,常用一句话描述其特性,先进后出。栈本身是一个线性表,但是在这个表中只有一个口子允许数据的进出
栈的常用操作包括入栈push和出栈pop,对应于数据的压入和压出。还有访问栈顶数据、判断栈是否为空和判断栈的大小等。由于栈后进先出的特性,常可以作为数据操作的临时容器,对数据的顺序进行调控,与其它数据结构相结合可获得许多灵活的处理。
Stack
- Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的,而非链表。当然,我们也可以将LinkedList当作栈来使用。Stack的继承关系
package java.util; |
队列
- 队列是栈的兄弟结构,与栈的后进先出相对应,队列是一种先进先出的数据结构。顾名思义,队列的数据存储是如同排队一般,先存入的数据先被压出。常与栈一同配合,可发挥最大的实力。
树
树的基本概念
树是数据结构中非常重要的一个存在。在Java中对树进行了大量的使用,如TreeMap、HashMap等。并且如MySQL、MongoDB也都使用到了树。
树是由N个节点组成的,每个节点中都会进行数据的存储。当N=0时,被称为空树。在任何一颗树中,有且只有一个根节点,在根节点下可以继续进行扩展,形成互不相交的集合。其中每个集合本身又可以理解为是一颗树,他们则被称为子树。
| 节点 | 树中的元素 |
|---|---|
| 节点的度 | 节点拥有子树的个数,二叉树的度不能大于2 |
| 高度 | 叶子节点的高度为1,叶子节点的父节点高度为2,依次类推。 |
| 根节点 | 树的顶部节点。 |
| 子节点 | 父节点的下层节点。 |
| 叶子节点 | 没有子节点的节点,也被称为终端节点、度为0的节点。 |
- 树的种类也非常的多:二叉树、平衡二叉树、红黑树、B树、B+树、哈夫曼树、B*树等等。
二叉树
基本概念
- 二叉树的特点是树的每个节点最多只能有两个子节点。其中一棵树叫做根的左子树,另一颗叫根的右子树。
- 如果节点数量超过两个,则不能叫做二叉树,而叫做多路树。
特点
- 每个节点最多有两颗子树,所以二叉树中不存在度(该结点孩子的个数)大于2的节点。
- 左子树和右子树是有顺序的。
- 即使树中某节点只有一颗子树, 也要区分它是左子树还是右子树。
特殊的二叉树
满二叉树
在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。其特点如下:
- 假设深度为k,且含有2^k-1个结点的树。
- 叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
- 非叶子结点的度一定是2。
- 在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
完全二叉树
完全二叉树是一颗特殊的二叉树。假设一个二叉树的高度为h,如果说它是一颗完全二叉树的话,需要满足以下规则:
叶子结点只能出现在最下层和次下层。
最下层的叶子结点集中在树的左部。
倒数第二层若存在叶子结点,一定在右部连续位置。
如果结点度为1,则该结点只有左孩子,即没有右子树。
斜树
- 所有的节点都之后左子树的二叉树叫做左斜树,同理还存在右斜树。一旦产生这种情况,相当于树结构退化为了链表,查找一个节点的时间复杂度为O(n),查询效率严重降低。
存储结构
- 二叉树的顺序存储结构就是使用一维数组存储二叉树中的结点,并且结点的存储位置,就是数组的下标索引。
- 当二叉树为完全二叉树时,结点数刚好填满数组。
- 当不为完全二叉树时,采用顺序存储又是什么样子呢?
- 其中,∧表示数组中此位置没有存储结点。此时可以发现,顺序存储结构中已经出现了空间浪费的情况。
在极端的右斜树极端情况对应的顺序存储结构。
- 可以看出,对于这种右斜树极端情况,采用顺序存储的方式是十分浪费空间的。因此,顺序存储一般适用于完全二叉树。
二叉树遍历
定义
二叉树的遍历是指从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。
二叉树的访问次序可以分为四种:
- 前序遍历
- 中序遍历
- 后序遍历
- 层序遍历
前序遍历
- 从根结点出发,当第一次到达结点时就输出结点数据,按照先向左再向右的方向访问。简单理解就是:父节点->左子树->右子树
中序遍历
- 从根结点出发,当第二次到达结点时则输出结点数据,按照先向左再向右的方向访问。简单理解就是:左子树->父节点->右子树
流程如下:
从根节点触发,则第一次到达结点A,不输出A,继续向左访问,第一次到达结点B,不输出B,继续到达D、H,都不进行输出。
到达H,H左子树为空,则返回到H,此时是第二次访问H,则输出H。
后序遍历
从根结点出发,当第三次到达结点时则输出输出,按照先向左后向右的方式访问。简单理解就是:左子树 -> 右子树 ->父节点
层次遍历
- 按照树的层次自上而下的遍历二叉树。最终结果为:ABCDEFGHIJ
二叉搜索树
定义
二叉搜索树,也被称为二叉查找树、二叉排序树。对于基础二叉树来说,数据查找、数据变动的效率都是非常低效的。因此才产生了二叉搜索树。
其定义规则如下:
- 若左子树不为空,则左子树上的各个结点的值,均小于它的父节点的值。
- 若右子树不为空,则右子树上的各个结点的值,均大于它的父节点的值。
- 没有值相等的结点。
- 左右子树也分别为二叉搜索树。
- 不一定是一颗完全二叉树,因此,二叉搜索树不能用数组来存储。
查找
流程:
- (1)如果树是空的,则查找结束,无匹配。
- (2)如果被查找的值和节点的值相等,查找成功。
- (3)如果被查找的值小于节点的值,递归查找左子树。
- (4)如果被查找的值大于节点的值,递归查找右子树。
插入
- 图解:
删除
删除节点为叶子节点
- 该方式最简单,只需找到对应节点,直接删除即可。
删除的节点只有左子树
- 需要将节点的左子树替代被删除节点的位置。
删除的节点只有右子树
- 需要将右子树替代被删除节点的位置。与左子树思想一致。
删除的节点拥有左右子树
此情况操作起来最为复杂。操作流程如下:
- (1)遍历待删除节点的左子树,找到其左子树中的最大节点。
- (2)将最大节点代替被删除节点。
- (3)删除左子树中的最大节点。
同理,也可以选取右子树中的最小值取代它并删除原结点。
平衡二叉树
定义
- 二叉搜索树一定程度上可以提高搜索效率,但是当有序序列为{1,2,3,4,5,6}时,此时构造的二叉搜索树为右斜树。可以发现二叉树已经退化为了单链表,搜索效率降低为O(n)。
- 在此二叉搜索树中查找元素6需要查找6次。二叉搜索树的查找效率取决于树的高度,因此树的高度越低,其查询效率就会越高。同样的序列,更改存储方式,查找元素6时只需要比较三次,查询效率提升一倍。
- 可以看出当节点数目一定,保持树的左右两端保持平衡,树的查找效率最高。这种左右子树的高度相差不超过1的树为平衡二叉树。
平衡因子
定义:某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor),平衡二叉树中不存在平衡因子大于1的节点。在一棵平衡二叉树中,节点的平衡因子只能取-1、1或者0。
左旋与右旋
左旋
- 如图所示的平衡二叉树
- 如在此平衡二叉树插入节点62,树结构变为:
- 左旋流程:
右旋
右旋操作与左旋类似,操作流程为:
- (1)原根节点的左孩子代表此节点
- (2)原根节点的左孩子的右子树变为节点的左子树。
- (3)原根节点作为左孩子节点的右子树。
过程如下:
插入
- 假设一颗 AVL 树的某个节点为A,有时会使 A 的左右子树高度差大于 1,从而破坏了原有 AVL 树的平衡性。平衡二叉树插入节点的情况分为以下2种:
A的左孩子的左子树插入节点
- 假设现在有这样的一颗平衡二叉树:
- 节点A的左孩子为B,B的左子树为D,无论在节点D的左子树或者右子树中插入F均会导致节点A失衡。因此需要对节点A进行旋转操作。A的平衡因子为2,值为正,因此对A进行右旋操作。
过程如下:
A的右孩子的右子树插入节点
- 插入节点F后,节点A的平衡因子为-2,对节点A进行左旋操作。
删除
- 平衡二叉树的节点删除与二叉搜索树删除方法一致,但是需要在节点删除后判断树是否仍然保持平衡,若出现失衡情况,需要进行调整。
红黑树
概述
红黑树是树的数据结构中最为重要的一种。Java的容器TreeSet、TreeMap均使用红黑树实现。JDK1.8中HashMap中也加入了红黑树。每个节点都带有颜色属性,颜色为红色或黑色。除了二叉查找树一般要求以外,对于任何有效的红黑树还增加了如下的额外要求:
1)节点要么是黑色要么是红色。
2)根结点一定是黑色的。
3)每个叶子节点都带有两个空(NIL)的黑色节点。
4)每个红色节点的两个子节点一定是黑色,因此不会存在两个连续的红色节点,红色节点的父节点一定是黑色节点。
5)从任一节点到它所能到达的叶子节点的所有路径都包含相同数目的黑色节点。从而达到黑色平衡。(平衡二叉树是一个完美平衡的树,红黑树是非完美平衡树,但是一个完美的黑色平衡二叉查找树)。
节点名称
**父节点——P(Parent)**
**祖父节点——G(GrandParent)**
**叔叔节点——U(Uncle)**
**当前节点——C(Current)**
**兄弟节点——B(Brother)**
**左孩子——L(Left)**
**右孩子——R(Right)**
B树
大部分数据库的索引都采用树的结构存储,这是因为树的查询效率相对较高,且保持有序。
- 对于二叉搜索树的时间复杂度是**O(logN)**,在算法以及逻辑上来分析,二叉搜索树的查找速度以及数据比较次数都是较小的。但是我们不得不考虑一个新的问题。数据量是远大于内存大小的,那我们在查找数据时并不能将全部数据同时加载至内存。既然不能全部加载至内存中就只能逐步的去加载磁盘中某个页,简而言之就是逐一的去加载磁盘,加数据分块的加载至内存进行查找与比较。如图所示,在树中查找10,树的每个节点代表一个磁盘页,相当于每次访问一个新节点代表一次磁盘IO。
通过查找过程可以看出,磁盘IO次数与树的高度相关,在最坏情况下,磁盘IO次数等于树的高度。由于磁盘IO过程是相对耗时效率较低的,因此,在设计数据存储结构时需要降低树的高度,即将一棵“瘦高”的树变得“矮胖”。
当数据数目相同,在保持有序前提下,降低树高度,只需将节点中存储的key值增加,即二叉搜索树中每个节点只有一个数据元素,而在B树中每个节点可以有多个数据元素。
定义
B树也成B-树。它是一颗多路平衡查找树(所有的叶子节点拥有相同的高度)。当描述一颗B树时需要指定它的阶数,阶数表示一个节点最多有多少个孩子节点,一般用字母m表示。当m取2时,就是一颗二叉查找树。
要定义一颗m阶的B树,需要遵循以下五条原则:
1)根节点最少可以只有一个元素,且至少要有两个子节点。
2)每个节点最多有m-1个元素。
3)非根节点至少有(m/2)-1个元素。m/2要进行向上取整,如m/2=1.5=2。
4)每个结点中的元素都按照从小到大的顺序排列,每个元素的左子树中的所有元素都小于它,而右子树中的所有元素都大于它。
5)所有叶子节点都位于同一层,相当于根节点到每个叶子节点的长度相同。
操作
- B树的查找其实是对二叉搜索树查找的扩展, 与二叉搜索树不同的地方是,B-树中每个节点有不止一棵子树。在B-树中查找某个结点时,需要先判断要查找的结点在哪棵子树上,然后在结点中逐个查找目标结点。B树的查找过程相对简单,与二叉搜索树类似,因此不再赘述。
插入
B树的插入操作是指在树种插入一条新记录,即(key, value)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的value替换旧的value。若B树不存在这个key,则一定是在叶子结点中进行插入操作。
插入流程如下:
1)根据要插入的key的值,对B树执行查找操作,查找到待插入数据的当前节点位置。
2)判断当前节点key的个数是否小于等于m-1,若满足,则直接插入数据。
3)若不满足,以节点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父节点中,这个key的左子树指向分裂后的左半部分,这个key的右子树指向分裂后的右半部分,然后将当前节点指向父节点,继续执行第三步。
下面以5阶B树为例,介绍B树的插入操作,在5阶B树中,结点最多有4个key,最少有2个key。
1:插入38,此时为空树,直接插入,并作为根节点。继续插入22、76、40,符合情形(2),直接插入。
2:插入51,符合情形(3),执行分裂。
3:按照相同的步骤继续插入13、21
4:插入39,符合情形(3),导致节点分裂。选择中值22作为父节点,并将22节点上移,与40节点进行合并。
5:按照同样的插入规则,继续向树中插入key为30、27、33、36、35、34、24、29的数据。
6:继续插入key为26的数据,插入之后需要执行节点分裂。
7:将key为27的数据节点上移至父节点
8:此时父节点已经有4个key,插入key27的数据后需要执行节点分裂,树的高度加1。
9:再依次插入14,23,28,29,31,32。
删除
1:首先删除21,符合情形(2)直接删除。
2:继续删除27,符合情形(1),使用后继节点28替代27,并删除28。
3:删除28后,节点中只有29一个key,因此需要按照情形(3)调整。29左兄弟节点有三个节点,将28下移,兄弟节点中26上移。
4:继续删除32。
5:删除32后,需要按照情形(3)进行调整。
6:当前节点的兄弟节点只有2个key,则将父节点下移,将当前节点与一个兄弟节点合并,调整完毕。
7:继续删除39节点。
8:删除39节点后,需要按照情形(4)进行调整。
9:调整过后,当前节点变为只有key40的节点,需要按照情形(3)进行调整。执行节点合并,合并操作包含根节点,导致合并之后,树的高度减1。
B+树
- B+树是在B树基础进一步优化得到的一种数据结构。B+树相比于B树具有更高的查询效率。
定义
1)B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。
2)根结点本身即可以是内部结点,也可以是叶子结点。根结点的关键字个数最少可以只有1个。
3)B+树与B树最大的不同是内部结点不保存数据,只用于索引,所有数据(或者说记录)都保存在叶子结点中。
4) m阶B+树表示了内部结点最多有m-1个关键字,阶数m同时限制了叶子结点最多存储m-1个数据。
5)内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的数据也按照key的大小排列。
6)每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
如图所示的B+树中,灰色节点代表索引节点,索引节点中只有key,而不含数据data。橙色节点代表叶子节点,叶子节点中既有key值又有数据data。叶子节点采用单链表的方式链接。
特点
1)索引节点的key值均会出现在叶子节点中。
2)索引节点中的key值在叶子节点中或者为最大值或者为最小值。
3)叶子节点使用单链表的形式链接起来。
性能分析
- 查找性能
- 1)在相同数量的待查数据下,B+树查找过程中需要调用的磁盘IO操作要少于普通B-树。由于B+树所在的磁盘存储背景下,因此B+树的查找性能要好于B-树。
- 2)B+树的查找效率更加稳定,因为所有叶子结点都处于同一层中,而且查找所有关键字都必须走完从根结点到叶子结点的全部历程。因此同一颗B+树中,任何关键字的查找比较次数都是一样的。而B树的查找是不稳定的。
- 插入性能
- B+树的插入过程与B树类似,性能也基本一致。
- 删除性能
- 删除性能与B树也基本一致。
相关问题
hashmap为什么使用红黑树而不用别的树
红黑树是一个比较特殊的树,跟他能产生对比的是平衡二叉树。但是平衡二叉树的严格平衡牺牲了插入、删除操作的性能,来保证了查询的高效。 而红黑树则采用了折中策略,即不牺牲太大的插入删除性能,同时又保证稳定高效的查找效率。
为什么MongoDB索引使用B树,而MySQL使用B+树
MongoDB是一个非关系型数据库,对于遍历数据的需求很低,更多的是在做一些单一记录查询。而对于MySQL这种关系型数据库来说,进行遍历关联查询的需求就会很高。
结合B树与B+树的特点来说,B树的查询效率不固定,最好的情况是O(1),所以在做单一数据查询时,B树的平均性能会更好。但如果要对B树进行遍历的话,由于各个节点间没有指针相连,所以性能会很低。
而B+树最大的特点是数据只会出现在叶子节点,因此对于单条数据查询,其一定会进入到叶子节点上,因此平均性能没有B树好。但B+树的叶子节点有指针相连,在进行遍历时,其效率会明显优于B树。
堆
- 堆通常是一个可以被看做一棵树的数组对象。堆的具体实现一般不通过指针域,而是通过构建一个一维数组与二叉树的父子结点进行对应,因此堆总是一颗完全二叉树。
对于任意一个父节点的序号n来说(这里n从0算),它的子节点的序号一定是2n+1,2n+2,因此可以直接用数组来表示一个堆。
不仅如此,堆还有一个性质:堆中某个节点的值总是不大于或不小于其父节点的值。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
- 堆常用来实现优先队列,与排序有关,比如堆排序、topK问题等。由于堆的根节点是序列中最大或者最小值,因而可以在建堆以及重建堆的过程中,筛选出数据序列中的极值,从而达到排序或者挑选topK值的目的。
案例PriorityBlockingQueue中二叉堆的使用
put过程
public void put(E e) { |
take过程
public E take() throws InterruptedException { |
























































































