day08_LinkedList与队列栈

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();
    }
}

八、课后作业

必做题

  1. 实现自定义队列和栈

    • 使用LinkedList实现
    • 实现基本操作
  2. 括号匹配检测

    • 支持多种括号
    • 返回匹配结果
  3. 浏览器前进后退

    • 使用双栈实现
    • 支持访问新页面

选做题

  1. 逆波兰表达式求值

    • 使用栈实现
    • 支持加减乘除
  2. 实现最小栈

    • 支持push、pop、getMin操作
    • 时间复杂度O(1)
  3. 实现双端队列

    • 支持头部和尾部操作
    • 实现所有Deque方法

九、学习总结

今日要点

  1. LinkedList底层结构

    • 双向链表
    • Node节点
  2. LinkedList常用方法

    • 添加、删除、获取、遍历
  3. ArrayList vs LinkedList

    • 底层结构不同
    • 性能差异
    • 适用场景
  4. 队列(Queue)

    • 先进先出(FIFO)
    • offer、poll、peek
  5. 栈(Stack)

    • 后进先出(LIFO)
    • push、pop、peek

学习建议

  1. 理解链表结构:画图理解节点关系
  2. 对比性能:测试ArrayList和LinkedList的性能差异
  3. 实践应用:实现队列和栈的应用场景
  4. 阅读源码:理解LinkedList的实现原理

下一课预告:HashMap源码与使用

学习时间:建议3小时(理论1小时 + 实践2小时)

相关推荐
无心水3 小时前
2、5分钟上手|PyPDF2 快速提取PDF文本
java·linux·分布式·后端·python·架构·pdf
Han_han9193 小时前
案例二:交通工具调度系统(核心:继承 + 多态 + final + 方法重写)
java·开发语言
cch89183 小时前
Java vs 汇编:高级与低级的终极对决
java·开发语言·汇编
码上实战3 小时前
到底Java 适不适合做 AI 呢?
java·人工智能·后端·python·ai
如若1233 小时前
ERROR:pdf2zh.converter:‘str‘ object has no attribute ‘choices‘ converter.py:357
java·开发语言·servlet
cch89183 小时前
PHP vs Java:谁更适合你的项目?
java·开发语言·php
萧逸才3 小时前
【learn-claude-code】S11AutonomousAgents - 自主 Agent:自动认领任务 + 空闲轮询
java·人工智能·ai
李少兄3 小时前
优化高负载详情接口:基于字段选择与懒加载的实践
java
简单点了3 小时前
mac安装Java环境
java·macos