当前位置:首页 > 后端开发 > 正文

java怎么创建链表的

在 Java 中,可通过 new LinkedList()(需导入 java.util.LinkedList)快速创建 链表,支持泛型指定元素类型

基础概念与设计思路

1 链表类型选择

类型 特点 适用场景
单链表 每个节点仅保存后继节点的引用 简单遍历、频繁增删尾部
双向链表 每个节点同时保存前驱和后继节点的引用 双向遍历、频繁增删任意位置
循环链表 首尾相连形成环形 轮转调度、缓冲区管理
带头节点链表 增加一个不存储数据的虚拟头节点 简化边界条件处理

2 核心组件设计

  • Node类:封装数据(data)和下一个节点的引用(next)
  • LinkedList类:管理链表整体行为(初始化、插入、删除、遍历)
  • 泛型支持:通过<E>定义通用类型参数,提升代码复用性


单链表完整实现步骤

1 定义节点类 Node<E>

// 节点类模板 class Node<E> { E data; // 存储的数据 Node<E> next; // 指向下一个节点的引用 // 构造函数 public Node(E data) { this.data = data; this.next = null; // 新节点默认无后续节点 } }

关键点:next字段初始化为null,表示新节点独立存在。

2 构建链表主体类 SinglyLinkedList<E>

