链表(Linked List)是 Java 中一种重要的数据结构,广泛应用于各种场景中。以下是关于链表的全面介绍,包括基本概念、实现方式、常用操作、优缺点以及实际应用。
1. 链表的基本概念
链表是一种线性数据结构,由一系列节点(Node)组成,每个节点包含两部分:
-
数据域:存储数据。
-
指针域:指向下一个节点的引用。
链表的特点:
-
动态内存分配:链表的大小可以动态增长或缩小。
-
非连续存储:节点在内存中不一定是连续存储的。
2. 链表的类型
(1)单链表(Singly Linked List)
-
每个节点只有一个指针,指向下一个节点。
-
最后一个节点的指针指向
null。 -
示例:
class Node { int data; Node next; Node(int data) { this.data = data; this.next = null; } }
(2)双链表(Doubly Linked List)
-
每个节点有两个指针,分别指向前一个节点和后一个节点。
-
示例:
class Node { int data; Node prev; Node next; Node(int data) { this.data = data; this.prev = null; this.next = null; } }
(3)循环链表(Circular Linked List)
-
单链表或双链表的变种,最后一个节点的指针指向头节点。
-
示例:
class Node { int data; Node next; Node(int data) { this.data = data; this.next = this; // 指向自身 } }
3. LinkedList 的常用方法
以下是 LinkedList 的常用方法及其示例:
(1)添加元素
| 方法 | 描述 |
|---|---|
add(E e) | 在链表末尾添加元素。 |
add(int index, E element) | 在指定位置插入元素。 |
addFirst(E e) | 在链表头部添加元素。 |
addLast(E e) | 在链表尾部添加元素。 |
示例:
LinkedList<String> list = new LinkedList<>();
list.add("Apple"); // [Apple]
list.addFirst("Banana"); // [Banana, Apple]
list.addLast("Cherry"); // [Banana, Apple, Cherry]
list.add(1, "Orange"); // [Banana, Orange, Apple, Cherry]
(2)删除元素
| 方法 | 描述 |
|---|---|
remove() | 删除并返回链表头部元素。 |
remove(int index) | 删除指定位置的元素。 |
remove(Object o) | 删除第一个匹配的元素。 |
removeFirst() | 删除并返回链表头部元素。 |
removeLast() | 删除并返回链表尾部元素。 |
示例:
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "D"));
list.remove(); // 删除 "A",剩余 [B, C, D]
list.remove(1); // 删除 "C",剩余 [B, D]
list.remove("B"); // 删除 "B",剩余 [D]
list.removeLast(); // 删除 "D",剩余 []
(3)获取元素
| 方法 | 描述 |
|---|---|
get(int index) | 返回指定位置的元素。 |
getFirst() | 返回链表头部元素。 |
getLast() | 返回链表尾部元素。 |
示例:
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));
System.out.println(list.get(1)); // 输出: B
System.out.println(list.getFirst()); // 输出: A
System.out.println(list.getLast()); // 输出: C
(4)修改元素
| 方法 | 描述 |
|---|---|
set(int index, E element) | 修改指定位置的元素。 |
示例:
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));
list.set(1, "X"); // [A, X, C]
(5)遍历链表
| 方法 | 描述 |
|---|---|
iterator() | 返回链表的迭代器。 |
listIterator() | 返回链表的列表迭代器。 |
示例:
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C"));
// 使用迭代器遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
// 使用增强 for 循环遍历
for (String item : list) {
System.out.print(item + " ");
}
(6)其他常用方法
| 方法 | 描述 |
|---|---|
size() | 返回链表的元素个数。 |
clear() | 清空链表。 |
contains(Object o) | 判断链表是否包含指定元素。 |
indexOf(Object o) | 返回指定元素第一次出现的索引。 |
lastIndexOf(Object o) | 返回指定元素最后一次出现的索引。 |
示例:
LinkedList<String> list = new LinkedList<>(Arrays.asList("A", "B", "C", "B"));
System.out.println(list.size()); // 输出: 4
System.out.println(list.contains("B")); // 输出: true
System.out.println(list.indexOf("B")); // 输出: 1
System.out.println(list.lastIndexOf("B")); // 输出: 3
list.clear(); // 清空链表
4. LinkedList 作为队列和栈
LinkedList 实现了 Deque 接口,因此可以作为队列或栈使用。
(1)作为队列
| 方法 | 描述 |
|---|---|
offer(E e) | 在队列尾部添加元素。 |
poll() | 删除并返回队列头部元素。 |
peek() | 返回队列头部元素(不删除)。 |
示例:
LinkedList<String> queue = new LinkedList<>();
queue.offer("A"); // [A]
queue.offer("B"); // [A, B]
System.out.println(queue.poll()); // 输出: A,剩余 [B]
System.out.println(queue.peek()); // 输出: B,剩余 [B]
(2)作为栈
| 方法 | 描述 |
|---|---|
push(E e) | 在栈顶添加元素。 |
pop() | 删除并返回栈顶元素。 |
示例:
LinkedList<String> stack = new LinkedList<>();
stack.push("A"); // [A]
stack.push("B"); // [B, A]
System.out.println(stack.pop()); // 输出: B,剩余 [A]
5. 性能分析
| 操作 | 时间复杂度 |
|---|---|
| 插入/删除(头部或尾部) | O(1) |
| 插入/删除(中间位置) | O(n) |
| 随机访问 | O(n) |
| 遍历 | O(n) |
4. 链表的常用操作
(1)插入
-
头插法:在链表头部插入节点。
-
尾插法:在链表尾部插入节点。
-
中间插入:在指定位置插入节点。
(2)删除
-
删除头节点。
-
删除尾节点。
-
删除指定值的节点。
(3)查找
-
按值查找:查找链表中是否存在某个值。
-
按索引查找:查找链表中第
k个节点。
(4)遍历
-
从头到尾遍历:输出链表的所有节点值。
6. 链表的优缺点
优点
-
动态大小:链表的大小可以动态调整。
-
插入/删除高效:在已知节点位置的情况下,插入和删除操作的时间复杂度为 O(1)。
-
内存利用率高:节点在内存中非连续存储,适合内存碎片较多的场景。
缺点
-
随机访问效率低:访问第
k个节点需要从头遍历,时间复杂度为 O(n)。 -
额外内存开销:每个节点需要存储指针,占用额外内存。
7. 链表的应用场景
-
实现栈和队列:
-
栈:使用单链表实现,头插法和头删法。
-
队列:使用双链表实现,尾插法和头删法。
-
-
LRU 缓存:
-
使用双向链表和哈希表实现最近最少使用(LRU)缓存淘汰算法。
-
-
多项式运算:
-
使用链表表示多项式,实现加法、乘法等运算。
-
-
图的邻接表表示:
-
使用链表存储图的邻接表。
-
8. 常见面试题
-
反转链表:
-
将单链表或双链表反转。
-
示例代码:
public Node reverseList(Node head) { Node prev = null; Node current = head; while (current != null) { Node next = current.next; current.next = prev; prev = current; current = next; } return prev; }
-
-
检测环:
-
判断链表中是否存在环。
-
示例代码(快慢指针法):
public boolean hasCycle(Node head) { if (head == null) return false; Node slow = head; Node fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) return true; } return false; }
-
-
合并两个有序链表:
-
将两个有序链表合并为一个有序链表。
-
示例代码:
public Node mergeTwoLists(Node l1, Node l2) { Node dummy = new Node(0); Node current = dummy; while (l1 != null && l2 != null) { if (l1.data < l2.data) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = (l1 != null) ? l1 : l2; return dummy.next; }
-
9. 总结
-
链表是一种灵活的数据结构,适合动态插入和删除操作。
-
Java 提供了
LinkedList类,可以直接使用。 -
链表的常见操作包括插入、删除、查找和遍历。
-
链表在栈、队列、LRU 缓存等场景中有广泛应用。

1286

被折叠的 条评论
为什么被折叠?



