前言
想要刷链表的算法,那必定得先知道它是什么,它的基本操作。
简介
各种数据结构,不管是队列,栈等线性数据结构还是树,图的等非线性数据结构,从根本上底层都是数组和链表。不管你用的是数组还是链表,用的都是计算机内存,物理内存是一个个大小相同的内存单元构成的。而数组和链表虽然用的都是物理内存,都是两者在对物理的使用上是非常不一样的,如图:
不难看出,数组和链表只是使用物理内存的两种方式。
数组是连续的内存空间,通常每一个单位的大小也是固定的,因此可以按下标随机访问。而链表则不一定连续,因此其查找只能依靠别的方式,一般我们是通过一个叫 next 指针来遍历查找。链表其实就是一个结构体。 比如一个可能的单链表的定义可以是:
interface ListNode<T> {
data: T;
next: ListNode<T>;
}
data 是数据域,存放数据,next 是一个指向下一个节点的指针。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。从上面的物理结构图可以看出数组是一块连续的空间,数组的每一项都是紧密相连的,所以适合适合查找,而链表由于元素的增删不会影响到整个链表结构,只是将前后元素的指针方向进行改变,因此更适合增删。
链表的基本操作
要想写出链表的题目, 熟悉链表的各种基本操作和复杂度是必须的。
插入
插入只需要考虑要插入位置前驱节点和后继节点即可,其他节点不受影响,因此在给定指针的情况下插入的操作时间复杂度为O(1)。
伪代码:
temp = 待插入位置的前驱节点.next
待插入位置的前驱节点.next = 待插入指针
待插入指针.next = temp
如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 O(N)。
删除
如果没有给定指针,我们需要先遍历找到节点,因此最坏情况下时间复杂度为 O(N)。
伪代码:
待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next
遍历
伪代码:
当前指针 = 头指针
while 当前节点不为空 {
print(当前节点)
当前指针 = 当前指针.next
}
链表和数组到底有多大的差异?
说到底,对于我们做题来说,二者的差异通常就只是细微的操作差异。
数组的遍历:
for(int i = 0; i < arr.size();i++) {
print(arr[i])
}
链表的遍历:
for (ListNode cur = head; cur != null; cur