数据结构的选取在编程中至关重要。选取合适的数据结构可以节省程序的内存开销,让程序不是那么的卡。因此,从本文开始,将复习大学时学习的数据结构。
数据元素之间存在的关联关系被称为数据的逻辑结构。归纳起来无非这几种
- 集合结构
- 线性结构
- 树形结构
- 图形结构
这篇文章主要讨论的是线性结构,其中主要包括:
- 数据的存储结构,包括:顺序存储结构 和 链式存储结构
- 顺序存储结构以及链式存储结构相关的操作,包括:查找、增加、删除
- 链式结构还包括:单链表、循环链表、双向链表
- JDK中提供的线性表相关的数据结构:ArrayList和LinkedList的源码的分析
什么是线性表?
线性表是由n(n >= 0)个数据元素(节点)a1 , a2 , a3….an 组成的有限序列。
线性表的特点
- 总存在唯一的“第一个”数据元素。
- 总存在唯一的“最后一个”数据元素。
- 除第一个数据元素外,集合中的每一个数据元素都只有一个前驱的数据元素。
- 除最后一个数据元素外,集合中的每一个数据元素都只有一个后继的数据元素。
线性存储结构的分类
线性表的存储结构分为:顺序存储结构 和 链式存储结构。
顺序存储结构
顺序存储结构是指用一组地址连续
的存储单元依次存放
线性表的元素。当程序采用顺序存储结构来实现链表时,线性表中相邻元素的两个元素ai 和 a(i+1)对应的存储地址也是相邻的。
插入
线性存储结构的插入运算是指在表的第i(0<= i < n)个位置插入一个新的数据元素X,使长度为n的线性表,变为长度为n+1 的线性表。
插入前: a0 , a1 , a2 .... a(i-1) , ai, .... , a(n-1)
插入后: a0 , a1 , a2 .... a(i-1) , X , ai , .... , a(n-1)
删除
线性存储结构的删除运算是指在表的第i(0<= i < n)个位置删除一个数据元素ai,使长度为n的线性表,变为长度为n-1 的线性表。
插入前: a0 , a1 , a2 .... a(i-1) , ai, .... , a(n-1)
插入后: a0 , a1 , a2 .... a(i-1) , a(i+1) , .... , a(n-1)
链式存储结构
链式存储结构采用一组地址任意的存储单元存放线性表的数据元素。链式存储结构的线性表不会按线性的逻辑顺序来保存数据元素,它需要在每一个数据元素里保存一个引用下一个数据元素的引用(叫做指针)。
特点: 链表的插入、删除数据元素比顺序存储结构的线性表快,但查找慢。
线性表的每一个节点都必须包含数据元素本身和一个或者两个用来引用上一个或者下一个节点的引用。
单链表
单链表是每个节点只保留一个引用,该引用指向当前节点的下一个节点,没有引用指向头节点,尾节点的next引用为null。
单链表的生成:
- 头插法建表:该方法从一个空表开始,不断地创建新结点,将数据元素存入节点的data域中,然后不断地以新节点为头结点,让新节点指向原有的头结点。
- 尾插法建表:将新的节点,插入到当前链表的尾部。
查找
查找第index个节点: 从header节点依次向下在单链表中查找第index个节点。算法为,设header为头,current为当前节点(初始时current从header开始),0为头节点序号,i为计数器,则可使current依次下移寻找节点,并使i同时递增记录节点序号,直接返回指定节点。
查找指定的element元素:查找是否有等于给定值element的节点。若有,则返回首次找到的值为element的节点的索引;否则,返回-1。查找过程从开始节点出发,顺着链表逐个将节点的值和给定值element做比较。
插入
插入操作是将值为element的新节点插入到链表的第index个节点的位置上。步骤如下
- 找到索引为index -1 的节点;
- 生成一个数据域为element的新节点newNode;
- 令index-1处节点的next引用新节点;
- 新节点的next引用原来index处的节点。
删除
删除操作是将链表第index个节点删去。步骤如下:
- 找到索引为index -1 的节点;
- 让index-1处的节点next引用到原index+1处的节点
- 释放index出节点
循环链表
循环链表是一种首尾相连的链表。将单链表的尾节点next指针改为引用单链表的header节点,这个单链表就成了循环链表。
双向链表
双向链表是每个节点保留两个引用prev和next,让prev指向当前节点的上一个节点,让next指向当前节点的下一个节点,此时的链表既可以向后一次访问每个节点,也可以向前依次访问每一个节点。如果将链表的header节点和tail节点链在一起就构成了双向循环链表。
查找
双向链表既可以从header节点开始依次向后搜索每个节点。也可以从tail节点开始依次向前搜索每个节点,因此当程序试图从双向链表中搜索指定索引处的节点时,既可以从该链表的header节点开始找,也可以从tail节点找,一般这么做:
通过index值来判断它更靠近header,还是更靠近tail。如果index < size/2 ,则可判断该位置更靠近header,应该从header开始搜索;否则,应该从tail开始搜索。
插入
由于双向链表每个节点有两个引用,因此,插入的时候,要修改两个方向的指针。
删除
由于双向链表每个节点有两个引用,因此,删除的时候,要修改两个方向的指针。
线性表两种实现的对比
线性表的顺序和链式各有优缺点:
空间性能
顺序表
:顺序表的存储空间是静态分布的,因此需要一个固定的数组,因此可能造成部分数组的浪费链表
:链表的存储空间是动态分布的,因此空间不会被浪费。但是链表要额外的空间来存储指针,也会牺牲部分空间。
时间性能
顺序表
:顺序表中元素的逻辑顺序与物理存储顺序保持一致,而且支持随机存取,因此:查找、读取时性能更好链表
:链表采用链式来保存表内元素,因此在插入、删除元素时性能更好
JDK提供的两种线性表的实现
ArrayList , LinkedList