数据结构(data structure)是带有结构特性的数据元素的集合,它研究的是数据的逻辑结构和数据的物理结构以及它们之间的相互关系,并对这种结构定义相适应的运算,设计出相应的算法,并确保经过这些运算以后所得到的新结构仍保持原来的结构类型。简而言之,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带“结构”的数据元素的集合。“结构”就是指数据元素之间存在的关系,分为逻辑结构和存储结构。
数据结构也分为物理结构和逻辑结构:
数据元素及其关系在计算机内的存储方式,成为数据的存储结构(物理结构)。数组和链表都属于物理数据结构;而逻辑结构则是用于描述数据间的逻辑关系的,它可以由多种不同的物理结构来实现,比如队列和栈都属于逻辑结构
队列是一种操作受限的线性表,它只允许在一端插入加元素,在另一端删除元素。允许插入的一端称为队尾,允许删除的一端称为队头。
队列的操作具有先进先出(FIFO)的特点。队列的基本运算有置空队列、判对空、入队列、出队列、取队头五种。
场景:消息队列、JUC中锁的底层原理大量运用了队列。
栈是限定在表的一端进行插入和删除运算的线性表,通常将 插入、删除的一端称为栈顶,另一端称为栈底。不含元素的称为空栈。
栈的操作具有先进后出、后进先出(LIFO)的特点。栈的运算主要有置空栈、判栈空、判栈满、进栈、退栈和取栈顶元素六种。
使用场景:JVM虚拟机栈(方法执行会开辟一块栈帧,栈帧是栈的元素。方法调用的开始到结束就是压栈弹栈的过程)
数组可以说是最基本最常见的数据结构。数组一般用来存储相同类型的数据,可通过数组名和下标进行数据的访问和更新。
数组是有序元素的序列,在内存中是的分配连续的,对内存的使用要求高,需要一块完整内存存放数据。数组初始化时需要指定数组的大小,无法动态的扩容。数组的访问是通过下标(索引)访问的,查询效率很高时间复杂度是O(1)。但是增删的效率就会明显的下降时间复杂度为O(n),由于要保证数据在内存中的连续性所以增删元素时会导致大量的元素被迫移动。
数组的优点:查询速率快。
数组的缺点:增删元素慢
使用场景:java集合框架中的ArrayList底层使用的就是数组
在内存中是非连续的,可以更好的利用内存碎片。由于这个特性所以可以更加好的利用内存,同时访问的性能也是比较差的。在内存中链表的每个元素存放着数据和指针域(指向下一个节点的指针)两个部分。 查找的时间复杂度是O(n)由于在内存中是非连续分布的所以每次访问元素都需要遍历整个链表,所以当元素很多时性能非常差。.增删元素的效率是(1)由于增删元素只需要调整指针的指向所以效率极快。
优缺点分析:
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。查询慢增删快。
链表根据其不同的特征又分为三类:单向链表、双向链表、循环链表。
一:单向链表
链表中最简单的一种是单向链表,或叫单链表,它包含两个域,一个数据域和一个指针域,指针域用于指向下一个节点,而最后一个节点则指向一个空值,如下图所示:
单链表的遍历方向单一,只能从链头一直遍历到链尾。它的缺点是当要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低,而双向链表的出现恰好解决了这个问题。
二:双向链表
双向链表也叫双面链表,Java集合框架中的LinkedList底层使用的就是双向链表,它的每个节点由三部分组成:prev 指针指向前置节点,此节点的数据和 next 指针指向后置节点,如下图所示:
三:循环链表
循环链表又分为单循环链表和双循环链表,也就是将单向链表或双向链表的首尾节点进行连接,这样就实现了单循环链表或双循环链表了,如下图所示:
使用场景:
链表作为一种基本的物理结构,常被用来构建许多其它的逻辑结构,如堆栈、队列都可以基于链表实现。
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。它利用数组支持按照下标访问的特性,所以散列表其实是数组的一种扩展,由数组演化而来。查找效率是O(1)
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
散列表首先需要根据key来计算数据存储的位置,也就是数组索引的下标;
HashValue=hash(key)
在上图散列表中,最下面连续的是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。
再上图散列表张,竖着的是链表,可以看出由一根指针连接着。每次存放元素前都会通过hash函数计算然后存放在计算后的位置,当出现hash碰撞时会直接挂在链表的下面。上图采用的求模取余方法存放元素。
场景:MySQL底层的索引结构,使用较少。
缺点:适用等值查询不支持范围查找、无法order by 排序。
树作为一种树状的数据结构,其数据节点之间的关系也如大树一样,将有限个节点根据不同层次关系进行排列,从而形成数据与数据之间的父子关系。常见的数的表示形式更接近“倒挂的树”,因为它将根朝上,叶朝下。
树的数据存储在结点中,每个结点有零个或者多个子结点。没有父结点的结点在最顶端,成为根节点;没有非根结点有且只有一个父节点;每个非根节点又可以分为多个不相交的子树。
当链表的元素增加到很多时其查询效率明显下降,所以树这种结构可以很好的弥补这点。树可以理解成链表的plus版本,其本身其实就是链表的一个变种。可以很好的弥补链表的查询料率的同时也不会使其增删效率太差。树的种类虽然很多,但都是由二叉树(二分查找思想)衍生而来,做了某些优化加上了一些特性而产生的有其特点的树。
一:二叉搜索树(Binary Search Trees)
二叉查找树的特征:
1.左子树上所有结点的值均小于或等于它的根结点的值。
2.右子树上所有结点的值均大于或等于它的根结点的值。
3.左、右子树也分别为二叉排序树。
缺陷:
当插入新节点的时候,依据二叉树的特性,数据大小一直小于就会导致下图的情形,几乎变成了线性结构,和链表差不多了。查询效果会大大折扣。所以就衍生出了红黑树(下面会详细介绍)
完全二叉树:除了最后一层结点,其它层的结点数都达到了最大值;同时最后一层的结点都是按照从左到右依次排布
满二叉树:除了最后一层,其它层的结点都有两个子结点
二:平衡二叉搜索树(AVL树)
AVL树是一种特殊的二叉搜索树,相对于数据极端的情况下,二叉搜索树会退化成单链表,AVL树定义了旋转操作,在平衡大于等于2时,AVL树会旋转来调整树的结构,来重新满足平衡因子小于2。
平衡二叉树的产生是为了解决二叉排序树在插入时发生线性排列的现象。由于二叉排序树本身为有序,当插入一个有序程度十分高的序列时,生成的二叉排序树会持续在某个方向的字数上插入数据,导致最终的二叉排序树会退化为链表,从而使得二叉树的查询和插入效率恶化
平衡二叉树的出现能够解决上述问题,但是在构造平衡二叉树时,却需要采用不同的调整方式,使得二叉树在插入数据后保持平衡。主要的四种调整方式有LL(左旋)、RR(右旋)、LR(先左旋再右旋)、RL(先右旋再左旋)。这里先给大家介绍下简单的单旋转操作,左旋和右旋。LR和RL本质上只是LL和RR的组合。
在插入一个结点后应该沿搜索路径将路径上的结点平衡因子进行修改,当平衡因子大于1时,就需要进行平衡化处理。从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点,如果这三个结点在一条直线上,则采用单旋转进行平衡化,如果这三个结点位于一条折线上,则采用双旋转进行平衡化。
左旋的操作可以用一句话简单表示:将当前结点S的左孩子旋转为当前结点父结点E的右孩子,同时将父结点E旋转为当前结点S的左孩子。可用动画表示:
右旋:S为当前需要左旋的结点,E为当前结点的父节点。右单旋是左单旋的镜像旋转。
左旋的操作同样可以用一句话简单表示:将当前结点S的左孩子E的右孩子旋转为当前结点S的左孩子,同时将当前结点S旋转为左孩子E的右孩子。可用动画表示:
三:红黑树(Red-Black Trees)
红黑树是一种自平衡的二叉查找树。除了符合二叉查找树基本特征外,还具有下列附加特征
红黑树特性:
1.结点是红色或黑色。
2.根结点是黑色。
3.每个叶子结点都是黑色的空结点(NIL结点)。
4 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
下图中这棵树,就是一颗典型的红黑树:
红黑树通过将结点进行红黑着色,使得原本高度平衡的树结构被稍微打乱,平衡程度降低。红黑树不追求完全平衡,只要求达到部分平衡。这是一种折中的方案,大大提高了结点删除和插入的效率。C++中的STL就常用到红黑树作为底层的数据结构。
使用场景:HashMap底层结构是数组+链表,JDK1.8中底层结构加入了红黑树,数组+链表+红黑树。
四:B树(B-Tree)
英文名字叫做B-tree,中间的短线是英文连接符,只是翻译的时候将短线翻译成了减号。全称Balance-tree(平衡多路查找树),平衡的意思是左边和右边分布均匀。多路的意思是相对于二叉树而言的,二叉树就是二路查找树,查找时只有两条路,而B-tree有多条路,即父节点有多个子节点。
B树的阶数:M阶表示 一个B树的结最多有多少个查找路径(即这个结点有多少个子节点)。M=M路,M=2是二叉树,M=3则是三叉树。
B树的定义如下:
1、每个结点最多有m-1个关键字。
2、根结点最少可以只有1个关键字。
3、非根结点至少有Math.ceil(m/2)-1个关键字。 Math.ceil() 为向上取整。
4、每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
5、所有叶子结点都位于同一层,或者说根结点到每个叶子结点的长度都相同。
上图是一颗阶数为3的B树。在实际应用中的B树的阶数m都非常大(通常大于100),所以即使存储大量的数据,B树的高度仍然比较小。每个结点中存储了关键字(key)和关键字对应的数据(data),以及孩子结点的指针。我们将一个key和其对应的data称为一个记录。但为了方便描述,除非特别说明,后续文中就用key来代替(key, value)键值对这个整体。在数据库中我们将B树(和B+树)作为索引结构,可以加快查询速速,此时B树中的key就表示键,而data表示了这个键对应的条目在硬盘上的逻辑地址。
五:B+树(B+Tree)
B+的特性:
1、有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
2、所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3、所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素
B+树和B树最大的不同是:
1、B+树内部有两种结点,一种是索引结点,一种是叶子结点。
2、B+树的索引结点并不会保存记录,只用于索引,所有的数据都保存在B+树的叶子结点中。而B树则是所有结点都会保存数据。
3、B+树的叶子结点都会被连成一条链表。叶子本身按索引值的大小从小到大进行排序。即这条链表是 从小到大的。多了条链表方便范围查找数据。
4、B树的所有索引值是不会重复的,而B+树 非叶子结点的索引值 最终一定会全部出现在 叶子结点中。
使用场景:MySQL的索引底层数据结构
缺点:1、增删操作的性能会下降 2、索引文件需要占用额外的磁盘空间
具有以下的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,示例图如下:
堆常用来实现优先队列,由于堆的根节点是序列中最大或者最小值,因而可以在建堆以及重建堆的过程中,筛选出数据序列中的极值,从而达到排序或者挑选topK值的目的。
图相较于上文的几个结构可能接触的不多,但是在实际的应用场景中却经常出现。比方说交通中的线路图、地铁路线图、百度地图常见的思维导图都可以看作是图的具体表现形式。
图结构一般包括顶点和边,顶点通常用圆圈来表示,边就是这些圆圈之间的连线。边还可以根据顶点之间的关系设置不同的权重,默认权重相同皆为1。此外根据边的方向性,还可将图分为有向图和无向图。
如下图所示:
数据结构在计算机科学中是一门很重要的基础学科,我们应该好好掌握它。以上只是介绍了各种数据结构的基本概念和基本的特征。大家可以在理解了这些的前提下去深入的学习数据结构。希望以上内容能帮助到你,欢迎大家留言讨论,相互学习。
数据结构推荐学习网站:数据结构可视化操作网站