栈与队列详解及模拟实现
目录
一、栈(Stack):后进先出
1.1 什么是栈
1.2 栈的使用
1.3 栈的模拟实现
1.4 栈的经典应用
二、队列(Queue):先进先出
2.1 什么是队列
2.2 队列的使用
2.3 队列的模拟实现
2.4 循环队列:空间利用
三、双端队列(Deque):双向操作
一、栈(Stack):后进先出
1.1 什么是栈
栈是一种只能在一端操作的线性表,遵循后进先出(LIFO)原则。想象一下叠盘子:你只能从最上面拿盘子,最后放上去的盘子会被最先取走。栈的两个核心操作是:
- 压栈(Push):将数据放到栈顶。
- 出栈(Pop):从栈顶移除数据。
现实中的例子:浏览器的“后退”按钮(最近访问的页面最先返回)、函数调用时的执行栈。
1.2 栈的使用
Java中的Stack
类提供了简单易用的方法:
方法 | 功能 |
---|---|
Stack() | 创建一个空栈 |
push(E e) | 压栈,返回元素 |
pop() | 弹出栈顶元素 |
peek() | 查看栈顶元素(不删除) |
size() | 返回栈中元素个数 |
empty() | 判断栈是否为空 |
public static void main(String[] args) {Stack<Integer> s = new Stack<>();s.push(1);s.push(2);s.push(3);System.out.println(s.size()); // 输出3System.out.println(s.peek()); // 输出3(栈顶元素)s.pop(); // 弹出3if (s.empty()) {System.out.println("栈空");} else {System.out.println(s.size()); // 输出2}
}
1.3 栈的模拟实现
栈的底层可以用数组或链表实现。Java的Stack
继承自Vector
(线程安全的动态数组),但实际开发中更推荐用LinkedList
或自己实现。
public class MyStack {private int[] data;private int top; // 栈顶指针public void push(int val) {if (top == data.length) throw new StackOverflowError();data[top++] = val;}public int pop() {if (top == 0) throw new EmptyStackException();return data[--top];}
}
1.4 栈的经典应用
括号匹配:检查表达式中的括号是否成对。
boolean isValid(String s) {Stack<Character> stack = new Stack<>();for (char c : s.toCharArray()) {if (c == '(') stack.push(')');else if (c == '[') stack.push(']');else if (c == '{') stack.push('}');else if (stack.isEmpty() || stack.pop() != c) return false;}return stack.isEmpty();
}
二、队列(Queue):先进先出
2.1 什么是队列
队列是一种只能在队尾插入、队头删除的线性表,遵循先进先出(FIFO)原则。就像排队买奶茶:先来的人先拿到奶茶,后来的人排在队尾。
2.2 队列的使用
Java中队列通过Queue
接口实现,常用LinkedList
:
方法 | 功能 |
---|---|
offer(E e) | 入队,返回是否成功 |
poll() | 出队(返回队头元素) |
peek() | 查看队头元素(不删除) |
size() | 返回队列元素个数 |
isEmpty() | 判断队列是否为空 |
public static void main(String[] args) {Queue<Integer> q = new LinkedList<>();q.offer(1);q.offer(2);q.offer(3);System.out.println(q.peek()); // 输出1q.poll(); // 移除1System.out.println(q.size()); // 输出2
}
2.3 队列的模拟实现
队列更适合用链表实现(因为插入和删除发生在两端)。
class Node {int val;Node next;
}public class MyQueue {private Node front; // 队头private Node rear; // 队尾public void offer(int val) {Node newNode = new Node(val);if (rear == null) {front = rear = newNode;} else {rear.next = newNode;rear = newNode;}}public int poll() {if (front == null) throw new NoSuchElementException();int val = front.val;front = front.next;if (front == null) rear = null;return val;}
}
2.4 循环队列:空间利用
当队列空间固定时,循环队列可以避免“假溢出”。核心技巧是通过取模运算实现下标循环:
- 入队:
rear = (rear + 1) % array.length
- 出队:
front = (front + 1) % array.length
如何区分队列空与满?
- 记录元素个数:通过
size
变量判断。 - 保留一个空位:当
(rear + 1) % size == front
时认为队列已满。
三、双端队列(Deque):双向操作
双端队列允许在两端插入和删除,既能当栈用,也能当队列用。
Deque<Integer> stack = new ArrayDeque<>(); // 当作栈
stack.push(1);
stack.pop();Deque<Integer> queue = new LinkedList<>(); // 当作队列
queue.offer(1);
queue.poll();
完 ~~