数据结构19栈和队列

栈和队列 - 完整学习指南

📊 知识体系总览

复制代码
mindmap
  root((栈和队列))
    
    栈(Stack)
      基本概念
        后进先出LIFO
        栈顶栈底
        压栈出栈
      
      实现方式
        数组实现
        链表实现
        Java Stack类
      
      基本操作
        push入栈
        pop出栈
        peek查看
        size大小
        empty判空
      
      应用场景
        括号匹配
        表达式求值
        函数调用栈
        撤销操作
        浏览记录
    
    队列(Queue)
      基本概念
        先进先出FIFO
        队头队尾
        入队出队
      
      实现方式
        数组队列
        链表队列
        循环队列
        Java Queue接口
      
      基本操作
        offer入队
        poll出队
        peek查看
        size大小
        isEmpty判空
      
      应用场景
        任务调度
        消息队列
        广度优先搜索
        缓存系统
    
    双端队列(Deque)
      基本概念
        两端操作
        栈和队列组合
      
      实现方式
        ArrayDeque
        LinkedList
      
      应用场景
        滑动窗口
        回文检查
        双向遍历
    
    经典问题
      用栈实现队列
      用队列实现栈
      最小栈
      循环队列
      栈的合法序列
    
    性能分析
      时间复杂度
        栈操作: O(1)
        队列操作: O(1)
      空间复杂度
        数组实现: O(n)
        链表实现: O(n)

1. 栈(Stack)

1.1 栈的基本概念

复制代码
graph TD
    A[栈 Stack] --> B[核心特性]
    A --> C[基本结构]
    A --> D[操作方式]
    A --> E[现实例子]
    
    B --> B1[后进先出 LIFO]
    B --> B2[只能在栈顶操作]
    B --> B3[访问受限]
    
    C --> C1[栈顶 Top]
    C --> C2[栈底 Bottom]
    C --> C3[存储空间]
    
    D --> D1[压栈 Push]
    D --> D2[出栈 Pop]
    D --> D3[查看 Peek]
    
    E --> E1[盘子叠放]
    E --> E2[浏览器后退]
    E --> E3[函数调用]
    E --> E4[撤销操作]

概念解释

栈是一种特殊的线性表,只允许在固定的一端(栈顶)进行插入和删除操作。遵循后进先出(LIFO, Last In First Out)原则。

核心特性

  • 操作受限:只能在栈顶进行插入和删除

  • 后进先出:最后入栈的元素最先出栈

  • 动态大小:可以根据需要动态增长

  • 访问受限:不能随机访问中间元素

代码示例 - 基本使用

复制代码
import java.util.*;

public class StackBasicDemo {
    public static void main(String[] args) {
        System.out.println("=== 栈的基本使用 ===");
        
        // 创建栈
        Stack<Integer> stack = new Stack<>();
        
        // 1. 压栈操作
        System.out.println("1. 压栈操作:");
        stack.push(10);
        stack.push(20);
        stack.push(30);
        stack.push(40);
        System.out.println("压栈 10, 20, 30, 40 后: " + stack);
        
        // 2. 查看栈顶元素
        System.out.println("\n2. 查看栈顶:");
        int top = stack.peek();
        System.out.println("栈顶元素: " + top);
        System.out.println("查看后栈不变: " + stack);
        
        // 3. 出栈操作
        System.out.println("\n3. 出栈操作:");
        int popped1 = stack.pop();
        System.out.println("出栈元素: " + popped1);
        System.out.println("出栈后: " + stack);
        
        int popped2 = stack.pop();
        System.out.println("再次出栈: " + popped2);
        System.out.println("当前栈: " + stack);
        
        // 4. 栈的大小和判空
        System.out.println("\n4. 栈信息:");
        System.out.println("栈大小: " + stack.size());
        System.out.println("是否为空: " + stack.empty());
        
        // 5. 搜索元素
        System.out.println("\n5. 搜索元素:");
        int position = stack.search(10);  // 返回从栈顶开始的1-based位置
        System.out.println("元素10的位置: " + position);
        System.out.println("元素100的位置: " + stack.search(100));  // -1表示不存在
        
        // 6. 遍历栈
        System.out.println("\n6. 遍历栈:");
        System.out.println("方法1 - 使用迭代器:");
        for (Integer num : stack) {
            System.out.print(num + " ");
        }
        System.out.println();
        
        System.out.println("方法2 - 使用forEach:");
        stack.forEach(num -> System.out.print(num + " "));
        System.out.println();
        
        // 注意:不能通过索引访问栈元素
        // System.out.println(stack.get(0));  // 编译错误
        
        // 7. 清空栈
        System.out.println("\n7. 清空栈:");
        while (!stack.empty()) {
            System.out.println("出栈: " + stack.pop());
        }
        System.out.println("清空后大小: " + stack.size());
        System.out.println("是否为空: " + stack.empty());
        
        // 8. 注意:Stack是线程安全的
        System.out.println("\n8. 线程安全性:");
        System.out.println("Stack继承自Vector,是线程安全的");
        System.out.println("但在高并发环境下,推荐使用ConcurrentLinkedDeque");
    }
}

