线性表
定义
线性表是具有 相同 数据类型的 n(n>=0)个数据元素的有限序列,其中 n 为表长,当 n=0
时线性表是一个空表。
注意:
- 线性表的数据元素的数据类型都是相同,如都是 int 类型,或者都是 float 类型的,或者都是结构体类型的。
- 线性表的数据元素个数是有限的。
- 空表表示线性表的元素个数为零个,但不表示线性表不存在(为 null)。
若用 L
命名为线性表,则其一般表示为 L = (a1, a2, ..., ai, a(i+1), ..., an)
。特性如下:
- 其中
a1
是唯一的『第一个』数据元素,又称为表头元素; an
是唯一的『最后一个』数据元素,又称为表尾元素;- 除第一个元素之外,每个元素都有且仅有一个直接前驱。如
a1
由于是第一个元素,所以没有直接前驱,只有直接后继a2
,而如a3
有且仅有一个直接前驱a2
。 - 除最后一个元素之外,每个元素有且仅有一个直接后继。如
an
是最后一个元素,所以没有直接后继,只有直接前驱a(n-1)
,而如a2
有且仅有一个直接后继a3
。
线性表的特点如下:
- 表中的元素个数有限。
- 表中元素具有逻辑上的顺序性,表中元素有其先后次序。如
a1
的直接后继是a2
,a2
的直接后继是a3
。而a3
的直接前驱是a2
。 表中元素都是数据元素,每个元素都是单个元素。- 表中元素的数据类型都相同,即每个元素都占有相同大小的存储空间。如都是整型
int
;都是字符型char
;或者都是结构体类型如struct Node
。 - 线性表可以是有序的,也可以是无序的。
- 表中元素具有抽象性,即仅讨论元素之间的逻辑关系,而不考虑元素究竟表示什么内容。
注:线性表是一种逻辑结构,表示元素一对一的相邻关系。而顺序表和链表指存储结构,两者概念不同。
举例:可以把线性表想象成一对学生。
- 学生人数对应线性表的长度,而学生人数是有限的,体现了线性表是一个有限序列。
- 队中所有人的身份都是学生,体现了线性表中的数据元素具有相同的特性。
- 如果学生按照身高来排队,其中矮在前,高在后,体现了线性表是可以有序的。
- 在一队学生中,只有一个学生在队头,同样只有一个学生在队尾,这体现了线性表只有唯一一个表头元素和表尾元素。
- 在队头的学生的前面没有其他学生,在队尾的学生后边也没有其他学生,这体现了线性表表头元素没有直接前驱和线性表表尾元素没有直接后继。
- 对于除了队头和队尾的学生之外的其他任何一个学生,紧挨着站在其前面和后面的学生都只有一个,这体现了线性表除了表头和表尾元素之外其他元素都只有一个直接前驱和一个直接后继。
基本操作
线性表的基本操作是其最核心、最基本的操作,其他更复杂的操作都可以通过一个或多个基本操作来实现。线性表的主要操作如下:
init(&L)
:初始化线性表。构造一个空的线性表,即元素个数为零的线性表。size(L)
:求表长。返回线性表 L 的长度,即线性表 L 中的元素个数。线性表如果为空则表长为零。findByEle(L, e)
:按值查找操作。在线性表 L 中查找具有给定关键字值的元素,然后返回。findByIndex(L, i)
:按位置查找操作。在线性表 L 中查找第 i 个位置的元素的值。insert(&L, i, e)
:插入操作。在表 L 中的第 i 个位置插入指定元素 e。remove(&L, i, &e)
:删除操作。删除表 L 中的第 i 个位置的元素,并用 e 返回删除元素的值。print(L)
:输出操作。按前后顺序输出线性表 L 中的所有元素值。isEmpty(L)
:判空操作。若 L 为空表,则返回 true,否则返回 false。destroy(&L)
:销毁操作。销毁线性表,并释放线性表 L 所占用的内存空间。
注意事项:
基本操作的实现取决于采用哪种存储结构,存储结构不同,算法的实现也不同。
如果采用的存储结构是顺序存储结构,那么参数
i
是从 0 开始的,表示下标;如果采用的存储结构是链式存储结构,那么参数i
是从 1 开始的,表示序号。如果传入参数的是
L
则表示在函数内不会修改链表节点(增加节点或删除节点);如果传入参数的是&L
则表示在函数内会修改链表节点,但&
是 C++ 语言中的引用调用,而在 C 语言中通常使用**
指针来达到同样的操作。在后面代码实现中,多采用 C 语言中的双指针
**
来实现操作,而非 C++ 语言中的&
来操作,如果提供了使用了&
引用调用的代码,则是通过了Dev-C++
编译器的操作。
存储结构
线性表的存储结构分为 顺序存储结构 和 链式存储结构 两种。前者称为 顺序表,后者称为 链表。
顺序表
顺序表就是按线性表中所有元素按照其逻辑顺序(可以是按照元素输入的顺序),依次存储到从指定的存储位置开始的一块连续的存储空间中。即线性表中的第一个元素的存储位置就是指定的存储位置,第 i+1
个元素存储位置紧接在第 i
个元素的存储位置的后面。
更多请参考:顺序表。
链表
在链式存储中,每个节点不仅包含所存元素的信息,还包含元素之间逻辑元素的信息。如单链表中前驱节点包含后继节点的地址信息,这样就可以通过前驱节点中的地址信息找到后继节点的位置。
注:链表分为带头结点和不带头结点的链表。
两种存储结构的比较
顺序表的特性:
- 支持随机访问。即可以通过下标就能访问到顺序表中指定下标处存储的元素。
- 占用连续的存储空间。存储分配只能预先进行(即在使用之前就必须分配一个连续存储空间的数组),一旦分配好了之后,在对其操作过程中始终不变,即无法修改数组的长度。
- 插入和删除操作需要移动多个元素。在顺序表中插入或者删除操作,都需要移动多个元素,如在顺序表的第一个位置插入新元素,则需要将原第一个位置及之后的所有结点元素都向后移动一位。
链表的特性:
- 不支持随机访问:如果要找到第
k
个元素,必须从链表的第一个元素开始遍历,最坏情况下,可能需要遍历整个链表才能找到第k
个元素。 - 存储空间利用率较顺序表稍低一些。因为链表中的每一个节点需要划分出一部分空间来存储指向下一个节点位置的指针,所以需要的空间相对顺序表更多一点。
- 支持存储空间动态分配。不要求占用连续的存储空间,即链表中的节点可以散落在内存中的任意位置,且不需要一次性地划分所有节点所需的空间给链表,而是需要几个节点就临时划分几个。
- 插入和删除操作不需要移动元素。在链表中插入或删除结点,只需要修改指针指向即可,无需移动节点。
链表的几种形式
链表有如下五种形式:
单链表
在每个节点中除了包含数据域外,还包含一个指针域,指向其后继节点。如图是带头结点的单链表:
更多请参考:单链表。
双链表
单链表只能从开始节点走到尾节点,而不能从尾节点反向走到开始节点。为了解决从链表的尾节点到开始节点的问题,可以使用双链表。双链表在单链表节点的基础上添加了一个指针域,指向当前节点的直接前驱,方便由其后继找到其前驱。如图是带头结点的双链表:
如图可以通过 4
的前驱指针找到它的前驱节点 3
。
更多请参考:双链表。
循环单链表
循环单链表是将单链表的最后一个节点的指针域指向链表的第一个节点。循环单链表可以实现从任何一个节点出发访问链表的任何节点,而单链表从任一节点出发后只能访问这个节点本身及其后边的所有结点。如图是带头结点的循环单链表:
注:之所以说是第一个结点。是因为如果循环单链表是带头结点的,则最后一个节点的指针域指向头结点;如果循环单链表是不带头结点的,则最后一个节点的指针域指向第一个节点。
更多请参考:循环单链表。
循环双链表
循环双链表就是在双链表结点的基础上将最后一个结点的 next
指针指向链表的第一个结点,将链表中的第一个结点的 prior
指针指向链表的最后一个结点。如图是带头结点的循环双链表:
更多请参考:循环双链表。
静态链表
静态链表可以通过数组来实现链表的功能。静态链表是一个结构体数组,数组中每一个结点含有两个分量:一个是数据元素 data
;另一个是指针分量,指示了当前节点的值即后继节点在数组中的位置(下标),这在和一般链表中 next
指针的功能是一样的。
注:静态链表中的指针不是我们通常说的 C 语言中的用来存储内存地址的指针型变量,而是一个用来存储数组下标的整型变量,通过它可以找到后继节点在数组中的位置,功能类似于真实的指针,因此称为指针。
更多请参考:静态链表