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小时)

相关推荐
野生技术架构师1 小时前
金三银四面试总结篇,汇总 Java 面试突击班后的面试小册
java·面试·职场和发展
小袁拒绝摆烂2 小时前
多表关联大平层转JSON树形结构
java·json
ja哇3 小时前
大厂面试高频八股
java·面试·职场和发展
yoyo_zzm3 小时前
Laravel6.x新特性全解析
java·spring boot·后端
Nick_zcy3 小时前
小说在线阅读网站和小说管理系统 · 功能全解析
java·后端·python·springboot·ruoyi
源码宝3 小时前
基于 SpringBoot + Vue 的医院随访系统:技术架构与功能实现
java·vue.js·spring boot·架构·源码·随访系统·随访管理
qinqinzhang4 小时前
Java 中的 IoC、AOP、MVC
java
禾叙_4 小时前
【langchain4j】结构化输出(六)
java·开发语言
饭小猿人4 小时前
Android 腾讯X5WebView如何禁止系统自带剪切板和自定义剪切板视图
android·java