线性表的定义和基本操作
线性表是具有相同数据类型的
式中,
线性表的特点如下:
- 表中元素的个数有限。
- 表中元素具有逻辑上的顺序性,表中元素有其先后次序。
- 表中元素都是数据元素,每个元素都是单个元素。
- 表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间。
- 表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容。
线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表是指存储结构,两者属于不同层面的概念,因此不要将其混淆。
一个数据结构的基本操作是指其最核心、最基本的操作。其他较复杂的操作可通过调用其基本操作来实现。线性表的主要操作如下:
:初始化表。构造一个空的线性表。 :销毁操作。销毁线性表,并释放线性表 所占用的内存空间。 :插入操作。在表 中的第 个位置上插入指定元素 。 :删除操作。删除表 中第 个位置的元素,并用 返回删除元素的值。 :按值查找操作。在表 中查找具有给定关键字值的元紊。 :按位查找操作。获取表 中第 个位置的元素的值。 :求表长。返回线性表 的长度,即 中数据元素的个数。 :输出操作。按前后顺序输出线性表 的所有元素值。 :判空操作。若 为空表,则返回 ,否则返回 。
基本操作的实现取决于采用哪种存储结构,存储结构不同,算法的实现也不同。
线性表的顺序表示(顺序表)
线性表的顺序存储又称顺序表。它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。第
常用数组来描述线性表的顺序存储结构。一维数组可以是静态分配的,也可以是动态分配的。
- 在静态分配时,由于数组的大小和空间事先已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。
- 在动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数组空间的目的,而不需要为线性表一次性地划分所有空间。动态分配并不是链式存储,它同样属于顺序存储结构,物理结构没有变化。
顺序表的特点:
- 每个数据元素的存储位置都和线性表的起始位置相差一个和该数据元素的位序成正比的常数,线性表中的任一数据元素都可以随机存取(通过首地址和元素序号可在时间
内找到指定的元素),所以线性表的顺序存储结构是一种随机存取的存储结构。 - 顺序表的存储密度高,每个结点只存储数据元素。
- 顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量元素。拓展容量不方便。
顺序表的插入:在顺序表
- 最好情况:在表尾插入,时间复杂度为
。 - 最坏情况:在表头插入,时间复杂度为
。 - 平均情况:所需移动结点的平均次数为
,时间复杂度为 。
顺序表的删除:删除顺序表
- 最好情况:删除表尾元素,时间复杂度为
。 - 最坏情况:删除表头元素,时间复杂度为
。 - 平均情况:所需移动结点的平均次数为
,时间复杂度为 。
顺序表的查找:如果是按位查找的话,直接将位序减一作为数组下标进行随机存取,无论好坏,时间复杂度均为
- 最好情况:查找的元素就在表头,时间复杂度为
。 - 最坏情况:查找的元素在表尾(或不存在),时间复杂度为
。 - 平均情况:所需比较元素的平均次数为
,时间复杂度为 。
线性表的链式表示
链式存储线性表时,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻,它通过“链”建立起数据元素之间的逻辑关系,因此插入和删除操作不需要移动元素,而只需修改指针,但也会失去顺序表可随机存取的优点。
单链表
线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。对每个链表结点,除存放元素自身的信息外,还需要存放一个指向其后继的指针
利用单链表可以解决顺序表需要大量连续存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。由于单链表的元素离散地分布在存储空间中,所以单链表是非随机存取的存储结构,即不能直接找到表中某个特定的结点。查找某个特定的结点时,需要从表头开始遍历,依次查找。
通常用头指针来标识一个单链表,头指针为
引入头结点后,可以带来两个优点:
- 由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理。
- 无论链表是否为空,其头指针都指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理也就得到了统一。
单链表的插入:将值为
如果采用不带头节点的方式,需要特殊考虑第一个节点。如果在第一个节点处插入,需要修改头指针。
后插:若在给定的结点后面插入新结点,则时间复杂度仅为
前插:在某结点
- 先按值查找到
结点的前驱结点,然后使用后插的方式插入新结点,则时间复杂度为 。 - 采用后插的方式将新结点插入到
结点的后面,然后交换两个结点的数据部分,则时间复杂度仅为 。
单链表的删除:将单链表的第
- 查找后删除:先检查删除位置的合法性,后查找表中第
个结点,即被删结点的前驱结点,再将第 个删除,时间复杂度为 。 - 复制后删除:若给出了要删除的结点
,可以将 的后继结点的值赋予其自身,然后删除 的后继结点,能使得时间复杂度为 。
删除操作为将被删结点的前驱结点的指针域
单链表的查找:
- 按位序查找:在单链表中从第一个结点出发,顺指针
域逐个往下搜索,直到找到第 个结点为止,否则返回最后一个结点指针域 。时间复杂度为 。 - 按值查找:从单链表的第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值
,则返回该结点的指针;若整个单链表中没有这样的结点,则返回 。时间复杂度为 。
求表长:计算单链表中数据结点(不含头结点)的个数,需要从第一个结点开始顺序依次访问表中的每个结点,为此需要设置一个计数器变量,每访问一个结点,计数器加
单链表的长度是不包括头结点的,因此不带头结点和带头结点的单链表在求表长操作上会略有不同。对不带头结点的单链表,当表为空时,要单独处理。
单链表的建立:核心就是初始化操作、指定结点的后插操作。
头插法:该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。采用头插法建立单链表时,读入数据的顺序与生成的链表中的元素的顺序是相反的。每个结点插入的时间为
,设单链表长为 ,则采用头插法建立单链表时的总时间复杂度为 。 头插法可以实现链表的逆置。
尾插法:该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针
,使其始终指向当前链表的尾结点。因为附设了一个指向表尾结点的指针,故时间复杂度和头插法的相同。 如果没有尾指针
的话,每次插入都需要查找尾结点,建立单链表的时间复杂度为 。
双链表
双链表结点中有两个指针:前驱结点
双链表不可随机存取,按位查找、按值查找操作都只能用遍历(后向遍历、前向遍历)的方式实现,时间复杂度为
在进行算法设计的过程中,需要注意有无头结点,以及对头结点的考虑。对于插入删除操作,需要注意前驱后继指针修改的顺序,不能发生断链的情况。当操作链头链尾(边界)时,要注意指针为
循环链表
循环单链表和单链表的区别在于,表中最后一个结点的指针不是
循环单链表的插入、删除算法与单链表的几乎一样,所不同的是若操作是在表尾进行,则执行的操作不同,以让单链表继续保持循环的性质。在任何一个位置上的插入和删除操作都是等价的,无须判断是否是表尾。循环单链表可以从表中的任意一个结点开始遍历整个链表。
由循环单链表的定义不难推出循环双链表。不同的是在循环双链表中,头结点的
静态链表
静态链表借助数组来描述线性表的链式存储结构,结点也有数据域
静态链表以
- 优点:增、删操作不需要大量移动元素。
- 缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变。
顺序表和链表的比较
顺序表 | 链表 | |
---|---|---|
存取(读写)方式 | 顺序存取&随机存取 | 顺序存取 |
逻辑结构 | 线性表,线性结构 | 线性表,线性结构 |
物理结构 | 顺序存储 | 链式存储 |
按序查找的时间复杂度 | ||
存储密度 | 高 | 低 |
使用链表的情况:
- 表长难以预估,需要扩容
- 经常要增加/删除元素
使用顺序表的情况:
- 表长可预估,不需要扩容
- 查询(搜索)操作较多