Java 实例 – 删除链表中的元素
在 Java 的数据结构世界里,链表是一种非常基础但又极其重要的结构。它不像数组那样需要连续的内存空间,而是通过节点之间的指针连接,实现动态存储。当我们面对需要频繁增删操作的场景时,链表的优势就凸显出来了。而“删除链表中的元素”正是链表操作中最常见、最核心的实战之一。
这篇文章,我们就以一个真实开发场景为背景,一步步带你掌握 Java 实例 – 删除链表中的元素。无论是刚接触数据结构的初学者,还是有一定经验的中级开发者,都能从中学到实用技巧。
什么是链表?它的结构如何理解?
在深入删除操作之前,先让我们理清链表的基本构成。你可以把链表想象成一列火车:每一节车厢(节点)都包含两个部分——一个是乘客(数据),另一个是连接下一辆车厢的“接口”(指针)。
在 Java 中,我们通常用一个 Node 类来表示这个车厢:
public class ListNode {
int val; // 节点存储的值,比如 3、5、8
ListNode next; // 指向下一个节点的引用,如果为空,表示这是最后一节车厢
// 构造函数:创建一个节点并指定值
public ListNode(int val) {
this.val = val;
this.next = null; // 新建节点默认没有下一个节点
}
}
这个 ListNode 类就是链表的“砖块”。多个 ListNode 通过 next 指针连接起来,就形成了链表。
删除链表中的元素:核心思想
删除一个节点,本质上是“让前一个节点跳过它”,也就是修改指针的指向。这就像你在火车上想让某节车厢“消失”:你只需要把前一节车厢的连接线,直接接到下下节车厢上即可。
关键点:
- 不能直接“删除”节点,因为 Java 不支持手动释放内存。
- 你只能通过“断开连接”,让垃圾回收器自动回收。
举个例子:链表是 1 → 2 → 3 → 4,我们要删除值为 3 的节点。
步骤如下:
- 找到值为
3的节点(即node3)。 - 找到它的前一个节点(即
node2)。 - 将
node2.next指向node3.next(也就是4)。 - 这样
node3就从链表中“断开”了,后续无法访问。
删除指定值的节点(基础版)
下面是一个完整的 Java 实例,实现删除链表中所有等于给定值的节点。
public class LinkedListDeletion {
// 删除链表中所有等于 val 的节点
public ListNode removeElements(ListNode head, int val) {
// 1. 如果链表为空,直接返回 null
if (head == null) {
return null;
}
// 2. 创建一个虚拟头节点(哑节点),避免处理头节点的特殊情况
ListNode dummy = new ListNode(0);
dummy.next = head;
// 3. 用指针 current 指向虚拟头节点,用于遍历
ListNode current = dummy;
// 4. 遍历链表,直到 current.next 为 null
while (current.next != null) {
// 如果下一个节点的值等于目标值 val
if (current.next.val == val) {
// 将当前节点的 next 指向下一个节点的下一个节点
// 相当于“跳过”了当前要删除的节点
current.next = current.next.next;
} else {
// 如果不等于 val,继续移动指针
current = current.next;
}
}
// 5. 返回虚拟头节点的下一个节点,即真正的头节点
return dummy.next;
}
}
代码注释详解:
dummy是一个“虚拟头节点”,它的作用是统一处理头节点删除的情况。比如,如果原始头节点就是val,没有虚拟头节点,你会很难处理。current.next.val == val:判断下一个节点是否是目标节点。current.next = current.next.next:这才是“删除”的关键操作——跳过目标节点。- 最后返回
dummy.next,因为dummy本身不是真实数据节点。
特殊情况处理:删除头节点
如果你直接删除链表的第一个节点,没有虚拟头节点的话,代码会变得非常复杂。例如:
// ❌ 危险写法(不推荐)
public ListNode removeFirst(ListNode head, int val) {
// 如果头节点就是要删除的值
if (head.val == val) {
return head.next; // 直接返回下一个节点
}
// 否则遍历后续节点
ListNode current = head;
while (current.next != null) {
if (current.next.val == val) {
current.next = current.next.next;
break;
}
current = current.next;
}
return head;
}
这个方法虽然能工作,但需要额外判断头节点。而使用虚拟头节点的版本,所有情况都统一处理,代码更简洁、更安全。
删除链表中第 k 个元素(按位置删除)
有时候我们不是按值删除,而是按位置删除,比如“删除第 3 个节点”。
public ListNode removeKthFromEnd(ListNode head, int k) {
// 1. 创建虚拟头节点,统一处理
ListNode dummy = new ListNode(0);
dummy.next = head;
// 2. 定义两个指针:fast 和 slow
ListNode fast = dummy;
ListNode slow = dummy;
// 3. 让 fast 指针先走 k 步
for (int i = 0; i < k; i++) {
fast = fast.next;
}
// 4. fast 和 slow 同时移动,直到 fast 到达末尾
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 5. 此时 slow 指向要删除节点的前一个节点
slow.next = slow.next.next;
// 6. 返回真实头节点
return dummy.next;
}
核心思想:快慢指针法。快指针先走 k 步,然后两个指针一起走,当快指针到达末尾时,慢指针正好在倒数第 k 个节点的前一个位置。
这个技巧在很多链表题目中都非常实用,比如“反转链表”、“环形链表检测”等。
链表删除操作的常见错误与调试建议
在实际编码中,初学者容易犯几个典型错误:
| 常见错误 | 问题原因 | 正确做法 |
|---|---|---|
| 忘记处理空链表 | head 为 null 时直接访问 head.next 会抛空指针异常 | 入口先判断 head == null |
| 没有使用虚拟头节点 | 删除头节点时逻辑混乱 | 使用 dummy 节点统一处理 |
| 指针移动顺序错误 | 先移动 current 再操作 next,导致丢失节点 | 先操作 next,再移动指针 |
| 循环条件写错 | while (current != null) 会漏掉最后一个节点 | 应该用 while (current.next != null) |
建议在调试时打印链表状态,比如:
public void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.val + " -> ");
current = current.next;
}
System.out.println("null");
}
实际应用案例:学生名单管理
假设你在开发一个学生管理系统,每个学生用一个节点表示,现在需要根据学号删除某位学生。
public class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
// 链表中删除指定 id 的学生
public StudentNode removeStudent(StudentNode head, int id) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode current = dummy;
while (current.next != null) {
if (current.next.val == id) { // 假设 val 存的是 id
current.next = current.next.next;
break;
}
current = current.next;
}
return dummy.next;
}
这个例子展示了 Java 实例 – 删除链表中的元素在实际业务中的价值:灵活、高效、可扩展。
总结与学习建议
链表的删除操作看似简单,实则蕴含了指针思维、边界处理、虚拟节点等重要编程理念。掌握“删除链表中的元素”这一基本操作,是迈向高级数据结构的必经之路。
建议你:
- 动手实现一遍代码,不要只看。
- 用测试用例验证各种边界情况:空链表、删除头节点、删除尾节点、删除中间节点。
- 尝试用递归方式实现删除操作,提升思维深度。
链表是 Java 编程中非常重要的一环,而“删除链表中的元素”作为其核心操作,值得你反复练习。相信通过这篇深入浅出的讲解,你已经掌握了这项关键技能。继续加油,下一个算法高手就是你!