【数据结构】 栈和队列

一、基本性质与主要知识点

1. 核心特性

  • :遵循后进先出(LIFO) 原则,仅允许在栈顶进行插入(入栈)和删除(出栈)操作。

    Java中可直接使用java.util.Stack类(继承自Vector,线程安全但效率较低),或通过Deque接口的push/pop方法(如ArrayDeque,更推荐)实现。

  • 队列 :遵循先进先出(FIFO) 原则,允许在队尾插入(入队)、队头删除(出队)操作。

    Java中通过java.util.Queue接口实现,常用实现类有LinkedList(链式)和ArrayBlockingQueue(顺序)。

Deque接口是Queue接口的子接口,若将Deque限制为只能从一端进行入队和出队,就实现了栈的数据结构,而栈有入栈(push)和出栈(pop)操作,遵循先进后出规则

2. 重点考察方向及Java实现示例

(1)序列合法性判断

问题 :给定入栈序列(如A,B,C,D,E)和出栈序列(如B,D,C,A,E),判断是否合法。
实现思路:用栈模拟入栈过程,每入栈一个元素就检查是否与出栈序列当前元素匹配,若匹配则出栈,最终栈为空则合法。

java 复制代码
public static boolean isLegalPopOrder(char[] pushSeq, char[] popSeq) {
    Stack<Character> stack = new Stack<>();
    int popIndex = 0; // 出栈序列指针
    for (char c : pushSeq) {
        stack.push(c);
        // 若栈顶与当前出栈元素匹配,则持续出栈
        while (!stack.isEmpty() && stack.peek() == popSeq[popIndex]) {
            stack.pop();
            popIndex++;
        }
    }
    return stack.isEmpty(); // 栈空则序列合法
}

示例1:入栈序列[A,B,C,D,E],出栈序列[B,D,C,A,E](合法)

核心规则:栈顶匹配出栈序列当前元素则弹出,最终栈空=合法

步骤 操作(入栈/出栈) 栈状态(自上而下) popIndex(指向出栈序列) 匹配情况
1 入栈A [A] 0(指向B) 栈顶A≠B,不弹出
2 入栈B [A, B] 0(指向B) 栈顶B=B,弹出B
3 弹出B后 [A] 1(指向D) 栈顶A≠D,不弹出
4 入栈C [A, C] 1(指向D) 栈顶C≠D,不弹出
5 入栈D [A, C, D] 1(指向D) 栈顶D=D,弹出D
6 弹出D后 [A, C] 2(指向C) 栈顶C=C,弹出C
7 弹出C后 [A] 3(指向A) 栈顶A=A,弹出A
8 弹出A后 [](空栈) 4(指向E) 栈空,等待入栈E
9 入栈E [E] 4(指向E) 栈顶E=E,弹出E
10 弹出E后 [](空栈) 5(到达序列末尾) 所有元素匹配

示例2:入栈序列[A,B,C,D,E],出栈序列[D,B,A,C,E](非法)

步骤 操作(入栈/出栈) 栈状态(自上而下) popIndex(指向出栈序列) 匹配情况
1 入栈A [A] 0(指向D) 栈顶A≠D,不弹出
2 入栈B [A, B] 0(指向D) 栈顶B≠D,不弹出
3 入栈C [A, B, C] 0(指向D) 栈顶C≠D,不弹出
4 入栈D [A, B, C, D] 0(指向D) 栈顶D=D,弹出D
5 弹出D后 [A, B, C] 1(指向B) 栈顶C≠B,不弹出
6 入栈E [A, B, C, E] 1(指向B) 栈顶E≠B,不弹出
7 入栈结束,尝试弹剩余元素 [A, B, C, E] 1(指向B) 栈顶E≠B,先弹出E
8 弹出E后 [A, B, C] 1(指向B) 栈顶C≠B,弹出C
9 弹出C后 [A, B] 1(指向B) 栈顶B=B,弹出B
10 弹出B后 [A] 2(指向A) 栈顶A=A,弹出A
11 弹出A后 [](空栈) 3(指向C) 栈空,无元素匹配C
(2)栈容量计算

问题 :根据出栈序列,计算栈所需的最小容量(如出队序列B,D,C,F,E,A,G对应出栈序列,求最小容量)。
实现思路:模拟入栈出栈过程,记录栈中元素的最大个数。

java 复制代码
public static int getMinStackCapacity(char[] pushSeq, char[] popSeq) {
    Stack<Character> stack = new Stack<>();
    int popIndex = 0;
    int maxSize = 0;
    for (char c : pushSeq) {
        stack.push(c);
        maxSize = Math.max(maxSize, stack.size()); // 更新最大容量
        while (!stack.isEmpty() && stack.peek() == popSeq[popIndex]) {
            stack.pop();
            popIndex++;
        }
    }
    return maxSize;
}

我们用入栈序列A,B,C,D,E和出栈序列B,D,C,A,E为例:

步骤 操作(入栈/出栈) 栈状态(自上而下) 栈当前大小 最大容量(maxSize)更新 popIndex(指向出栈序列)
1 入栈A [A] 1 maxSize = max(0, 1) = 1 0(指向B)
2 入栈B [A, B] 2 maxSize = max(1, 2) = 2 0(指向B)
3 弹出B(匹配出栈序列) [A] 1 不变(仍为2) 1(指向D)
4 入栈C [A, C] 2 不变(仍为2) 1(指向D)
5 入栈D [A, C, D] 3 maxSize = max(2, 3) = 3 1(指向D)
6 弹出D(匹配出栈序列) [A, C] 2 不变(仍为3) 2(指向C)
7 弹出C(匹配出栈序列) [A] 1 不变(仍为3) 3(指向A)
8 弹出A(匹配出栈序列) [](空栈) 0 不变(仍为3) 4(指向E)
9 入栈E [E] 1 不变(仍为3) 4(指向E)
10 弹出E(匹配出栈序列) [](空栈) 0 不变(仍为3) 5(序列结束)

