1. LinkedList简介
LinkedList(链表)是一种线性数据结构,与数组不同,它不要求元素在内存中连续存储。链表由一系列节点组成,每个节点包含数据和指向下一个节点的引用(指针)。
基本特性
动态大小:链表可以动态增长和缩小,不需要预先指定大小
内存效率:只在需要时分配内存,不像数组需要连续内存空间
插入/删除效率:在已知位置插入或删除节点的复杂度为O(1)
访问效率:随机访问效率低(O(n)),必须从头开始遍历
链表类型
1. 单向链表(Singly Linked List)
每个节点包含:
数据部分
指向下一个节点的指针(最后一个节点指向null)
代码实现:
private static class Node<E>{
E item;
Node<E> next;
Node(E element, Node<E> next){
this.item = element;
this.next = next;
}
}
链表结构:
查询节点:
- 只有在查询头节点的时候不需要遍历链表,时间复杂度是O(1)。
- 查询其他结点需要遍历链表,时间复杂度是O(n)。
插入删除节点:
- 只有在添加和删除头节点的时候不需要遍历链表,时间复杂度是O(1)。
- 添加或删除其他结点需要遍历链表找到对应节点后,才能完成新增或删除节点,时间复杂度是O(n)。
2. 双向链表(Doubly Linked List)
每个节点包含:
数据部分
指向前一个节点的指针
指向下一个节点的指针
代码实现:
private static class Node<E>{
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next){
this.item = element;
this.next = next;
this.prev = prev;
}
}
查询节点:
- 查询头尾结点的时间复杂度是O(1)
- 平均的查询时间复杂度是O(n)
- 给定节点找前驱节点的时间复杂度为O(1)
增删节点:
- 头尾结点增删的时间复杂度为O(1)
- 其他部分结点增删的时间复杂度是 O(n)
- 给定节点增删的时间复杂度为O(1)
3. 循环链表(Circular Linked List)
单向循环链表:最后一个节点指向第一个节点
双向循环链表:第一个节点的prev指向最后一个节点,最后一个节点的next指向第一个节点
基本操作复杂度
操作 | 时间复杂度 | 备注 |
---|---|---|
访问元素 | O(n) | 必须从头节点开始遍历 |
插入/删除头节点 | O(1) | 直接操作头指针 |
插入/删除尾节点 | O(1) | 双向链表;单向链表为O(n) |
在中间插入 | O(n) | 需要先找到位置 |
搜索元素 | O(n) | 需要遍历 |
链表 vs 数组
特性 | 链表 | 数组 |
---|---|---|
内存分配 | 动态,不连续 | 静态,连续 |
大小 | 可动态增长 | 固定大小 |
访问元素 | O(n) | O(1) |
插入/删除 | O(1)(已知位置) | O(n)(需要移动元素) |
内存开销 | 每个节点需要额外存储指针 | 只有数据 |
2. 问题总结
2.1 单向链表和双向链表的区别是什么?
- 单向链表只有一个方向,结点只有一个后继指针 next。
- 双向链表它支持两个方向,每个结点不止有一个后继指针next指的结点,还有一个前驱指针prev指向前面的结点。
破旧的船帆总有一天会到达彼岸
鲜红的花蕊绽放在蓝色的海洋边缘
未来的憧憬随时间的流逝渐行渐远
长大的我们现身处低谷却仰望顶端