注:本篇内容参考了《Java常用算法手册》一书。
本人水平有限,文中如有错误或其它不妥之处,欢迎大家指正!
记得几年前曾学过数据结构和算法,看的是《Java常用算法手册》,当时在武汉也是找了几个书店才买到。学了一段时间觉得书还不错,自己也明白了不少编程的知识。但时间一久,工作中也用的很少,慢慢的就忘记了好多。现在也需要重新学学了,毕竟工作这些年了,至少要必须要掌握一些常用的数据结构。
一是充实自己;二是认为想要把Java以及相关的知识学的深一点,数据结构是必须的一个知识点,比如讲HashMap的原理,如果你对散列表本来就理解的比较深入,把理解HashMap就很容易了。
目录
2,5 直接前趋(Immediate Predecessor)
3,1 数据的逻辑结构(Logical Structure)
3,2 数据的存储结构(Storage Structure)
6,1 顺序存储方式(Sequential Storage Structure)
6,2 链接存储方式(Linked Storage Structure)
6,3 索引存储方式(Index Storage Structure)
1,数据结构概念
Sartaj Sahni在其经典著作《数据结构、算法与应用》一书中提出:数据结构是数据对象、存在于该对象的实例以及组成实例的数据元素之间的各种关系,并且这种关系可以通过定义相关的函数来给出。
Clifford A.Shaffer在其《数据结构与算法分析》一书中认为:数据结构是抽象数据类型ADT的物理实现。其中抽象数据类型ADT,英文全称为Abstract Data Type,简称ADT。
计算机科学家尼克劳斯·沃思(Nikiklaus Wirth)提出了“数据结构+算法=程序”的著名公式。
数据结构是数据的组织形式,是计算机中对数据的一种存储和组织形式,同时也泛指相互之间存在一种或多种特定关系的数据的集合。可以用来表征特定的数据对象。合理的数据结构能够提高算法的执行效率,还可以提高数据的存在效率。
2,数据结构中的基本概念
下面以我们常见的数据表格来说明数据结构中的基本概念。下面先画一个表格。
| 序号 | 姓名 | 地址 | 电话 |
| 1 | 张三 | 深圳 | 12345678901 |
| 2 | 李四 | 北京 | 12345678902 |
| 3 | 王五 | 襄阳 | 12345678903 |
2,1 数据(Data)
数据是信息的载体,其能被计算机识别、存储和加工处理,是计算机程序加工的“原材料”。数据包含的类型非常广泛,如整数、字符、字符串、实数、图像、声音等。数据可认为是由若干个数据元素组成的。上述表格就是一个数据,其中每一行可以看做是一个数据元素,也可叫做记录或结点,每一行这个数据元素是由序号、姓名、地址、电话数据项构成。
2,2 数据元素(Data Element)
数据元素是数据的基本单位,其也称元素、结点、顶点、记录等。一般来讲,一个数据元素由若干个数据项组成。在上述表格中每一行可看作是一个数据元素。由序号、姓名、地址、电话数据项构成。
2,3 数据项(Data Item)
数据项是具有独立含义的最小标识单位,也称为字段、域、属性等。在上述表格中,每一行每一列所在的单元格可看作是一个数据项。
2,4 数据结构(Data Structure)
数据结构是数据之间的相互关系,也是数据的组织形式。
2,5 直接前趋(Immediate Predecessor)
例如对一个表格,对表中任意一个结点,其直接前趋结构最多只有一个,直接前趋结点也就是与它相邻且在它前面的结点。表格中第一个结点没有直接前趋,也就是开始结点。在上述表格中,李四的直接前趋是张三。
2,6 直接后继(Immediate Successor)
例如对一个表格,对表中任意一个结点,直接后继结点最多只有一个,直接后继结点是与它相邻且在它后面的结点。表中最后一个结点没有直接后继,也就是终端结点。在上述表格中,李四的直接后继是王五。
3,数据结构的内容
一般来说,数据结构包含三方面的内容:数据的逻辑结构、数据的存储结构、数据的运算。
3,1 数据的逻辑结构(Logical Structure)
也就是数据元素(Data Element)之间的逻辑关系。是从逻辑关系上对数据进行描述,与数据在计算机中是如何存储无关,它是独立计算机的抽象概念。从数据分析的角度来看,数据的逻辑结构可以看做是从具体的问题抽象出来的数据模型。
3,2 数据的存储结构(Storage Structure)
是数据元素及其逻辑关系在计算机存储器中的表示形式。数据的存储依赖于计算机语言,是逻辑结构用计算机语言的实现。一般来讲,只有在高级语言的层次上才会讨论存储结构,在低级的机器语言中,存储结构是具体的。
3,3 数据的运算
对数据施加操作。数据的运算的基础在于数据的逻辑结构,因为每种逻辑结构都可以归纳一个运算的集合。在数据结构范畴内,最常用的运算包括检索、插入、删除、更新、排序等。
4,数据结构是一个有机整体
数据的逻辑结构、数据的存储结构和数据的运算是一个整体,孤立的去理解这三者中的任何一个都是不全面的。
4,1 同个逻辑结构可以有不同的存储结构
比如线性表是最简单的一种逻辑结构,如果线性表采用顺序方式存储,这种数据结构就是顺序表;如果采用链式方式存储,这种数据结构就是链表;如果线性表采用散列方式存储,这种数据结构就是散列表。
4,2 同一种逻辑结构也可以有不同的数据运算集合
相同的数据逻辑结构和存储结构,采用不同的运算集合及运算性质,将会有全新的数据结构。比如线性表,若将线性表的插入运算限制在表的一段,而删除操作限制在表的另一端,那这种数据结构就是队列;若将线性表的插入和删除都限制在表的同一段,那这种数据结构就是栈。
所以数据结构中这三个方面是一个有机的整体,缺一不可。数据的逻辑结构、数据的存储结构和数据的运算任何一个发生变化都将导致一个全新的数据结构出现。
5,数据结构的分类
数据结构有多种,按照数据的逻辑结构来对其进行简单分类,包含线性结构和非线性结构两类。
5,1 线性结构
线性结构就是各个结点具有线性关系,比如一维数组、栈、队列和串都是线性结构。它包含以下几个特点:
- 线性结构是非空集;
- 线性结构有且仅有一个开始结点和终端结点,即开始结点和结束结点都是唯一的;
- 线性结构所有结点最多只有一个直接前趋结点和直接后继结点,即结构中各元素之间存在一对一的关系,且是相连的;
- 存储结构包含顺序存储结构和链式存储结构,顺序存储的线性表称为顺序表,顺序表中的元素都是连续的,按照逻辑顺序依次存放在计算机的一组连续的存储单元中;链式存储的线性表称为链表,链表中存储的元素不一定是连续的,元素节点中存放着数据元素以及相邻元素的地址信息。
比如一维数组a1,a2......an,a1是开始结点,an是终端(结束)结点,且这两个都是唯一的,a2只有一个直接前趋a1,a2也只有一个直接后继a3,数据中的元素是连续的。
5,2 非线性结构
非线性结构就是各个结点之间具有多个对应关系,多维数组、广义表、树、图都是非线性结构。它有以下几点特点:
- 非线性结构是非空集;
- 非线性结构的一个结点可能有多个直接前趋结点和直接后继结点,即结构中各元素可能有多个指向关系,还可能有层次关系。
如上面的树,A结构有多指向,它同时指向了B和C,且有四层关系。
6,数据结构的存储方式
数据的存储方式有以下四种:顺序存储方式、链接存储方式、索引存储方式和散列存储方式。
6,1 顺序存储方式(Sequential Storage Structure)
顺序存储方式就是一块连续的存储区域,一个接一个地存放数据。它把逻辑上相邻的结点存储在物理位置上相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。顺序存储方式也称为顺序存储结构,一般采用数组或结构数组来描述。顺序存储的线性表称为顺序表,顺序表中的元素都是连续的。
顺序存储方式主要用于线性逻辑结构的数据存放,比如Java中的一维数组,像图、树这样的非线性结构则不适用。
6,2 链接存储方式(Linked Storage Structure)
链接存储比较灵活,因为它不要求逻辑上相邻的结点在物理位置上也相邻,结点间的逻辑关系由附加的引用字段来表示。即一个结点的引用 字段往往指向下一结点的存放位置。
链接存储方式也称为链式存储结构,一般在原数据项中增加引用类型来表示结点之间的位置。简而言之,就是链接存储方式中存储的元素不一定连续,结点中除了存储元素数据外还存储了下一个节点的地址信息。
6,3 索引存储方式(Index Storage Structure)
索引存储方法是采用附加的索引表的方式来存储结点信息。索引表由若干索引项组成,索引存储方式中索引项的一般形式为:(关键字、地址)。其中关键字能唯一标识一个结点的数据项。索引存储方式还可细分为两类:
稠密索引(Dense Index):每个结点在索引表中都有一个索引项,其中索引项的地址指示结点所在位置信息;
稀疏索引(Spare Index):一组结点在索引表中只对应一个索引项,其中索引项的地址指示一组结点的起始存储位置。
软件行业中的很多数据库的表都用到了索引,也是类似的一种存储方式。
6,4 散列存储方式
散列存储方式是根据结点的关键点直接计算出该结点的存储地址的一种存储方式。像Java的HashTable的hashcode就是如此。
在实际应用中,往往需要根据具体的数据结构来决定采用哪种存储方式。同一逻辑结构采用不同的存储方式,可以得到不同的存储结构。以上四种存储方式即可单独使用,也可组合来使用。
7,常用数据结构
平时工作中会涉及到一些常用的数据结构,这里先做简要说明,后面对有详细一点的说明及代码示例。
7,1 数组(Array)
数组是一种聚合数据类型,将具有相同类型的若干变量有序的组织在一起的集合。数组可以说是最基本的数据结构了。在很多编程语言中都有对应。
一个数组可分解为多个数组元素,按照元素的类型,可分为整型数组、字符数组、对象数组等。还有一维、二维以及多维数据等。
7,2 栈(Stack)
栈是一种特殊的线性表,它只能在一个表的一个固定端进行数据结点的插入和删除操作(可操作的一端称为栈顶,另一端称为栈底)。栈按照后进先出(Last In First Out,LIFO)的原则来进行存储数据。即先插入的数据将被压入栈底,最后插入的数据在栈顶。读出数据时从栈顶开始逐个读出。在汇编语言中经常用于重要数据的现场保护。栈中没有数据时,称为空栈。
在栈结构中,只有栈顶元素是可以访问的,其基本操作操作有两个:
- 入栈(Push):将数据保存到栈顶的操作;
- 出栈(Pop):将栈顶的数据弹出的操作。
到这里也知道了,常说的堆栈,其中是有堆和栈两个数据结构。
7,3 队列(Queue)
队列和栈类似,也是一种特殊的线性表。和栈不同的是队列史允许在表的一端进行插入操作,而在另一端进行删除操作。进行插入操作的一端称为队尾,进行删除操作的一端称为队头。队列中没有元素中称为空队列。
7,4 链表(Linked List)
链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构在物理上是非连接的。它由一系列数据结点组成,每个数据节点包括数据域和引用域两部分。其中引用域保存数据结构中下一个元素存储的地址。链表结构中数据元素的逻辑顺序是通过链表中的引用链接次序来实现的。
7,5 树(Tree)
树是典型的非线性结构,它是包含了n个结点的有空集合K。在树结构中,有且仅有一个根结点,该结点没有前趋结点。在树结构中的其它结点都是有且仅有一个前趋结点,而且可以有m个后继结点,m >= 0。如下图。
7,6 图(Graph)
图是另一种非线性数据结构。在图结构中,数据结点一般称为顶点,而边是顶点的有序偶对。若两个项点之间存在一条边,那么就表示这两个顶点具有相邻的关系。
7,7 堆(Head)
堆是一种特殊的树型数据结构,平时讨论较多的树结构是二叉树。堆的特点是其根结点的值是所有结点中最小的或者最大的,并且根结点的两个子树也是一个堆结构。
7,8 散列表(Hash)
散列表源自于散列函数(Hash function),其思想是若在结构中存在关键字和T相等的记录,那么必定有F(T)的存储位置可以找到该记录,这样就可以不用进行比较而直接取得所要查找的记录了。
8,顺序表结构和链表结构
8,1 线性表(Linear List)
线性表是简单也是最常用的数据结构。从逻辑上看,它是由n(n >= 0)个数据元素a1, a2, ..., an组成的有限序列。数据元素的个数为n,也称为表的长度,当n = 0时称为空表。
8,2 顺序表(Sequential List)
顺序表(Sequential List)是按照顺序存储方式存储的线性表。若所有结点的类型相同,那每个结点所占用的存储空间大小就是一样的。每个结点占用 c 个存储单元,其中第一个单元的存储是该结点的存储地址,并设顺序表中开始结点 a1 的存储地址(简称基地址)是LOC(a1),那么结点 ai 的存储地址LOC(ai)可以通过下面的公式计算:【其中1 <= i <= n】
这就是能够操作顺序表进行运算的基本规则。下面看下顺序表的优缺点。
当向顺序表中插入一新的结点,那第表的长度将增加1,其后的结点编号依次加1,插入结点的难点在于随后的每个数据都要进行移动,计算量是比较大的。
追加结点可能看作是插入结点的一种特殊形式,相当于在顺序表的末尾新增加一个数据结点。此时表的长度也会增加1,但不必进行大量数据的移动。
删除结点就是表中的某个结点,使得其后的所有结点的编号依次减1,需要进行大量数据的移动。
查找结点时,就是在线性表中找到值为X的结点,并返回该结点在表中的位置。此操作操作,是可以随机访问的。
- 优点:容易理解,操作方便,查找方便;
- 缺点:因为是连续存储的,所以在插入或删除结点时,往往需要移动大量的数据;若表比较大,有时比较难以分配足够的连续存储空间,往往导致内存分配失败而无法存储。
8,3 链表结构
如下图所示,链表中的每个结点都应该包含两个部分:
- 数据部分:保存该结点的实际数据;
- 地址部分:保存下一个结点的地址信息(指针)。
链表结构就是由许多这样的结点构成的。在进行链表操作时,首先需要定义一个“头引用”变量,一般以head表示,该引用变量指向链表结构的第一个结点,第一个结点的地址部分又指向第二个结点...,直到最后一个结点。最后一个结点不再指向其他结点,称为“表尾”,一般在表尾的地址部分存放 一个空地址“null”,链表到此结束。从上图可以看出,整个存储过程很像一个长链条,因此形象的称为链表结构,或链表结构。
由于链表采用了引用来指示下个数据的地址,因此在链表结构中,逻辑上相邻的结点在内存中并不一定相邻。逻辑相邻关系通过地址部分的引用变量来实现。
链表结构带来的最大好处就是结点之间不要求连续存放,因此在保存大量数据时,不需要分配一块连续的存储空间。用户可以用new函数动态分配结点的存储空间,当删除某个结点时给该节点赋值"null",释放其占用的空间。
链表结构也有缺点,那就是浪费存储空间。对于每个结点数据,都要额外保存一个引用变量,但在某此场合,链表结构所带来的好处还是大于其缺点的。
对于链表的访问只能从表头开始逐个查找,即通过head头引用找到第一个结点,再从第一个结点找到第二个结点,...这样逐个比较一直到找到需要的结点为止,而不能像顺序表那样进行随机访问。
综上所述,链表结构的优缺点如下:
- 缺点:不像顺序表那样只存储数据,还需要存储引用信息,所以较为占用空间;查找较顺序表困难,需要逐个查找;
- 优点:因为结点存储不是连续的,所以插入和删除方便且性能高,插入和删除后不需要像顺序表那样移动后面的数据。
链式存储是最常用的存储方式之一,它不仅可以用来表示线性表,也可以用来表示各种非线性的数据结构。链表结构可细分为以下几类:
- 单链表:和上面链式结构一样,每个结点中只包含一个引用;
- 双向链表:若每个结点包含两个引用,一个指向下一个结点,另一个指向上一个结点,这就是双向链表;
- 单循环链表:在单链表中,将终端结点的引用域null改为指指向表头结点或开始结点即可构成单循环链表;
- 多重链的循环链表:如果将表中的结点链在多个环上,将构成多重链的循环链表。