注意易错点

  1. 空栈操作:对空栈调用pop()或peek()会抛出EmptyStackException

  2. 遍历修改:不能在遍历时修改栈结构

  3. 线程安全:Stack是线程安全的,但性能较差

  4. 搜索位置:search()返回的是从栈顶开始的1-based位置

  5. 接口选择:推荐使用Deque接口代替Stack

1.2 栈的模拟实现

复制代码
classDiagram
    direction BT
    class MyArrayStack {
        -int[] elementData
        -int top
        -int capacity
        +MyArrayStack()
        +MyArrayStack(int initialCapacity)
        +push(int element) int
        +pop() int
        +peek() int
        +size() int
        +isEmpty() boolean
        +isFull() boolean
        -ensureCapacity(int minCapacity) void
    }
    
    class MyLinkedStack {
        -Node top
        -int size
        +MyLinkedStack()
        +push(int element) int
        +pop() int
        +peek() int
        +size() int
        +isEmpty() boolean
    }
    
    class Node {
        -int data
        -Node next
        +Node(int data)
        +Node(int data, Node next)
    }
    
    MyLinkedStack o-- Node

数组实现栈

复制代码
import java.util.Arrays;

// 数组实现的栈
public class MyArrayStack {
    private int[] elementData;  // 存储元素的数组
    private int top;            // 栈顶指针
    private int capacity;       // 栈容量
    
    // 构造方法
    public MyArrayStack() {
        this(10);  // 默认容量10
    }
    
    public MyArrayStack(int initialCapacity) {
        if (initialCapacity <= 0) {
            throw new IllegalArgumentException("初始容量必须大于0");
        }
        this.capacity = initialCapacity;
        this.elementData = new int[capacity];
        this.top = -1;  // 栈空时top为-1
    }
    
    // 压栈
    public int push(int element) {
        ensureCapacity(top + 2);  // 检查是否需要扩容
        elementData[++top] = element;  // 先移动指针,再赋值
        return element;
    }
    
