一、基本性质与主要知识点
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。