【数据结构】 栈和队列

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

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

相关推荐
Freedom_my2 小时前
插入排序算法
数据结构·算法·排序算法
952362 小时前
排序-算法
数据结构·算法·排序算法
WongKyunban2 小时前
插入排序的原理和示例
数据结构·算法·排序算法
聪明绝顶的小Yya2 小时前
数据结构万字解读
数据结构
迈巴赫车主4 小时前
蓝桥杯 20541魔法科考试
java·数据结构·算法·蓝桥杯
帧栈6 小时前
并发编程原理与实战(三十八)高并发利器ConcurrentHashMap 数据结构与核心API深度剖析
数据结构
cpp_25017 小时前
P1765 手机
数据结构·c++·算法·题解·洛谷
就是ping不通的蛋黄派8 小时前
数据结构与算法—线性表(C++描述)
数据结构·c++
hweiyu009 小时前
数据结构和算法分类
数据结构·算法·分类