链表
链表是一种常见的基础数据结构,是一种线性表。链表中的每个元素都存储有下个元素的指针,通过首尾相接的方式将所有元素有序的串在一起。链表并不要求所有元素在内存中的位置是相邻的,所有元素都可以分散地存储在内存中。
链表的分类
- 单向链表:单向链表只有一个方向,一般地,每个元素都只有下个元素的指针。
- 双向链表
- 循环链表
- 块状链表
- 跳表
链表的效率
- 插入元素:插入元素的时间复杂度是 。
- 查找元素:查找元素的时间复杂度是 。
- 随机访问:随机访问的时间复杂度是 。
注意:插入元素的时间复杂度是 ,这是因为在计算这个时间复杂度的时候并没有计算查找元素 的时间。如向 后插入一个元素 。这时候只需要将 的下一个节点的指针指向 ,的下一个节点的指针指向 。
这个两个操作的时间复杂度都是常数级的。因此时间复杂度是 。但是请注意找到 这个元素的时间复杂度可是 。
在已获取到 的情况下,向 后插入一个元素的时间复杂度是 。在未知 的情况下,向 后插入一个元素的时间复杂度是 。
- 对于尾插法的时间复杂度:如果保存了尾指针,那么向尾部添加元素的时间复杂度是,否则是 。
- 头插法的时间复杂度是
术语及概念
在链表中为了表示每个数据 与 之间的逻辑关系,对于数据元素 来说,除了要存储其本身的信息外,还需要存储一个指向 的指针。
存储数据元素信息的域称为数据域,存储直接后继指针或直接前驱指针的域称为指针域。这两部分信息组成数据元素 的存储映像,称为节点(Node),有的地方也叫结点。后文中我们统一用节点。
头节点(Head Node):头节点是链表的起始节点,它没有前驱节点。在单向链表中,头节点通常用于表示链表的开始。
尾节点(Tail Node):尾节点是链表的最后一个节点,它没有后继节点。在单向链表中,尾节点通常用于表示链表的结束。
前驱节点(Previous Node):前驱节点是指链表中当前节点的前一个节点。在单向链表中,每个节点只有一个指向下一个节点的指针,因此没有前驱节点的概念。
后继节点(Next Node):后继节点是指链表中当前节点的下一个节点。在单向链表中,每个节点只有一个指向下一个节点的指针,因此后继节点是指向当前节点的下一个节点。
链表长度(Linked List Length):链表长度是指链表中节点的数量。在单向链表中,可以通过遍历链表来计算链表的长度。
链表的优缺点
优点:
- 可以灵活的向链表中插入或删除元素,理论上只要内存足够大,链表的长度是不受限 制的。可以根据需求任意伸缩。
- 不需要连续的存储单元,与数组不同的是链表不需要连续的存储单元,因为每个节点都有下一个节点的指针,因此节点可以存在未被占用的内存中的任何位置。
缺点:
- 随机访问的时间复杂度为 ,因为需要遍历链表。
- 存储空间增大,相比于数组,由于每个节点都存储有指向下一个节点的指针,因此需要更多的存储空间。
- 排序实现复杂,由于链表的方向单一,交换两个节点的位置实现起来就比较复杂。
关于链表的优缺点可以在后续的文章中体会 。
📄️ 单向链表
单向链表的特点是链表的链接方向是单向的,对链表的访问要通过从头部开始,依序往下读取。
📄️ 双向链表
单链表结点中只有一个指向其后继的指针,使得单链表只能从头节点依次顺序地向后遍历。要访问某个结点的前驱节点(插入、删除操作时),只能从头开始遍历, 访问后继节点的时间复杂度为 $O(1)$,访问前驱节点的时间复杂度为 $O(n)$。
📄️ 循环链表
在循环链表中最后一个节点指向第一个节点,形成一个环。循环链表可以使用单向链表实现也可以使用双向链表实现。
📄️ 跳表
跳表(Skip List)是一种随机化的数据结构,它在链表的基础上引入多级索引,从而实现类似于平衡树的对数时间复杂度的搜索、插入和删除操作。跳表由 William Pugh 于1990年发明,用于替代平衡树。跳表主要用于有序数据序列,