二、存储结构与Java实现方法

1. 栈的存储实现

(1)链式存储(自定义单链表实现)
java 复制代码
// 节点类
class ListNode {
    char val;
    ListNode next;
    ListNode(char val) {
        this.val = val;
    }
}

// 链式栈(带头节点)
class LinkedStack {
    private ListNode head; // 头节点(不存储数据)

    public LinkedStack() {
        head = new ListNode('\0');
    }

    // 入栈(头插法)
    public void push(char c) {
        ListNode newNode = new ListNode(c);
        newNode.next = head.next;
        head.next = newNode;
    }

    // 出栈(头删法)
    public char pop() {
        if (isEmpty()) throw new RuntimeException("栈空");
        ListNode top = head.next;
        head.next = top.next;
        return top.val;
    }

    public boolean isEmpty() {
        return head.next == null;
    }
}
(2)顺序存储(数组实现,top指向栈顶元素)
java 复制代码
class ArrayStack {
    private char[] stack;
    private int top; // 指向当前栈顶元素(初始-1)
    private int capacity;

    public ArrayStack(int capacity) {
        this.capacity = capacity;
        stack = new char[capacity];
        top = -1; // 栈空标志
    }

    // 入栈:top先+1再存值
    public void push(char c) {
        if (isFull()) throw new RuntimeException("栈满");
        stack[++top] = c;
    }

    // 出栈:先取值再top-1
    public char pop() {
        if (isEmpty()) throw new RuntimeException("栈空");
        return stack[top--];
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public boolean isFull() {
        return top == capacity - 1;
    }
}

2. 队列的存储实现

(1)链式存储(自定义单链表实现)
java 复制代码
class LinkedQueue {
    private ListNode head; // 头节点(不存储数据)
    private ListNode rear; // 队尾指针(指向最后一个元素)

    public LinkedQueue() {
        head = new ListNode('\0');
        rear = head; // 初始队尾与头节点重合
    }

    // 入队:队尾插入
    public void enqueue(char c) {
        ListNode newNode = new ListNode(c);
        rear.next = newNode;
        rear = newNode;
    }

    // 出队:队头删除(头节点后第一个元素)
    public char dequeue() {
        if (isEmpty()) throw new RuntimeException("队空");
        ListNode front = head.next;
        head.next = front.next;
        // 若队空,更新rear指向头节点
        if (head.next == null) rear = head;
        return front.val;
    }

    public boolean isEmpty() {
        return head.next == null;
    }
}
(2)循环队列(数组实现,牺牲一个存储单元判满)
java 复制代码
class CircularQueue {
    private char[] queue;
    private int front; // 队头指针(指向队头元素)
    private int rear;  // 队尾指针(指向队尾元素的下一个位置)
    private int capacity;

    public CircularQueue(int capacity) {
        this.capacity = capacity;
        queue = new char[capacity];
        front = 0;
        rear = 0; // 初始队空:front == rear
    }

    // 入队:先存值,再rear = (rear + 1) % capacity
    public void enqueue(char c) {
        if (isFull()) throw new RuntimeException("队满");
        queue[rear] = c;
        rear = (rear + 1) % capacity;
    }

    // 出队:先取值,再front = (front + 1) % capacity
    public char dequeue() {
        if (isEmpty()) throw new RuntimeException("队空");
        char val = queue[front];
        front = (front + 1) % capacity;
        return val;
    }

    // 判空:front == rear
    public boolean isEmpty() {
        return front == rear;
    }

    // 判满:(rear + 1) % capacity == front(牺牲一个单元)
    public boolean isFull() {
        return (rear + 1) % capacity == front;
    }

    // 队列长度:(rear - front + capacity) % capacity
    public int size() {
        return (rear - front + capacity) % capacity;
    }
}

变体 :若用size变量判满(无需牺牲单元),可在类中增加int size,入队size++,出队size--,判满条件为size == capacity

相关推荐
小熳芋4 小时前
验证二叉搜索树- python-递归&上下界约束
数据结构
不穿格子的程序员7 小时前
从零开始写算法——链表篇2:从“回文”到“环形”——链表双指针技巧的深度解析
数据结构·算法·链表·回文链表·环形链表
诺....8 小时前
C语言不确定循环会影响输入输出缓冲区的刷新
c语言·数据结构·算法
长安er9 小时前
LeetCode876/141/142/143 快慢指针应用:链表中间 / 环形 / 重排问题
数据结构·算法·leetcode·链表·双指针·环形链表
workflower9 小时前
PostgreSQL 数据库的典型操作
数据结构·数据库·oracle·数据库开发·时序数据库
仰泳的熊猫10 小时前
1140 Look-and-say Sequence
数据结构·c++·算法·pat考试
EXtreme3510 小时前
栈与队列的“跨界”对话:如何用双队列完美模拟栈的LIFO特性?
c语言·数据结构·leetcode·双队列模拟栈·算法思维
松涛和鸣10 小时前
29、Linux进程核心概念与编程实战:fork/getpid全解析
linux·运维·服务器·网络·数据结构·哈希算法
hweiyu0010 小时前
数据结构:有向图
数据结构