什么是Java单链表
单链表(Linked List)是一种常见的基础数据结构,它由一系列节点(Node)组成,每个节点包含两个部分:数据域(data)和指针域(next)。在Java中,单链表通常通过自定义类来实现,相比数组,它具有动态内存分配的优势。
单链表的基本特点
- 动态大小:不需要预先指定大小,可以动态增长和缩减
- 内存效率:只在需要时分配内存,避免内存浪费
- 插入删除高效:时间复杂度为O(1),前提是知道操作位置
- 随机访问低效:访问特定元素需要从头遍历,时间复杂度为O(n)
Java单链表的基本实现
节点类定义
```java
public class ListNode {
int val; // 数据域
ListNode next; // 指针域
public ListNode(int val) {
this.val = val;
this.next = null;
}
}
### 单链表类框架
```java
public class SinglyLinkedList {
private ListNode head; // 头节点
public SinglyLinkedList() {
this.head = null;
}
// 各种操作方法将在下面实现
}
Java单链表的常用操作
插入操作
头部插入
public void insertAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head;
head = newNode;
}
尾部插入
public void insertAtTail(int val) {
ListNode newNode = new ListNode(val);
if (head == null) {
head = newNode;
return;
}
ListNode current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
指定位置插入
public void insertAtIndex(int index, int val) {
if (index < 0) {
throw new IllegalArgumentException("索引不能为负数");
}
if (index == 0) {
insertAtHead(val);
return;
}
ListNode newNode = new ListNode(val);
ListNode current = head;
for (int i = 0; i < index - 1 && current != null; i++) {
current = current.next;
}
if (current == null) {
throw new IndexOutOfBoundsException("索引超出链表长度");
}
newNode.next = current.next;
current.next = newNode;
}
删除操作
删除头节点
public void deleteAtHead() {
if (head == null) {
return;
}
head = head.next;
}
删除尾节点
public void deleteAtTail() {
if (head == null || head.next == null) {
head = null;
return;
}
ListNode current = head;
while (current.next.next != null) {
current = current.next;
}
current.next = null;
}
删除指定值节点
public void deleteValue(int val) {
if (head == null) {
return;
}
if (head.val == val) {
head = head.next;
return;
}
ListNode current = head;
while (current.next != null && current.next.val != val) {
current = current.next;
}
if (current.next != null) {
current.next = current.next.next;
}
}
Java单链表的高级应用
链表反转
public void reverse() {
ListNode prev = null;
ListNode current = head;
ListNode next = null;
while (current != null) {
next = current.next;
current.next = prev;
prev = current;
current = next;
}
head = prev;
}
检测环
public boolean hasCycle() {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
合并两个有序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode current = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
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;
}
Java单链表的性能优化
使用虚拟头节点(Dummy Node)
在处理链表问题时,使用虚拟头节点可以简化边界条件处理:
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode current = dummy;
while (current.next != null) {
if (current.next.val == val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return dummy.next;
}
快慢指针技巧
快慢指针是解决链表问题的强大工具,常用于:
- 找到链表的中间节点
- 检测链表是否有环
- 找到环的入口点
public ListNode findMiddle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
Java单链表在实际项目中的应用
实现LRU缓存
单链表可以用于实现简单的LRU(最近最少使用)缓存算法:
public class LRUCache {
private final int capacity;
private final Map<Integer, ListNode> map;
private final ListNode dummyHead;
private final ListNode dummyTail;
private int size;
public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>();
this.dummyHead = new ListNode(-1);
this.dummyTail = new ListNode(-1);
dummyHead.next = dummyTail;
dummyTail.prev = dummyHead;
this.size = 0;
}
// 其他方法实现...
}
浏览器历史记录
单链表适合模拟浏览器的前进后退功能:
public class BrowserHistory {
private ListNode current;
public BrowserHistory(String homepage) {
current = new ListNode(homepage);
}
public void visit(String url) {
ListNode newNode = new ListNode(url);
current.next = newNode;
newNode.prev = current;
current = newNode;
}
public String back(int steps) {
while (steps-- > 0 && current.prev != null) {
current = current.prev;
}
return current.val;
}
public String forward(int steps) {
while (steps-- > 0 && current.next != null) {
current = current.next;
}
return current.val;
}
}
Java单链表的常见面试题
- 反转链表:如何高效地反转一个单链表?
- 环检测:如何判断链表是否有环?如果有,如何找到环的入口?
- 相交链表:如何找到两个单链表相交的起始节点?
- 删除倒数第N个节点:如何一次遍历删除链表的倒数第N个节点?
- 排序链表:如何在O(nlogn)时间复杂度和常数级空间复杂度下对链表进行排序?
掌握这些问题的解法,能够帮助你在技术面试中游刃有余地应对链表相关问题。
总结
Java单链表作为一种基础数据结构,在算法和实际应用中都有广泛使用。通过本文的学习,你应该已经掌握了单链表的基本实现、常用操作、高级应用以及性能优化技巧。在实际开发中,根据具体需求选择合适的链表变体(如双向链表、循环链表等)可以进一步提高程序效率。
记住,理解数据结构的核心在于实践。建议你动手实现本文中的代码示例,并尝试解决一些LeetCode上的链表相关问题,这将帮助你更深入地掌握Java单链表的精髓。