线性表Ⅲ- 链式存储
想看博主whywait的更多同系列查漏补缺必看文?点击传送门,点击关注不迷路哦
之前我们介绍了线性表的顺序表示,现在我们来介绍一下线性表的另一种表示方法——链式表示。
基础知识
注意:下面都是关于线性表的链式存储结构的介绍,为了文章的简洁,有些地方适当省略。
特点
线性表链式存储结构的特点:用一组任意的存储单元存储线性表的数据元素.
这一组存储单元可以是连续的,也可以是不连续的
这是最基础的,也是最应该记住的一点!一定要记住!
几种链式存储结构的介绍
单链表
下面我们来介绍单链表。
单链表是最基础的一种链式存储结构。
由前文中对于链式存储结构的特点的介绍中,我们知道,“链式存储结构的存储单元可以是连续的,也可以是不连续的”。如此一来,为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,对每个数据元素来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。
几个概念
结点(node):由两部分信息组成数据元素的存储映像。两部分信息分别为上文提到的每个数据元素本身的信息以及指示其直接后继的信息。
结点包括两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。指针域中存储的信息称作指针或链。 n n n个结点( a i , ( 1 ⩽ i ⩽ n ) a_i,(1\leqslant i\leqslant n) ai,(1⩽i⩽n)的存储映像)链接成一个链表,即为线性表 ( a 1 , a 2 , ⋅ ⋅ ⋅ , a n ) (a_1, a_2, ···, a_n) (a1,a2,⋅⋅⋅,an)的链式存储结构。
单链表:链表的每个结点只包含一个指针域的链表。
存取方式
整个链表的存取必须从头指针开始,头指针指示链表中的第一个结点(即第一个数据元素的存储映像)的存储位置。同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为“空”(NULL)。
逻辑关系
线性链表为非顺序映像或链式映像
解释:用线性链表表示线性表时,数据元素之间的逻辑关系是由结点中的指针指示的。换句话说,指针为数据元素之间的逻辑关系的映像,则逻辑上相邻的2个数据元素其存储的物理位置不要求相邻)。
图示方法
头节点:有时,我们在单链表的第一个结点之前附设一个结点,称之为头结点。头结点的数据域可以不存储任何信息,也可存储如线性表的长度等类的附加信息,头结点的指针与存储指向第一个结点的指针(即第一个元素结点的存储位置)。
通常我们把链表画成用箭头相链接的结点的序列,结点之间的箭头表示链表中的指针。
如果单链表为空呢?图示如下:
单链表的头指针指向头结点。如果线性表为空表,则头结点的指针域为“空”。
非随机存取
在单链表中,取得第i个数据元素必须从头指针出发寻找,因此,单链表是非随机存取的存储结构。
回想顺序表,将两者进行对比。
单链表的操作
说到操作,自然就少不了malloc和free函数了,因为是否了解对于我们学习相关操作并不影响,所以我把它放在文末的《附》里面。有需要的朋友可以自行滑动滚轮哦。
在已知链表中元素插入或删除的确切位置的情况下,在链表中插入或删除一个节点时,仅需修改指针而不需要移动元素。
插入和删除算法的时间复杂度均为 O ( n ) O(n) O(n)。(在第i个结点之前插入一个新结点或删除第i个结点,都必须首先找到第i-1个结点)
单链表和顺序存储结构不同,它是一种动态结构。
整个可用存储空间可为多个链表共同享用,因此链表占用的空间不需预先分配划定,而是可以由系统应需求及时生成。因此建立线性表的链式存储结构过程就是一个动态生成链表的过程(即从空表的初始状态起,依次建立各元素节点,并逐个插入链表)。
从表尾到表头逆向建立单链表的算法,其时间复杂度为O(n)。
如何将两个有序链表并为一个有序链表:不需要另建新表的结点空间,只需将原来的两个链表中的结点之间的关系解除,重新按元素值非递减的关系将所有结点链接成一个链表即可。(提示:设立3个指针)
静态链表
用一维数组来描述线性链表,这种描述方法便于在不设指针类型的高级程序设计语言中使用链表结构。
数组的一个分量表示一个结点,同时用游标(指示器cur)代替指针指示结点在数组中的相对位置。数组的第0分量可看成头结点,其指针域指示链表中的第一个结点。
具有链式存储结构的主要优点:需要预先分配一个较大的空间,但在做线性表的插入和删除操作时不需移动元素,仅需修改指针。
为什么是主要优点呢?对于单链表来说,每个结点的空间只有需要的时候才会申请,对空间的利用率更高。
为了辨明数组中的哪些分量未被使用,解决的办法是将所有未被使用过以及被删除的分量用游标链成一个备用链表,每当进行插入时便可从备用链表上取得第一个结点作为待插入的新结点;反之,在删除时将从链表中删除下来的结点链接到备用链表上。
循环链表
循环链表的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。由此,从表中任意结点出发均可找到表中其他结点。
类似的,还可以有多重链的循环链表。
循环链表的操作和线性链表基本一致,差别仅在于算法中的循环条件不是p
或p->next
是否为空,而是他们是否等于头指针。
但有时,若在循环链表中设立尾指针而不设头指,可使某些操作简化
例如将2个线性表合并成一个表时,仅需将一个表的表尾和另一个表的表头相接,这个操作仅需改变2个指针的值即可,运算时间为 O ( 1 ) O(1) O(1).
双向链表
我们之前讨论的链式存储结构的结点中只有一个指示直接后继的指针域,因此,从某个节点出发只能顺指针往后寻查其他节点。若要查询节点的直接前驱,只能从表头指针出发。为了克服单链表这种单向性的缺点,可以利用双向链表。
双向链表的结点中有两个指针域,其一指向直接后继,另一指向直接前驱。
和单链的循环表类似,双向链表也可以有循环表。
附
这里介绍c语言中的两个标准函数malloc
和free
。
通常,在设有“指针”数据类型的高级语言中均存在与其相应的过程或函数。
假设p和q是LinkList
型的变量,则执行p = (LinkList)malloc(sizeof(LNode))
的作用是由系统生成一个LNode
型的结点,同时将该结点的起始位置赋给指针变量;
而free(q)
的作用是由系统回收一个Lnode
型的结点,回收后的空间可以备用再次生成结点时用。
想看博主whywait的更多同系列查漏补缺必看文?点击传送门,点击关注不迷路哦
内容参考:《数据结构 (C语言版)》 严蔚敏、吴伟民