线性表(LinearList)是由n(n$ \geq 0 ) 个 属 于 同 一 个 数 据 对 象 的 数 据 元 素 ( 节 点 ) 0)个属于同一个数据对象的数据元素(节点) 0)个属于同一个数据对象的数据元素(节点)a_1,a_2,a_3,…,a_n$组成的有限序列。
线性表中每个元素必须具有相同的结构(即拥有相同的数据项)。线性表是线性结构中最常用而又最简单的一种数据结构。
线性表中每个数据元素其实可以包含若干个数据项,例如,使用 a i a_i ai来代表线性表中的第i个元素,其中 a i a_i ai元素可以包含若干个数据项。
对于一个非空,有限的线性表而言,它总具有如下特征。
同一个线性表中的数据元素必定具有相同特性,即它们都属于同一个数据对象。
前驱和后继
数据结构中,一组数据中的每个个体成为“数据元素”(简称“元素”)。
- 某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”。
- 某一元素的右侧相邻元素称为“直接后驱”,位于此元素右侧的所有元素都统称为“后驱元素”。
如果需要实现一个线性表,程序首先需要确定该线性表的每个数据元素。接下来,应该为该线性表实现如下基本操作。
注:进行某些操作(如对线性表进行插入或者删除操作)时,在某种存储结构下,会引起一系列元素的移动,降低操作效率,尤其是当线性表的长度很大时,这种移动可能比较突出。
数组
数组就是相同数据类型的元素按一定顺序排列的集合。本质:物理上存储在一组联系的地址上,也就是数据结构中的顺序存储物理结构。
数组分为静态数组和动态数组,在定义数组时,首先要确定数组的大小。
静态数组在编译时就需要确定数组的大小,所以,为了防止内存溢出,我们尽量将数组定义的大一些,但是这样太过浪费内存。
动态数组则不同,它不需要在编译时就确定大小,它的大小在程序运行过程中确定,所以可以根据程序需要而灵活的分配数组的大小,相比静态数组,它更“灵活”、“自由”。但是动态数组需要进行显式的内存释放。
线性表
线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。本质:线性表是数据结构中的逻辑结构。线性表可以通过数组(顺序存储结构)存储,也可以通过链式存储。
线性表根据存储结构的不同可以分为顺序表和链表。其中,顺序表为顺序存储的线性表,即用数组描述的线性表就是顺序表;链表为链式存储的线性表。
链表可以根据描述方式的不同分为静态链表和动态链表。通过指针对链表进行描述的称为动态链表,如我们常说的单链表,循环链表等;通过数组对链表进行描述的称为静态链表,主要为了解决没有指针或者不用指针的情况下具备链表插入删除操作便捷的特性。
注意
看到很多人直接将顺序表等同于动态数组,认为实现了数组长度可变,数据可删减,但这样做容易造成概念混淆。我们可以通过数组实现顺序表,但动态数组的概念并不是实现数组长度可变,而是通过new操作符或malloc函数实现运行时内存的动态分配。
线性表的顺序存储结构是指用一组地址连续的存储单元依次存放线性表的元素。当程序采用顺序存储结构来实现线性表时,线性表中相邻元素的两个元素 a i a_i ai和 a i + 1 a_i+1 ai+1对应的存储地址 l o c ( a i ) loc(a_i) loc(ai)和 l o c ( a i + 1 ) loc(a_i+1) loc(ai+1)也是相邻的。
换句话说,顺序结构线性表中数据元素的物理关系和逻辑关系是一致的。
顺序存储结构比较适合于线性表的长度不经常发生变化,或者只需要在顺序存取设备上做批处理的场合。
链式存储结构的线性表(简称为链表)将采用一组地址任意的存储单元存放线性表中的数据元素。链式存储结构的线性表不会按线性的逻辑顺序来保存数据元素,他需要在每个数据元素里保存一个引用下一个数据元素的引用(或者叫指针)。
无论位于链表何处,无论链表的长度如何,插入和删除操作的时间都是O(1)
在链表结构中,每个节点仅存储本身需要存储的数据和下一个节点地址的这种链表结构,我们称为单链表结构,其示意图如下:
如图所示,在单链表中的每个节点中,除了数据区域外,还有一个区域存储了当前节点的下一节点的地址,我们把这个记录下个结点地址的指针或引用叫作后继指针或引用Next。
循环链表是一种特殊的单链表,特殊之处在于,我们在单链表中,尾节点的后继指针或者引用不是指向一个具体的节点,而是指向一个空地址NULL,表示这就是最后一个节点。而将单链表的尾节点从指向空地址NULL调整为指向头结点Head,就形成了循环链表。
单向链表是单向的,只有一个后继指针或者引用Next指向后面的节点,而双向链表,指的是一个链表结构,它支持两个方向,每个节点不止只有一个后继指针或者引用Next指向后继节点,还有一个前驱指针或者引用Prev指向前面的节点。
从图中可以得知,双向链表需要额外的空间来存储后继节点和前驱节点的地址,所以,存储同样多的数据,双向链表要比单向链表需要的存储空间要多。虽然两个指针或者引用比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。