学习数据结构和算法中的栈和队列是计算机科学和编程中非常重要的基础知识。这篇文章将系统地介绍栈和队列的基础概念、实现、应用、时间复杂度分析,并通过比喻和生活场景建模来加深理解。
1. 基础概念和定义
1.1 栈 (Stack)
定义:栈是一种后进先出(LIFO, Last In First Out)的数据结构,意味着最后一个压入栈的元素将最先弹出。想象一下一个叠盘子,如果你想拿出一个盘子,你必须先移除上面的所有盘子。
基本操作:
push
:将元素压入栈顶。pop
:从栈顶弹出元素。peek/top
:查看栈顶元素但不弹出。isEmpty
:检查栈是否为空。size
:获取栈的大小。
时间复杂度:上述操作均为 O(1)。
1.2 队列 (Queue)
定义:队列是一种先进先出(FIFO, First In First Out)的数据结构,意味着第一个进入队列的元素将最先出队。想象一下排队买票,第一个排队的人将第一个买到票。
基本操作:
enqueue
:将元素添加到队尾。dequeue
:从队首移除元素。front
:查看队首元素但不移除。isEmpty
:检查队列是否为空。size
:获取队列的大小。
时间复杂度:上述操作均为 O(1)。
2. 栈的实现和应用
2.1 实现栈
数组实现栈:使用数组来实现栈的基本操作。
class ArrayStack {
private int[] stack;
private int top;
private int capacity;
public ArrayStack(int capacity) {
this.capacity = capacity;
stack = new int[capacity];
top = -1;
}
public void push(int value) {
if (top == capacity - 1) {
throw new StackOverflowError("Stack is full");
}
stack[++top] = value;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stack[top--];
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return stack[top];
}
public boolean isEmpty() {
return top == -1;
}
public int size() {
return top + 1;
}
}
链表实现栈:使用链表来实现栈的基本操作。
class LinkedListStack {
private static class Node {
private int data;
private Node next;
public Node(int data) {
this.data = data;
}
}
private Node top;
public void push(int value) {
Node node = new Node(value);
node.next = top;
top = node;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
int value = top.data;
top = top.next;
return value;
}
public int peek() {
if (isEmpty()) {
throw new IllegalStateException("Stack is empty");
}
return top.data;
}
public boolean isEmpty() {
return top == null;
}
public int size() {
int size = 0;
Node current = top;
while (current != null) {
size++;
current = current.next;
}
return size;
}
}
2.2 栈的应用
表达式求值:使用栈来求解中缀表达式、前缀表达式和后缀表达式。例如,后缀表达式求值:
import java.util.Stack;
public class PostfixEvaluator {
public int evaluate(String expression) {
Stack<Integer> stack = new Stack<>();
for (String token : expression.split(" ")) {
if (isOperator(token)) {
int b = stack.pop();
int a = stack.pop();
stack.push(applyOperator(token, a, b));
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
private boolean isOperator(String token) {
return "+-*/".contains(token);
}
private int applyOperator(String operator, int a, int b) {
switch (operator) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/": return a / b;
}
throw new IllegalArgumentException("Invalid operator: " + operator);
}
public static void main(String[] args) {
PostfixEvaluator evaluator = new PostfixEvaluator();
String expression = "3 4 + 2 * 7 /";
System.out.println("Result: " + evaluator.evaluate(expression)); // Result: 2
}
}
括号匹配:检查括号是否成对匹配(包括圆括号、方括号和花括号)。
import java.util.Stack;
public class ParenthesesMatcher {
public boolean isBalanced(String expression) {
Stack<Character> stack = new Stack<>();
for (char c : expression.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 boolean isMatchingPair(char open, char close) {
return (open == '(' && close == ')') ||
(open == '{' && close == '}') ||
(open == '[' && close == ']');
}
public static void main(String[] args) {
ParenthesesMatcher matcher = new ParenthesesMatcher();
String expression = "{[()]}";
System.out.println("Is balanced: " + matcher.isBalanced(expression)); // Is balanced: true
}
}
函数调用栈:了解栈在递归和函数调用中的应用。例如,递归实现阶乘函数:
public class Factorial {
public int factorial(int n) {
if (n == 0) {
return 1;
}
return n * factorial(n - 1);
}
public static void main(String[] args) {
Factorial factorial = new Factorial();
System.out.println("Factorial of 5: " + factorial.factorial(5)); // Factorial of 5: 120
}
}
3. 队列的实现和应用
3.1 实现队列
数组实现队列:使用数组来实现队列的基本操作,处理数组队列的循环问题。
class ArrayQueue {
private int[] queue;
private int front;
private int rear;
private int capacity;
private int size;
public ArrayQueue(int capacity) {
this.capacity = capacity;
queue = new int[capacity];
front = 0;
rear = -1;
size = 0;
}
public void enqueue(int value) {
if (size == capacity) {
throw new IllegalStateException("Queue is full");
}
rear = (rear + 1) % capacity;
queue[rear] = value;
size++;
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = queue[front];
front = (front + 1) % capacity;
size--;
return value;
}
public int front() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return queue[front];
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
}
链表实现队列:使用链表来实现队列的基本操作。
class LinkedListQueue {
private static class Node {
private int data;
private Node next;
public Node(int data) {
this.data = data;
}
}
private Node front;
private Node rear;
private int size;
public void enqueue(int value) {
Node node = new Node(value);
if (rear != null) {
rear.next = node;
}
rear = node;
if (front == null) {
front = node;
}
size++;
}
public int dequeue() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
int value = front.data;
front = front.next;
if (front == null) {
rear = null;
}
size--;
return value;
}
public int front() {
if (isEmpty()) {
throw new IllegalStateException("Queue is empty");
}
return front.data;
}
public boolean isEmpty() {
return front == null;
}
public int size() {
return size;
}
}
3.2 队列的应用
广度优先搜索 (BFS):使用队列实现图或树的广度优先搜索。
import java.util.LinkedList;
import java.util.Queue;
class Graph {
private int vertices;
private LinkedList<Integer>[] adjacencyList;
public Graph(int vertices) {
this.vertices = vertices;
adjacencyList = new LinkedList[vertices];
for (int i = 0; i < vertices; i++) {
adjacencyList[i] = new LinkedList<>();
}
}
public void addEdge(int source, int destination) {
adjacencyList[source].add(destination);
}
public void bfs(int startVertex) {
boolean[] visited = new boolean[vertices];
Queue<Integer> queue = new LinkedList<>();
visited[startVertex] = true;
queue.add(startVertex);
while (!queue.isEmpty()) {
int vertex = queue.poll();
System.out.print(vertex + " ");
for (int adjVertex : adjacencyList[vertex]) {
if (!visited[adjVertex]) {
visited[adjVertex] = true;
queue.add(adjVertex);
}
}
}
}
public static void main(String[] args) {
Graph graph = new Graph(6);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(1, 3);
graph.addEdge(1, 4);
graph.addEdge(2, 4);
graph.addEdge(3, 5);
graph.addEdge(4, 5);
System.out.println("Breadth First Search starting from vertex 0:");
graph.bfs(0);
}
}
缓存机制:了解队列在实现缓存中的应用,如LRU(最近最少使用)缓存。
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
System.out.println("Cache: " + cache);
cache.get(1);
cache.put(4, "four");
System.out.println("Cache after accessing 1 and adding 4: " + cache);
}
}
任务调度:模拟任务调度系统中的任务排队和处理。
import java.util.LinkedList;
import java.util.Queue;
class TaskScheduler {
private Queue<String> taskQueue = new LinkedList<>();
public void addTask(String task) {
taskQueue.add(task);
System.out.println("Added task: " + task);
}
public void executeTask() {
if (taskQueue.isEmpty()) {
System.out.println("No tasks to execute");
return;
}
String task = taskQueue.poll();
System.out.println("Executing task: " + task);
}
public static void main(String[] args) {
TaskScheduler scheduler = new TaskScheduler();
scheduler.addTask("Task 1");
scheduler.addTask("Task 2");
scheduler.executeTask();
scheduler.executeTask();
scheduler.executeTask();
}
}
4. 进阶队列
4.1 双端队列 (Deque)
定义:双端队列(Deque,Double Ended Queue)是一种具有双端操作能力的队列,允许在队首和队尾进行插入和删除操作。双端队列结合了栈和队列的特点,既可以当作栈使用,也可以当作队列使用。
时间复杂度:
addFirst(E e)
和addLast(E e)
:O(1)removeFirst()
和removeLast()
:O(1)getFirst()
和getLast()
:O(1)
比喻和生活场景建模 想象你在一个双向开口的电梯中,你可以从任意一端进入或离开。你可以随意选择进出的位置,而不需要遵守一端进另一端出的规则。这种灵活性使得双端队列非常适合用于需要频繁从两端操作的场景。
代码示例
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
// 将元素插入队首和队尾
deque.addFirst(10);
deque.addLast(20);
deque.addFirst(5);
// 查看队首和队尾元素
System.out.println("队首元素: " + deque.getFirst());
System.out.println("队尾元素: " + deque.getLast());
// 移除队首和队尾元素
System.out.println("移除队首元素: " + deque.removeFirst());
System.out.println("移除队尾元素: " + deque.removeLast());
// 查看队首元素
System.out.println("队首元素: " + deque.getFirst());
}
}
4.2 优先队列 (Priority Queue)
定义:优先队列是一种特殊的队列结构,其中的元素每一个都有一个优先级,出队时总是优先级最高的元素先出。通常优先队列是通过堆(Heap)来实现的。
时间复杂度:
add(E e)
:O(log n)remove()
:O(log n)peek()
:O(1)
比喻和生活场景建模 想象你在一个医院急诊室等待就诊,医生会根据病人的病情严重程度(优先级)来决定谁先看病,而不是按到达的先后顺序。一个病情严重的病人会优先得到治疗,即使他是最后到达的。
代码示例
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
// 添加元素到优先队列
priorityQueue.add(10);
priorityQueue.add(20);
priorityQueue.add(5);
// 移除并返回队首元素
System.out.println("移除队首元素: " + priorityQueue.poll());
// 查看队首元素
System.out.println("队首元素: " + priorityQueue.peek());
// 添加更多元素到优先队列
priorityQueue.add(1);
priorityQueue.add(15);
// 打印所有元素(按优先级顺序)
while (!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll());
}
}
}
5. 实践和练习
5.1 编写代码
- 实现栈和队列的基本操作。
- 编写应用栈和队列的算法,如括号匹配和广度优先搜索。
5.2 练习题
- 在在线平台(如 LeetCode、HackerRank、CodeSignal 等)上练习相关题目。
- 具体练习题目示例:
- 用栈实现队列(LeetCode 题目)
- 用队列实现栈(LeetCode 题目)
- 有效的括号(LeetCode 题目)
- 滑动窗口最大值(LeetCode 题目)
6. 参考资料
6.1 书籍
- 《数据结构与算法分析》(Data Structures and Algorithm Analysis)
- 《算法导论》(Introduction to Algorithms)
6.2 在线课程和资料
- Coursera、edX 上的算法和数据结构课程。
- Khan Academy 的计算机科学课程。
- GeeksforGeeks 和 TutorialsPoint 的数据结构教程。
7. 总结和复习
- 定期复习所学知识,确保理解和记忆。
- 尝试讲解所学内容,教会他人是巩固知识的好方法。
- 应用所学知识解决实际问题,参与开源项目或个人项目。
通过这篇文章,你将系统地学习和掌握栈和队列这两种重要的数据结构。希望这篇文章对你有所帮助!