public class SinglyLinkedList<E> { private Node<E> head; // 链表头节点 private int size; // 链表长度计数器 // 构造函数:创建空链表 public SinglyLinkedList() { head = null; // 初始时链表为空 size = 0; } }

优化建议:可改为带头节点的设计(见下文扩展部分),统一空/非空状态的处理逻辑。

java怎么创建链表的 第1张

3 核心方法实现

方法 功能描述 时间复杂度 关键逻辑
add(E element) 在链表末尾追加元素 O(n) 遍历至最后一个节点,将其next指向新节点
addAt(int index, E element) 在指定位置插入元素 O(n) 找到index-1位置的节点,修改其next指向新节点
remove(int index) 删除指定位置的元素 O(n) 找到index-1位置的节点,跳过待删除节点,重新连接前后节点
contains(E element) 判断元素是否存在 O(n) 遍历链表逐个比对元素
get(int index) 获取指定位置的元素 O(n) 遍历至目标位置返回数据
size() 返回链表长度 O(1) 直接返回size变量
isEmpty() 判断链表是否为空 O(1) 检查head == null或size == 0

示例代码片段:add方法实现

public void add(E element) { Node<E> newNode = new Node<>(element); if (head == null) { // 链表为空时直接设为头节点 head = newNode; } else { // 非空时找到最后一个节点 Node<E> current = head; while (current.next != null) { current = current.next; } current.next = newNode; // 将新节点挂在末尾 } size++; // 更新长度计数器 }

注意:上述实现未使用尾指针优化,导致add操作需要遍历整个链表,若需高频尾部插入,应增加tail字段维护尾节点引用。


进阶优化方案

1 带头节点的链表设计

// 修改后的链表结构 private Node<E> dummyHead; // 虚拟头节点,不存储实际数据 private int size; public SinglyLinkedList() { dummyHead = new Node<>(null); // 初始化虚拟头节点 size = 0; }

优势

  • 消除空链表的特殊处理(head永远不为null)
  • 统一插入/删除操作的逻辑(无需判断是否是第一个节点)
  • 简化边界条件处理(如删除头节点时无需特殊处理)

2 双向链表扩展

class DoublyNode<E> { E data; DoublyNode<E> prev; // 前驱节点引用 DoublyNode<E> next; // 后继节点引用 public DoublyNode(E data) { this.data = data; this.prev = null; this.next = null; } }

典型应用:浏览器前进/后退历史记录、文本编辑器光标移动。

java怎么创建链表的 第2张

3 循环链表实现

// 在单链表基础上修改构造函数 public SinglyLinkedList() { head = new Node<>(null); head.next = head; // 形成自环 size = 0; }

特点:遍历时无需判断next == null,适合需要无限循环的场景。


常见错误与调试技巧

错误类型 现象 解决方案
空指针异常 NullPointerException 确保所有节点的next都有合理初始化,特别是最后一个节点的next=null
索引越界 IndexOutOfBoundsException 在addAt/get/remove方法中增加索引范围校验(0 <= index <= size)
内存泄漏 JVM堆内存持续增长 确保删除节点时断开所有引用关系,避免垃圾回收失效
死循环 遍历时无法终止 检查终止条件是否为current != null(单链表)或current != dummyHead(带头节点)


性能对比与选型建议

操作 单链表 双向链表 ArrayDeque(JDK内置) ArrayList(JDK内置)
头部插入/删除 O(1) O(1) O(1) O(n)
尾部插入/删除 O(n) O(1) O(1) O(n)
随机访问 O(n) O(n) O(n) O(1)
空间利用率 较低

选型原则

  • 频繁头部操作 → 单链表/双向链表
  • 频繁尾部操作 → 双向链表(配合尾指针)
  • 随机访问为主 → ArrayList/ArrayDeque
  • 需要双向遍历 → 双向链表


完整代码示例(带头节点的单链表)

public class AdvancedLinkedList<E> { private Node<E> dummyHead; // 虚拟头节点 private int size; private static class Node<E> { E data; Node<E> next; public Node(E data) { this.data = data; } } public AdvancedLinkedList() { dummyHead = new Node<>(null); // 虚拟头节点不存数据 size = 0; } // 在指定位置插入元素(索引从0开始) public void addAt(int index, E element) { if (index < 0 || index > size) throw new IndexOutOfBoundsException(); Node<E> newNode = new Node<>(element); Node<E> prev = getNode(index 1); // 获取前驱节点 newNode.next = prev.next; prev.next = newNode; size++; } // 获取指定位置的节点(用于内部辅助) private Node<E> getNode(int index) { Node<E> current = dummyHead; for (int i = 0; i < index; i++) { current = current.next; } return current; } // 删除指定位置的元素 public E removeAt(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(); Node<E> prev = getNode(index 1); Node<E> toRemove = prev.next; E oldData = toRemove.data; prev.next = toRemove.next; // 跳过待删除节点 toRemove.next = null; // 清除引用防止内存泄漏 size--; return oldData; } // 遍历打印链表内容 public void printList() { Node<E> current = dummyHead.next; // 跳过虚拟头节点 while (current != null) { System.out.print(current.data + " -> "); current = current.next; } System.out.println("NULL"); } }

🧪 测试用例

public static void main(String[] args) { AdvancedLinkedList<Integer> list = new AdvancedLinkedList<>(); list.addAt(0, 10); // [10] list.addAt(1, 20); // [10, 20] list.addAt(2, 30); // [10, 20, 30] list.printList(); // 输出: 10 -> 20 -> 30 -> NULL list.removeAt(1); // 删除20 → [10, 30] list.printList(); // 输出: 10 -> 30 -> NULL }


FAQs

Q1: Java已经有java.util.LinkedList,为什么还要手动实现?

A: Java标准库的LinkedList是基于双向链表实现的,虽然功能强大,但隐藏了底层细节,手动实现可以帮助理解链表的核心机制(如节点管理、指针操作),这对面试和算法学习至关重要,自定义链表可以针对特定需求进行优化(如只读链表、固定容量限制等)。

Q2: 如何处理链表中的重复元素?

A: 如果需要去重,可以在插入时遍历链表检查是否已存在相同元素,有两种策略:①允许重复但限制最大出现次数;②完全禁止重复,示例代码片段:

public boolean contains(E element) { Node<E> current = dummyHead.next; while (current != null) { if (element.equals(current.data)) return true; current = current.next; } return false; }

注意:需重写equals()方法的对象才能正确判断相等

java怎么创建链表的 第3张

0