本节介绍线性表中的循环链表与双向链表,主要包括基本结构,主要特点以及适用场景三部分内容。
3.1 循环链表与双向链表
循环链表(Circular Linked List) 是另一种形式的链式存储结构。其特点是表中 最后一个结点的指针域指向头结点,整个链表形成一个环。
双向链表(Double Linked List) 指的是每个结点都具有两个指针域分别指向它的前驱结点和后继结点的链表。
3.2 单链表、循环列表与双向列表特点比较
链表名称 | 查找表头结点 | 查找表尾结点 | 查找结点 *p 的前驱结点 |
---|---|---|---|
带头结点的单链表L | L->next 时间复杂度O(1) | 从 L->next依次向后遍历 时间复杂度O(n) | 通过 p->next无法找到其前驱 |
带头结点仅设头指针L的 循环单链表 | L->next 时间复杂度O(1) | 从 L->next依次向后遍历 时间复杂度O(n) | 通过 p->next可以找到其前驱 时间复杂度O(n) |
带头结点仅设尾指针R的 循环单链表 | R->next 时间复杂度O(1) | R 时间复杂度O(1) | 通过 p->next可以找到其前驱 时间复杂度O(n) |
带头结点的双向 循环单链表L | L->next 时间复杂度O(1) | L->prior 时间复杂度O(1) | 通过 p->prior可以找到其前驱 时间复杂度O(1) |
这个地方需要画画图理解一下。比如不管是哪一种链表,查找表头结点方法都很简单 L − > n e x t L->next L−>next,所以时间复杂度为 O ( 1 ) O(1) O(1),因为四种链表都满足 L->next 就表示第一个结点;
而查找表尾结点同样容易理解,除了循环单链表,其他的都需要 O ( n ) O(n) O(n) 的时间复杂度;
查找结点 *p 的前驱结点这种情况下毫无疑问只有双向循环链表最合适,直接存储了前驱的指针地址,所以找到前驱的时间复杂度为 O(1)
3.3 适用场景
在3.2中提到过三种不同链表不同需求下的时间复杂度,这里我们可以归纳一下在什么情况下我们应当使用循环链表和双向链表。
“适用” 这个词必须跟 “需求” 紧密绑定在一起,所以这里我们得从需求的角度出发分析它们的适用场景。
3.3.1 单链表
各个数据元素之间具有明确的顺序关系,并且逆向关系的通讯需求较低时适合选择使用单链表。比如说考试的时候常常是坐前排的把试卷发给后面一排的,比如说我们的在存储小伙伴的电话号码的时候一般也是按顺序输入,很少有逆序输入的需求。
如果有这种逆序查找或通讯的需求,那么需要考虑这种需求多不多,如果经常发生后排的同学向前排的同学请教问题,那么如果是单链表的话就比较麻烦了,后排的同学必须把问题(也就是数据)取出来,然后从链表表头开始向后面寻找,找到自己前排的同学然后询问问题。
3.3.2 循环链表
循环链表是一种特殊类型的链表,它的最后一个节点指向第一个节点,形成了一个环形结构。与单向链表和双向链表相比,循环链表具有以下优点:
-
方便的循环操作:由于循环链表是一个环形结构,因此在处理需要循环遍历的问题时,使用循环链表比使用单向链表或双向链表更方便。
-
实现轮流访问:在某些场景中,需要轮流访问某个数据集中的元素,循环链表是一种非常有效的实现方式。例如,计算机操作系统的进程调度算法中,就可以使用循环链表来实现轮流访问各个进程。
-
优化内存使用:如果在某些场景中需要频繁地在链表的头尾进行插入和删除操作,使用循环链表比单向链表和双向链表更优,因为可以避免创建新的节点和释放已有节点的内存。
因此,循环链表在需要循环遍历链表、需要轮流访问数据集中的元素、需要频繁在链表头尾进行插入和删除操作的场景下是非常有用的。例如,游戏开发中的动画循环播放功能就可以使用循环链表来实现。
当最后一位元素需要访问链表中第一个元素时,或者当前结点 *p 不清楚自己的相对位置,并且需要从这个结点出发访问整个链表的时候,需要考虑使用循环链表。
比如我们小伙伴们围成一圈,玩 “数到 3 退出圈” 的游戏。那么这个时候我们并不清楚是谁开始数 “1” 的了,每次数到3的小伙伴离圈以后,都从新开始从 1 开始数,所以这个时候使用循环链表更加符合实际需求,操作起来也较为方便。
但对于有明确的 “终止元素” 的链表,不可使用循环链表以避免出现 “死循环” 的现象。
3.3.3 双向链表
双向链表是一种数据结构,它具有单向链表的所有特性,但是每个节点除了指向下一个节点的指针之外,还有指向前一个节点的指针。这使得双向链表具有许多优点,例如:
-
插入和删除操作:由于每个节点都有指向前一个节点的指针,因此在双向链表中插入或删除一个节点比单向链表更容易和更高效。例如,删除节点时,可以直接通过前一个节点的指针找到需要删除的节点,而无需遍历整个链表。
-
遍历操作:由于双向链表可以从前往后或从后往前遍历,因此在某些情况下,使用双向链表比单向链表更方便。例如,当需要在链表中查找某个节点的前一个节点时,双向链表比单向链表更快。
链表中相邻元素相互访问密切时,考虑使用双向链表。比如说我们在搜索两个地方之间的距离的时候,对应的数据应当是一个 “无向图”,也就是说,两个点之间都是相互的,A地点与B地点之间的距离,等于B地点与A地点的距离,相互前往都是可能的,所以我们考虑使用双向链表。
比如说你和你的同桌,两个人总是打打闹闹的,这节课他找你借个圆规,下节课你找他借个橡皮;这节课你抄他数学题课后作业,下节课你帮他补习英语完形填空…… 这种双向关系更加适合使用双向链表,通讯更加简单方便 —— 不至于常常需要从头结点开始查。
3.4 总结
本章主要总结一些面试官可能问到的问题或者是考试可能考到的问题,事实上理解清楚了基本上就差不多了,一定需要跟面试的老师(考研)或是面试官(工作)说清楚这个数据结构的主要特点,讲清楚以后一般就足够了,面试官没心思听太仔细的东西。
Smileyan
2023.04.29 23:41