day08:LinkedList与队列栈
📚 学习目标
- 理解LinkedList的底层结构
- 掌握LinkedList的常用方法
- 理解ArrayList与LinkedList的区别
- 掌握队列(Queue)的使用
- 掌握栈(Stack)的使用
- 能够使用LinkedList实现队列和栈
一、LinkedList概述
1.1 LinkedList简介
什么是LinkedList?
LinkedList是基于双向链表实现的List,也可以作为栈、队列或双端队列使用。
继承体系
java.lang.Object
└── java.util.AbstractCollection<E>
└── java.util.AbstractList<E>
└── java.util.AbstractSequentialList<E>
└── java.util.LinkedList<E>
实现接口
- List:列表接口
- Deque:双端队列接口
- Cloneable:支持克隆
- Serializable:支持序列化
1.2 LinkedList特点
| 特点 | 说明 |
|---|---|
| 有序性 | 按照插入顺序存储元素 |
| 可重复 | 允许存储重复元素 |
| 可存储null | 可以存储null值 |
| 非线程安全 | 多线程环境下需要手动同步 |
| 插入删除快 | 只需修改指针,时间复杂度O(1) |
| 随机访问慢 | 需要遍历链表,时间复杂度O(n) |
| 双向链表 | 每个节点存储前后指针 |
| 内存占用大 | 每个节点需要额外存储前后指针 |
二、LinkedList源码分析
2.1 核心数据结构
java
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 元素个数
transient int size = 0;
// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// 节点内部类
private static class Node<E> {
E item; // 元素值
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
2.2 链表结构图
双向链表结构:
first → [ null ← Node1 → ] ← → [ ← Node2 → ] ← → [ ← Node3 → null ] ← last
↑ ↑ ↑
item1 item2 item3
2.3 添加元素
头插法
java
/**
* 在头部添加元素
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
尾插法
java
/**
* 在尾部添加元素
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
2.4 删除元素
java
/**
* 删除节点
*/
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
三、LinkedList常用方法
3.1 添加元素
java
import java.util.LinkedList;
public class LinkedListAddDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 1. add(E e):添加到末尾
list.add("Java");
list.add("Python");
System.out.println("add: " + list);
// 2. addFirst(E e):添加到头部
list.addFirst("C++");
System.out.println("addFirst: " + list);
// 3. addLast(E e):添加到末尾
list.addLast("Go");
System.out.println("addLast: " + list);
// 4. offer(E e):添加到末尾(队列方法)
list.offer("Rust");
System.out.println("offer: " + list);
// 5. offerFirst(E e):添加到头部
list.offerFirst("Swift");
System.out.println("offerFirst: " + list);
// 6. offerLast(E e):添加到末尾
list.offerLast("Ruby");
System.out.println("offerLast: " + list);
// 7. push(E e):添加到头部(栈方法)
list.push("Kotlin");
System.out.println("push: " + list);
}
}
3.2 获取元素
java
import java.util.LinkedList;
public class LinkedListGetDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("Java");
list.add("Python");
list.add("C++");
list.add("Go");
// 1. get(int index):获取指定位置元素
System.out.println("get(1): " + list.get(1));
// 2. getFirst():获取第一个元素
System.out.println("getFirst: " + list.getFirst());
// 3. getLast():获取最后一个元素
System.out.println("getLast: " + list.getLast());
// 4. peek():获取但不删除头部元素
System.out.println("peek: " + list.peek());
// 5. peekFirst():获取但不删除头部元素
System.out.println("peekFirst: " + list.peekFirst());
// 6. peekLast():获取但不删除尾部元素
System.out.println("peekLast: " + list.peekLast());
// 7. element():获取但不删除头部元素(队列为空时抛异常)
System.out.println("element: " + list.element());
}
}
3.3 删除元素
java
import java.util.LinkedList;
public class LinkedListRemoveDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("Java");
list.add("Python");
list.add("C++");
list.add("Go");
System.out.println("原始列表: " + list);
// 1. remove():删除并返回头部元素
String removed = list.remove();
System.out.println("remove(): " + removed + ", 列表: " + list);
// 2. removeFirst():删除并返回头部元素
removed = list.removeFirst();
System.out.println("removeFirst(): " + removed + ", 列表: " + list);
// 3. removeLast():删除并返回尾部元素
removed = list.removeLast();
System.out.println("removeLast(): " + removed + ", 列表: " + list);
// 4. poll():删除并返回头部元素(队列为空时返回null)
list.add("Rust");
list.add("Swift");
System.out.println("列表: " + list);
removed = list.poll();
System.out.println("poll(): " + removed + ", 列表: " + list);
// 5. pollFirst():删除并返回头部元素
removed = list.pollFirst();
System.out.println("pollFirst(): " + removed + ", 列表: " + list);
// 6. pollLast():删除并返回尾部元素
removed = list.pollLast();
System.out.println("pollLast(): " + removed + ", 列表: " + list);
// 7. pop():删除并返回头部元素(栈方法)
list.push("Kotlin");
list.push("Scala");
System.out.println("列表: " + list);
removed = list.pop();
System.out.println("pop(): " + removed + ", 列表: " + list);
}
}
四、ArrayList vs LinkedList
4.1 对比表
| 对比项 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1),快 | O(n),慢 |
| 插入删除(头部) | O(n),需要移动元素 | O(1),只需修改指针 |
| 插入删除(中间) | O(n),需要移动元素 | O(n),需要遍历找到位置 |
| 插入删除(尾部) | O(1)(不需要扩容时) | O(1) |
| 内存占用 | 连续内存,可能浪费空间 | 每个节点需要额外存储前后指针 |
| 缓存友好性 | 好(连续内存) | 差(非连续内存) |
| 适用场景 | 查询多,修改少 | 频繁插入删除 |
4.2 性能测试
java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class PerformanceCompare {
public static void main(String[] args) {
int count = 100000;
// 测试1:头部插入
System.out.println("========== 头部插入测试 ==========");
long start = System.currentTimeMillis();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < count; i++) {
arrayList.add(0, i);
}
long end = System.currentTimeMillis();
System.out.println("ArrayList头部插入" + count + "次: " + (end - start) + "ms");
start = System.currentTimeMillis();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < count; i++) {
linkedList.add(0, i);
}
end = System.currentTimeMillis();
System.out.println("LinkedList头部插入" + count + "次: " + (end - start) + "ms");
// 测试2:尾部插入
System.out.println("\n========== 尾部插入测试 ==========");
start = System.currentTimeMillis();
arrayList = new ArrayList<>();
for (int i = 0; i < count; i++) {
arrayList.add(i);
}
end = System.currentTimeMillis();
System.out.println("ArrayList尾部插入" + count + "次: " + (end - start) + "ms");
start = System.currentTimeMillis();
linkedList = new LinkedList<>();
for (int i = 0; i < count; i++) {
linkedList.add(i);
}
end = System.currentTimeMillis();
System.out.println("LinkedList尾部插入" + count + "次: " + (end - start) + "ms");
// 测试3:随机访问
System.out.println("\n========== 随机访问测试 ==========");
start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
arrayList.get(i);
}
end = System.currentTimeMillis();
System.out.println("ArrayList随机访问" + count + "次: " + (end - start) + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
linkedList.get(i);
}
end = System.currentTimeMillis();
System.out.println("LinkedList随机访问" + count + "次: " + (end - start) + "ms");
}
}
五、队列(Queue)
5.1 队列概述
什么是队列?
队列是一种先进先出(FIFO)的数据结构。
队列特点
- 先进先出(FIFO)
- 从尾部添加元素
- 从头部删除元素
5.2 队列实现
java
import java.util.LinkedList;
import java.util.Queue;
public class QueueDemo {
public static void main(String[] args) {
// 使用LinkedList实现队列
Queue<String> queue = new LinkedList<>();
// 入队
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println("队列: " + queue);
// 查看队头元素
System.out.println("队头元素: " + queue.peek());
// 出队
while (!queue.isEmpty()) {
String item = queue.poll();
System.out.println("出队: " + item + ", 剩余: " + queue);
}
}
}
5.3 自定义队列
java
/**
* 自定义队列
*/
public class MyQueue<T> {
private LinkedList<T> list = new LinkedList<>();
// 入队
public void enqueue(T item) {
list.addLast(item);
}
// 出队
public T dequeue() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return list.removeFirst();
}
// 查看队头
public T peek() {
if (isEmpty()) {
throw new RuntimeException("队列为空");
}
return list.getFirst();
}
// 判断是否为空
public boolean isEmpty() {
return list.isEmpty();
}
// 获取大小
public int size() {
return list.size();
}
public static void main(String[] args) {
MyQueue<String> queue = new MyQueue<>();
queue.enqueue("A");
queue.enqueue("B");
queue.enqueue("C");
System.out.println("队头: " + queue.peek());
System.out.println("出队: " + queue.dequeue());
System.out.println("出队: " + queue.dequeue());
System.out.println("大小: " + queue.size());
}
}
六、栈(Stack)
6.1 栈概述
什么是栈?
栈是一种后进先出(LIFO)的数据结构。
栈特点
- 后进先出(LIFO)
- 从栈顶添加元素
- 从栈顶删除元素
6.2 栈实现
java
import java.util.LinkedList;
import java.util.Stack;
public class StackDemo {
public static void main(String[] args) {
// 方式1:使用Stack类(不推荐)
Stack<String> stack1 = new Stack<>();
stack1.push("A");
stack1.push("B");
stack1.push("C");
System.out.println("Stack: " + stack1);
System.out.println("栈顶: " + stack1.peek());
System.out.println("出栈: " + stack1.pop());
// 方式2:使用LinkedList(推荐)
LinkedList<String> stack2 = new LinkedList<>();
stack2.push("A");
stack2.push("B");
stack2.push("C");
System.out.println("\nLinkedList: " + stack2);
System.out.println("栈顶: " + stack2.peek());
System.out.println("出栈: " + stack2.pop());
}
}
6.3 自定义栈
java
/**
* 自定义栈
*/
public class MyStack<T> {
private LinkedList<T> list = new LinkedList<>();
// 入栈
public void push(T item) {
list.addFirst(item);
}
// 出栈
public T pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空");
}
return list.removeFirst();
}
// 查看栈顶
public T peek() {
if (isEmpty()) {
throw new RuntimeException("栈为空");
}
return list.getFirst();
}
// 判断是否为空
public boolean isEmpty() {
return list.isEmpty();
}
// 获取大小
public int size() {
return list.size();
}
public static void main(String[] args) {
MyStack<Integer> stack = new MyStack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("栈顶: " + stack.peek());
System.out.println("出栈: " + stack.pop());
System.out.println("出栈: " + stack.pop());
System.out.println("大小: " + stack.size());
}
}
七、实践案例
7.1 括号匹配检测
java
import java.util.LinkedList;
/**
* 括号匹配检测
* 使用栈实现
*/
public class BracketMatcher {
public static boolean isValid(String s) {
LinkedList<Character> stack = new LinkedList<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (stack.isEmpty()) {
return false;
}
char top = stack.pop();
if (!isMatch(top, c)) {
return false;
}
}
}
return stack.isEmpty();
}
private static boolean isMatch(char left, char right) {
return (left == '(' && right == ')') ||
(left == '[' && right == ']') ||
(left == '{' && right == '}');
}
public static void main(String[] args) {
String[] tests = {
"()",
"()[]{}",
"(]",
"([)]",
"{[]}",
""
};
for (String test : tests) {
System.out.println("\"" + test + "\" -> " + isValid(test));
}
}
}
7.2 表达式求值
java
import java.util.LinkedList;
/**
* 简单表达式求值
* 支持加减乘除
*/
public class ExpressionCalculator {
public static int calculate(String expression) {
LinkedList<Integer> numbers = new LinkedList<>();
LinkedList<Character> operators = new LinkedList<>();
int i = 0;
while (i < expression.length()) {
char c = expression.charAt(i);
if (Character.isDigit(c)) {
int num = 0;
while (i < expression.length() &&
Character.isDigit(expression.charAt(i))) {
num = num * 10 + (expression.charAt(i) - '0');
i++;
}
numbers.push(num);
} else if (c == '+' || c == '-' || c == '*' || c == '/') {
while (!operators.isEmpty() &&
precedence(operators.peek()) >= precedence(c)) {
numbers.push(applyOp(operators.pop(),
numbers.pop(),
numbers.pop()));
}
operators.push(c);
i++;
} else {
i++;
}
}
while (!operators.isEmpty()) {
numbers.push(applyOp(operators.pop(),
numbers.pop(),
numbers.pop()));
}
return numbers.pop();
}
private static int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
}
private static int applyOp(char op, int b, int a) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return a / b;
}
return 0;
}
public static void main(String[] args) {
String[] expressions = {
"3+2*2",
" 3/2 ",
" 3+5 / 2 ",
"1+2*3+4"
};
for (String expr : expressions) {
System.out.println(expr + " = " + calculate(expr));
}
}
}
7.3 浏览器前进后退
java
import java.util.LinkedList;
/**
* 浏览器前进后退功能
* 使用两个栈实现
*/
public class BrowserHistory {
private LinkedList<String> backStack = new LinkedList<>();
private LinkedList<String> forwardStack = new LinkedList<>();
private String currentPage;
public BrowserHistory(String homepage) {
this.currentPage = homepage;
}
// 访问新页面
public void visit(String url) {
backStack.push(currentPage);
forwardStack.clear();
currentPage = url;
System.out.println("访问: " + url);
}
// 后退
public String back() {
if (backStack.isEmpty()) {
System.out.println("无法后退");
return currentPage;
}
forwardStack.push(currentPage);
currentPage = backStack.pop();
System.out.println("后退到: " + currentPage);
return currentPage;
}
// 前进
public String forward() {
if (forwardStack.isEmpty()) {
System.out.println("无法前进");
return currentPage;
}
backStack.push(currentPage);
currentPage = forwardStack.pop();
System.out.println("前进到: " + currentPage);
return currentPage;
}
public static void main(String[] args) {
BrowserHistory browser = new BrowserHistory("google.com");
browser.visit("baidu.com");
browser.visit("taobao.com");
browser.visit("jd.com");
browser.back();
browser.back();
browser.forward();
browser.visit("github.com");
browser.back();
}
}
八、课后作业
必做题
-
实现自定义队列和栈
- 使用LinkedList实现
- 实现基本操作
-
括号匹配检测
- 支持多种括号
- 返回匹配结果
-
浏览器前进后退
- 使用双栈实现
- 支持访问新页面
选做题
-
逆波兰表达式求值
- 使用栈实现
- 支持加减乘除
-
实现最小栈
- 支持push、pop、getMin操作
- 时间复杂度O(1)
-
实现双端队列
- 支持头部和尾部操作
- 实现所有Deque方法
九、学习总结
今日要点
-
LinkedList底层结构
- 双向链表
- Node节点
-
LinkedList常用方法
- 添加、删除、获取、遍历
-
ArrayList vs LinkedList
- 底层结构不同
- 性能差异
- 适用场景
-
队列(Queue)
- 先进先出(FIFO)
- offer、poll、peek
-
栈(Stack)
- 后进先出(LIFO)
- push、pop、peek
学习建议
- 理解链表结构:画图理解节点关系
- 对比性能:测试ArrayList和LinkedList的性能差异
- 实践应用:实现队列和栈的应用场景
- 阅读源码:理解LinkedList的实现原理
下一课预告:HashMap源码与使用
学习时间:建议3小时(理论1小时 + 实践2小时)