一、链表的基本概念
链表(Linked List)是一种常见的线性数据结构,它由一系列节点(Node)组成,每个节点包含数据域和一个指向下一个节点的引用(指针)。与数组相比,链表的优势在于插入和删除操作效率高(时间复杂度 O (1)),但随机访问效率低(需从头遍历,时间复杂度 O (n))。
链表的分类
- 单向链表:每个节点包含数据和指向下一个节点的引用,最后一个节点的引用为 null。
- 双向链表:每个节点包含数据、指向前一个节点的引用和指向后一个节点的引用,头节点的前驱引用和尾节点的后继引用为 null。
- 循环链表:与单向链表类似,但最后一个节点的引用指向头节点,形成一个环。
二、Java 中链表的实现
单向链表的实现
在 Java 中,我们可以通过定义节点类和链表类来实现单向链表:
// 节点类
class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
// 单向链表类
class LinkedList {
private Node head;
private int size;
public LinkedList() {
head = null;
size = 0;
}
// 在链表尾部添加元素
public void add(int data) {
Node newNode = new Node(data);
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
size++;
}
// 删除指定位置的元素
public void remove(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
if (index == 0) {
head = head.next;
} else {
Node prev = getNode(index - 1);
prev.next = prev.next.next;
}
size--;
}
// 获取指定位置的节点
private Node getNode(int index) {
Node current = head;
for (int i = 0; i < index; i++) {
current = current.next;
}
return current;
}
// 其他方法...
}
Java 内置链表类
Java 集合框架中提供了LinkedList
类,它实现了双向链表结构,并实现了List
和Deque
接口,因此可以作为列表、队列或栈使用:
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
// 创建LinkedList
LinkedList<Integer> list = new LinkedList<>();
// 添加元素
list.add(1); // 添加到尾部
list.addFirst(0); // 添加到头部
list.addLast(2); // 添加到尾部
// 访问元素
int first = list.getFirst(); // 获取头部元素
int last = list.getLast(); // 获取尾部元素
// 删除元素
list.removeFirst(); // 删除头部元素
list.removeLast(); // 删除尾部元素
// 遍历链表
for (int num : list) {
System.out.println(num);
}
}
}
三、链表的常见操作及实现
1. 链表反转
链表反转是面试中的经典问题,可通过迭代或递归方法实现:
// 迭代法反转链表
public Node reverseList(Node head) {
Node prev = null;
Node curr = head;
while (curr != null) {
Node nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
// 递归法反转链表
public Node reverseListRecursive(Node head) {
if (head == null || head.next == null) return head;
Node p = reverseListRecursive(head.next);
head.next.next = head;
head.next = null;
return p;
}
2. 链表中环的检测
使用快慢指针法检测链表中是否存在环:
public boolean hasCycle(Node head) {
if (head == null) return false;
Node slow = head;
Node fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) return false;
slow = slow.next;
fast = fast.next.next;
}
return true;
}
3. 合并两个有序链表
递归合并两个有序链表:
public Node mergeTwoLists(Node l1, Node l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.data < l2.data) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
四、链表的应用场景
- 实现栈和队列:链表的插入和删除操作效率高,适合实现栈和队列。
- 内存管理:操作系统的内存分配常使用链表结构。
- 哈希表的冲突处理:哈希表解决冲突时,常使用链表存储相同哈希值的元素。
- 文件系统:文件系统的目录结构通常采用链表实现。
五、链表与数组的对比
特性 | 链表 | 数组 |
---|---|---|
存储方式 | 动态分配内存,不连续 | 连续内存空间 |
插入 / 删除效率 | O (1)(已知位置) | O (n)(需移动元素) |
随机访问效率 | O (n)(需遍历链表) | O (1)(通过下标直接访问) |
内存占用 | 每个节点需额外存储引用 | 无需额外存储引用 |
六、常见思考题
- 如何判断链表是否有环?
- 如何找到链表的中间节点?
- 如何反转一个链表?
- 如何合并两个有序链表?
- 如何删除链表的倒数第 N 个节点?
掌握链表的基本原理和常见操作,对于理解数据结构和解决算法问题至关重要。通过不断练习和深入思考,你将能够灵活运用链表解决各种实际问题。