【链表的分类】
1.单链表
介绍单链表前,首先我们要知道以下概念:
- 节点:链表的基本单位,包含数据和后继指针。
- 头结点:链表的第一个节点,用来记录链表的基地址。
- 尾结点:链表的最后一个节点,指针指向一个空地址。
- 后继指针:指向下一个节点的指针。
查找、插入、删除操作时间复杂度分析:
- 查找:O(n) —— 查找操作需要从头结点开始,依次遍历直到找到目标节点或达到尾节点。所以,最坏情况下,你可能需要遍历整个链表。因此,时间复杂度为 O(n)。
- 插入:O(1) —— 如果你知道插入的位置(你已经找到了前一个节点和后一个节点),则插入操作只需改变一些指针,所以时间复杂度为 O(1)。
- 删除:O(1) —— 与插入相似,如果你知道要删除的节点位置,只需要改变一些指针。因此,时间复杂度为 O(1)。
2.循环链表:循环链表是单链表的变种,其尾节点指向头结点。
循环链表的查找、插入、删除操作时间复杂度分析同单链表。有兴趣的小伙伴可以看看著名的约瑟夫问题(Josephus Problem)。
3.双向链表:双向链表这里又引出了一个新的概念:前驱指针(指向前一个节点)。双向链表的每个节点除了有后继指针外,还有一个前驱指针指向前一个节点。
由于每个双向链表的节点有两个指针(前驱和后继),而单链表的节点只有一个指针,所以双向链表的每个节点占用的额外空间是单链表的两倍。
尽管双向链表比较费内存,但是,在实际的软件开发中,双向链表却应用的更加广泛。双向链表相对于单向链表的优势我认为有以下两点:
① 双向遍历:由于双向链表每个节点都有指向前一个节点的前驱指针和指向后一个节点的后继指针,因此我们可以方便地向前或向后遍历链表。而在单链表中,我们只能从头到尾进行遍历。
②在任意位置插入或删除节点更高效:在双向链表中,如果我们要删除给定指针指向的结点,我们可以在O(1)时间内插入或删除该节点,因为我们可以快速访问前一个节点。而在单链表中,删除特定节点(链表的末尾除外)通常需要从头开始遍历以找到该节点的前一个节点,时间复杂度为O(n)。
所以,双向链表在插入和删除的操作上更加高效。在空间与时间的权衡,通常我们为了实现时间效率,会牺牲一些空间(额外的前驱指针)。
【数组与链表的区别】
连续性 | 大小 | 查询 | 插入和删除 | 空间效率 | |
数组 | 数组是在内存中连续存储的 | 固定的:定义之后不能动态地增加或减少 | O(1) | 最坏的情况下会需要O(n)的时间 | 具有较好的空间局部性 |
链表 | 链表中的元素不一定是连续存储的 | 动态数据结构:可以根据需要增加或减少 | 最坏情况下的访问时间是O(n) | O(1) | 需要额外的空间来存储指针 |
【数组与链表在代码中的实际应用】
数组:
- 用于实现数据结构如动态数组(ArrayList in Java)。
- 存储有固定数量的同类型元素,例如矩阵操作或像像素数据这样的大数据集。
- 用于算法,需要基于索引的快速访问,如二分查找。
链表:
- 用于实现数据结构如队列(Queue)和双端队列(Deque)。
- 在文本编辑器中用于撤销功能(每次编辑都可以视为链表中的一个节点,撤销操作就是移动到前一个节点)。
- 用于存储有不确定数量的元素,例如在浏览器历史中记录访问的网页。