链表实现:双链表与单链表的完整 Java 实现

raw/articles/2026/05/链表实现

侧重:链表的工程级实现(虚拟头尾节点、头尾引用、内存管理)、完整的增删查改操作

核心实现技巧

1. 同时持有头尾节点引用

在实际开发中,双链表通常会同时持有头尾节点的引用,以优化尾部操作性能:

操作 无尾引用 有尾引用
尾部添加 O(N) 需遍历 O(1)
尾部删除 O(N) 需找前驱 O(1)(单链表仍需遍历前驱)

单链表的权衡

  • 持有尾引用可以 O(1) 添加,但删除时仍需遍历找前驱
  • 删除尾节点时,正好需要遍历到倒数第二个节点,可以顺便更新尾引用

2. 虚拟头尾节点(Dummy Head/Tail)

这是本文的核心技巧,在 链表基础 中已提及虚拟头结点,本文扩展到虚拟头尾节点

原理

  • 创建链表时就创建虚拟头节点(dummyHead)和虚拟尾节点(dummyTail)
  • 无论链表是否为空,这两个节点都存在
  • 避免空指针问题,简化边界条件处理

示例

空链表:dummyHead <-> dummyTail

添加 1,2,3:dummyHead <-> 1 <-> 2 <-> 3 <-> dummyTail

优点

  • 统一头部、尾部、中间插入/删除的逻辑
  • 无需单独处理空链表边界情况
  • 代码更简洁

注意

  • 虚拟节点是内部实现,对外不可见(如 get(index) 从真实节点开始计算)
  • 虚拟尾节点对双链表有用,对单链表作用不大

3. 内存泄露问题

本文澄清了链表删除中的内存管理误区:

Java 场景

// 删除单链表头结点
head = head.next;  // 原头结点 1 的 next 仍指向 2

为什么没问题?

  • Java 垃圾回收判断对象是否被别人引用,不关心对象是否引用别人
  • 原头结点 1 虽然还指向 2,但没有其他指针引用它,所以会被 GC 回收

最佳实践

  • 删除节点时,仍建议把被删除节点的指针置为 null
  • 不会有性能代价,还可能避免潜在问题

代码实现要点

双链表实现(推荐)

节点结构

private static class Node<E> {
    E val;
    Node<E> next;
    Node<E> prev;
}

关键设计

  • 构造函数初始化虚拟头尾节点,互相指向对方
  • addLast:在 tail.prevtail 之间插入
  • addFirst:在 headhead.next 之间插入
  • remove:直接修改前驱和后继的指针

单链表实现

节点结构

private static class Node<E> {
    E val;
    Node<E> next;
}

关键设计

  • 使用虚拟头节点(head),但不使用虚拟尾节点
  • 持有真实尾节点引用(tail),优化尾部添加
  • removeLast 仍需遍历找前驱(这是单链表的固有局限)

相关概念

相关实体

验证方法

本文实现可通过 LeetCode 707 设计链表 验证:

  • 注意 API 名字需要调整(本文用 addFirst/removeLast 等,707题用 addAtHead/addAtTail 等)
  • 虚拟头尾节点技巧可大幅简化实现

总结

本文提供了工程级的链表实现,核心在于虚拟头尾节点头尾引用两大技巧。双链表实现更优雅(O(1) 尾部操作),单链表实现更简单但尾部删除仍是 O(N)。理解这些实现技巧,对掌握链表这一基础数据结构至关重要。


Interactive Graph