我们学习另一种数据结构 —— 链表。
与数组相似,链表也是一种线性数据结构。这里有一个例子:
正如我们所看到的,链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。
链表有两种类型:单链表和双链表。上面给出的例子是一个单链表,这里有一个双链表的例子:
温故而知新,学完这一模块,我们会:
1.了解单链表和双链表的结构;
2.在单链表或双链表中实现遍历、插入和删除;
3.分析在单链表或双链表中的各种操作的复杂度;
4.在链表中使用双指针技巧(快指针慢指针技巧);
5.解决一些经典问题,例如反转链表;
6.分析你设计的算法的复杂度;
7.积累设计和调试的经验。
- 单链表
单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来。、
下面是一个单链表的例子:
蓝色箭头显示单个链接列表中的结点是如何组合在一起的。
下面代码是单链表中结点的典型定义:
// Definition for singly-linked list.
public class SinglyListNode {
int val;
SinglyListNode next;
SinglyListNode(int x) { val = x; }
}
与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。
例如,在上面的示例中,头结点是 23。访问第 3 个结点的唯一方法是使用头结点中的“next”字段到达第 2 个结点(结点 6); 然后使用结点 6 的“next”字段,我们能够访问第 3 个结点。
你可能想知道为什么链表很有用,尽管它在通过索引访问数据时(与数组相比)具有如此糟糕的性能。 在接下来的两篇文章中,我们将介绍插入和删除操作,你将了解到链表的好处。
之后,我们将为你提供练习设计自己的单链表。
如果我们想在给定的结点 prev 之后添加新值,我们应该:
1.使用给定值初始化新结点 cur;
2.将 cur 的“next”字段链接到 prev 的下一个结点 next;
3.将 prev 中的“next”字段链接到 cur 。
与数组不同,我们不需要将所有元素移动到插入元素之后。因此,您可以在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。
示例
让我们在第二个结点 6 之后插入一个新的值 9。
我们将首先初始化一个值为 9 的新结点。然后将结点 9 链接到结点 15。最后,将结点 6 链接到结点 9。
插入之后,我们的链表将如下所示:
在开头添加结点
众所周知,我们使用头结点来代表整个列表。
因此,在列表开头添加新节点时更新头结点 head 至关重要。
初始化一个新结点 cur;
将新结点链接到我们的原始头结点 head。
将 cur 指定为 head。
例如,让我们在列表的开头添加一个新结点 9。
1.我们初始化一个新结点 9 并将其链接到当前头结点 23。
2.指定结点 9 为新的头结点。