    // 出栈
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈为空,无法出栈");
        }
        return elementData[top--];  // 先返回值,再移动指针
    }
    
    // 查看栈顶元素
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("栈为空,无法查看");
        }
        return elementData[top];
    }
    
    // 获取栈大小
    public int size() {
        return top + 1;
    }
    
    // 判断栈是否为空
    public boolean isEmpty() {
        return top == -1;
    }
    
    // 判断栈是否已满
    public boolean isFull() {
        return top == capacity - 1;
    }
    
    // 扩容
    private void ensureCapacity(int minCapacity) {
        if (minCapacity > capacity) {
            int newCapacity = Math.max(capacity * 2, minCapacity);
            elementData = Arrays.copyOf(elementData, newCapacity);
            capacity = newCapacity;
            System.out.println("栈扩容: " + capacity / 2 + " -> " + capacity);
        }
    }
    
    // 清空栈
    public void clear() {
        top = -1;
        // 可选:释放数组引用
        // elementData = new int[capacity];
    }
    
    // 打印栈
    public void display() {
        if (isEmpty()) {
            System.out.println("栈[]");
            return;
        }
        
        System.out.print("栈[");
        for (int i = 0; i <= top; i++) {
            System.out.print(elementData[i]);
            if (i < top) {
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }
    
    // 测试代码
    public static void main(String[] args) {
        System.out.println("=== 数组实现栈测试 ===");
        
        MyArrayStack stack = new MyArrayStack(3);
        
        // 测试压栈
        stack.push(10);
        stack.push(20);
        stack.push(30);
        System.out.print("压栈10,20,30: ");
        stack.display();
        System.out.println("栈大小: " + stack.size());
        
        // 测试自动扩容
        stack.push(40);
        System.out.print("压栈40触发扩容: ");
        stack.display();
        
        // 测试查看栈顶
        System.out.println("栈顶元素: " + stack.peek());
        
        // 测试出栈
        System.out.println("出栈: " + stack.pop());
        System.out.print("出栈后: ");
        stack.display();
        
        // 测试判空
        System.out.println("栈是否为空: " + stack.isEmpty());
        
        // 测试连续出栈
        while (!stack.isEmpty()) {
            System.out.println("出栈: " + stack.pop());
        }
        System.out.println("清空后是否为空: " + stack.isEmpty());
        
        // 测试异常
        try {
            stack.pop();
        } catch (RuntimeException e) {
            System.out.println("空栈出栈异常: " + e.getMessage());
        }
    }
}

链表实现栈

复制代码
// 链表节点
class StackNode {
    int data;
    StackNode next;
    
    public StackNode(int data) {
        this.data = data;
        this.next = null;
    }
    
    public StackNode(int data, StackNode next) {
        this.data = data;
        this.next = next;
    }
}

// 链表实现的栈
public class MyLinkedStack {
    private StackNode top;  // 栈顶节点
    private int size;       // 栈大小
    
    public MyLinkedStack() {
        this.top = null;
        this.size = 0;
    }
    
    // 压栈
    public int push(int element) {
        StackNode newNode = new StackNode(element);
        newNode.next = top;  // 新节点指向原栈顶
        top = newNode;       // 更新栈顶
        size++;
        return element;
    }
    
    // 出栈
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈为空,无法出栈");
        }
        
        int value = top.data;  // 保存栈顶值
        top = top.next;        // 移动栈顶指针
        size--;
        return value;
    }
    
    // 查看栈顶
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("栈为空,无法查看");
        }
        return top.data;
    }
    
    // 获取大小
    public int size() {
        return size;
    }
    
    // 判断是否为空
    public boolean isEmpty() {
        return top == null;
    }
    
    // 清空栈
    public void clear() {
        top = null;
        size = 0;
    }
    
    // 打印栈
    public void display() {
        if (isEmpty()) {
            System.out.println("栈[]");
            return;
        }
        
        System.out.print("栈[");
        StackNode current = top;
        while (current != null) {
            System.out.print(current.data);
            if (current.next != null) {
                System.out.print(" -> ");
            }
            current = current.next;
        }
        System.out.println("]");
    }
    
    // 测试代码
    public static void main(String[] args) {
        System.out.println("=== 链表实现栈测试 ===");
        
        MyLinkedStack stack = new MyLinkedStack();
        
        // 测试压栈
        stack.push(10);
        stack.push(20);
        stack.push(30);
        System.out.print("压栈10,20,30: ");
        stack.display();
        System.out.println("栈大小: " + stack.size());
        
        // 测试查看栈顶
        System.out.println("栈顶元素: " + stack.peek());
        
        // 测试出栈
        System.out.println("出栈: " + stack.pop());
        System.out.print("出栈后: ");
        stack.display();
        
        // 测试判空
        System.out.println("栈是否为空: " + stack.isEmpty());
        
        // 测试连续出栈
        while (!stack.isEmpty()) {
            System.out.println("出栈: " + stack.pop());
        }
        System.out.println("清空后是否为空: " + stack.isEmpty());
        
        // 测试大量数据
        for (int i = 0; i < 1000; i++) {
            stack.push(i);
        }
        System.out.println("压栈1000个元素后大小: " + stack.size());
        
        // 测试内存自动管理
        stack.clear();
        System.out.println("清空后大小: " + stack.size());
    }
}

