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

相关推荐
xbgRS2 小时前
springboot的自动装配
java·spring boot
嘻哈∠※9 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
白狐_79810 小时前
408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序
java·数据结构·算法
zander25811 小时前
LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界
java·数据结构·算法
码匠许师傅11 小时前
【C++ 面试真题】聊聊 C++ 的拷贝构造与拷贝赋值
java·c++·面试
极创信息13 小时前
系统安全隐患全方位排查方案:标准化渗透测试全流程
java·opencv·struts·数据挖掘·eclipse·语音识别·hibernate
汉字萌萌哒13 小时前
2019CCF-CSP入门级C++试题解析
java·开发语言·c++
喜欢的名字被抢了13 小时前
程序出问题怎么查,以及如何让它不掉线
java·运维·数据库
数据知道14 小时前
反序列化漏洞:Java、PHP、Python 三条线各讲透
java·网络·python·安全·网络安全·php
2601_9499506315 小时前
Java 面试准备,用练题簿把“背过八股”变成“真正会答”
java·开发语言·面试·刷题·小程序推荐