注意易错点

  1. 指针管理:数组实现要注意top指针的位置

  2. 边界检查:操作前要检查栈是否为空/满

  3. 扩容策略:数组栈需要考虑扩容和缩容

  4. 内存泄漏:链表栈删除节点时要断开引用

  5. 线程安全:自己实现的栈不是线程安全的

  6. 异常处理:要对非法操作进行合适的异常处理

1.3 栈的应用场景

复制代码
graph TD
    A[栈的应用场景] --> B[表达式求值]
    A --> C[括号匹配]
    A --> D[函数调用]
    A --> E[浏览记录]
    A --> F[撤销操作]
    A --> G[递归转循环]
    A --> H[迷宫求解]
    
    B --> B1[中缀表达式]
    B --> B2[后缀表达式]
    B --> B3[前缀表达式]
    
    C --> C1[括号有效性]
    C --> C2[HTML/XML标签]
    C --> C3[代码缩进]
    
    D --> D1[调用栈]
    D --> D2[栈帧]
    D --> D3[递归深度]
    
    E --> E1[前进后退]
    E --> E2[页面历史]
    
    F --> F1[编辑器撤销]
    F --> F2[游戏状态]
    
    G --> G1[深度优先搜索]
    G --> G2[树遍历]

应用1:括号匹配

复制代码
import java.util.*;

public class BracketMatching {
    
    // 检查括号是否匹配
    public static boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        
        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 (!isMatchingPair(top, c)) {
                    return false;  // 括号不匹配
                }
            }
            // 忽略其他字符
        }
        
        return stack.isEmpty();  // 栈空说明所有括号都匹配
    }
    
    // 检查括号是否成对
    private static boolean isMatchingPair(char left, char right) {
        return (left == '(' && right == ')') ||
               (left == '[' && right == ']') ||
               (left == '{' && right == '}');
    }
    
    // 扩展:支持多种括号
    public static boolean isValidExtended(String s) {
        Map<Character, Character> map = new HashMap<>();
        map.put(')', '(');
        map.put(']', '[');
        map.put('}', '{');
        map.put('>', '<');
        
        Stack<Character> stack = new Stack<>();
        
        for (char c : s.toCharArray()) {
            if (map.containsValue(c)) {
                // 左括号
相关推荐
鱼跃鹰飞2 小时前
Leetcode279:完全平方数
数据结构·算法·leetcode·面试
小龙报2 小时前
【数据结构与算法】单链表核心精讲:从概念到实战,吃透指针与动态内存操作
c语言·开发语言·数据结构·c++·人工智能·算法·链表
long3162 小时前
合并排序 merge sort
java·数据结构·spring boot·算法·排序算法
范纹杉想快点毕业2 小时前
STM32单片机与ZYNQ PS端 中断+状态机+FIFO 综合应用实战文档(初学者版)
linux·数据结构·数据库·算法·mongodb
多米Domi0114 小时前
0x3f 第49天 面向实习的八股背诵第六天 过了一遍JVM的知识点,看了相关视频讲解JVM内存,垃圾清理,买了plus,稍微看了点确定一下方向
jvm·数据结构·python·算法·leetcode
L_090713 小时前
【C++】高阶数据结构 -- 红黑树
数据结构·c++
划破黑暗的第一缕曙光16 小时前
[数据结构]:5.二叉树链式结构的实现1
数据结构
青桔柠薯片17 小时前
数据结构:单向链表,顺序栈和链式栈
数据结构·链表
XiaoFan01217 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树