一、开篇:程序员的“食堂哲学”
假设你是一个程序员,正坐在公司食堂排队打饭。此时,食堂阿姨的窗口只有一个,队伍必须先进先出(FIFO)——这就是队列的精髓。而当你吃完饭想把盘子放回洗碗池时,洗碗阿姨只会从最上面拿走一个盘子——这便是栈的“后进先出”(LIFO)哲学。
栈和队列,这两个看似简单的数据结构,却是程序员世界里最基础的“交通规则”。它们像两条平行的单行道:栈是“只进不出”的单行道(只能从顶部操作),而队列是“只进只出”的双向道(队尾进,队头出)。
二、原理篇:LIFO vs FIFO
1. 栈(Stack)
LIFO(Last In, First Out):就像你叠盘子,最后一个放上去的盘子,必须第一个被拿走。
- 核心操作:
push
:往栈顶压入元素(叠盘子)。pop
:弹出栈顶元素(拿走最上面的盘子)。top
:查看栈顶元素(看看最上面是什么盘子)。
应用场景:
- 函数调用栈(递归调用时的“记忆点”)。
- 括号匹配(比如
{[()]}
的匹配问题)。 - 撤销/重做功能(比如 Word 的 Ctrl+Z)。
2. 队列(Queue)
FIFO(First In, First Out):就像食堂排队,先来的先打饭。
- 核心操作:
push
:在队尾添加元素(排队的人站在队尾)。pop
:移除队头元素(打饭的人从队头出)。front
:查看队头元素(看谁该打饭了)。
应用场景:
- 任务调度(比如打印队列)。
- 广度优先搜索(BFS)。
- 网络请求处理(比如限流队列)。
三、实现篇:C++代码大乱斗
1. 栈的实现(数组 vs 链表)
数组实现(简单粗暴)
#include <iostream>
using namespace std;
class MyStack {
private:
int* arr;
int top; // 栈顶指针
int capacity; // 容量
public:
MyStack(int size) {
arr = new int[size];
capacity = size;
top = 0; // 初始栈顶为0
}
void push(int x) {
if (top >= capacity) {
cout << "栈满啦!别压了,洗碗阿姨会崩溃的!" << endl;
return;
}
arr[top++] = x;
}
int pop() {
if (top <= 0) {
cout << "栈空啦!盘子都洗完了!" << endl;
return -1;
}
return arr[--top];
}
int topValue() {
if (top <= 0) {
cout << "栈空啦!啥都没有!" << endl;
return -1;
}
return arr[top - 1];
}
};
int main() {
MyStack s(5);
s.push(1); // 压入1号盘子
s.push(2); // 压入2号盘子
s.push(3); // 压入3号盘子
cout << "栈顶元素: " << s.topValue() << endl; // 输出3
s.pop(); // 弹出3号盘子
cout << "新栈顶元素: " << s.topValue() << endl; // 输出2
return 0;
}
链表实现(灵活多变)
struct Node {
int data;
Node* next;
Node(int x) : data(x), next(nullptr) {}
};
class MyStackLinkedList {
private:
Node* top; // 栈顶指针
public:
MyStackLinkedList() { top = nullptr; }
void push(int x) {
Node* newNode = new Node(x);
newNode->next = top;
top = newNode;
}
int pop() {
if (top == nullptr) {
cout << "链表栈空啦!别弹了!" << endl;
return -1;
}
Node* temp = top;
int val = temp->data;
top = top->next;
delete temp;
return val;
}
};
2. 队列的实现(环形数组 vs 链表)
环形数组实现(高效省空间)
class MyQueue {
private:
int* arr;
int front; // 队头指针
int rear; // 队尾指针
int capacity;
public:
MyQueue(int size) {
capacity = size;
arr = new int[capacity];
front = 0;
rear = 0; // 初始队列为空
}
void push(int x) {
if ((rear + 1) % capacity == front) {
cout << "队列满啦!食堂窗口堵住了!" << endl;
return;
}
arr[rear] = x;
rear = (rear + 1) % capacity;
}
int pop() {
if (front == rear) {
cout << "队列空啦!没人排队了!" << endl;
return -1;
}
int val = arr[front];
front = (front + 1) % capacity;
return val;
}
};
链表实现(动态扩展)
struct QueueNode {
int data;
QueueNode* next;
QueueNode(int x) : data(x), next(nullptr) {}
};
class MyQueueLinkedList {
private:
QueueNode* front; // 队头
QueueNode* rear; // 队尾
public:
MyQueueLinkedList() { front = rear = nullptr; }
void push(int x) {
QueueNode* newNode = new QueueNode(x);
if (rear == nullptr) { // 空队列
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
}
int pop() {
if (front == nullptr) {
cout << "链表队列空啦!没饭吃了!" << endl;
return -1;
}
int val = front->data;
QueueNode* temp = front;
front = front->next;
delete temp;
return val;
}
};
四、实战篇:栈和队列的“江湖对决”
1. 用栈实现队列(双栈设计哲学)
场景:你有两个栈,一个负责“接收订单”(输入栈),另一个负责“出货”(输出栈)。
class MyQueueUsingStack {
private:
stack<int> inStack, outStack;
void transfer() { // 元素转移
while (!inStack.empty()) {
outStack.push(inStack.top());
inStack.pop();
}
}
public:
void push(int x) {
inStack.push(x); // 订单进入输入栈
}
int pop() {
if (outStack.empty()) transfer(); // 输出栈空则转移
int val = outStack.top();
outStack.pop();
return val;
}
};
总结:
输入栈就像“食堂窗口”,输出栈是“打饭阿姨的托盘”。每次打饭前,阿姨都要把窗口的盘子倒进托盘,才能按顺序发饭。
2. 用队列实现栈(双队列设计哲学)
场景:你有两个队列,每次压栈时,将元素放入非空队列,然后将原队列的其他元素转移到另一个队列。
class MyStackUsingQueue {
private:
queue<int> q1, q2;
public:
void push(int x) {
if (q1.empty()) {
q2.push(x);
while (!q1.empty()) {
q2.push(q1.front());
q1.pop();
}
} else {
q1.push(x);
while (!q2.empty()) {
q1.push(q2.front());
q2.pop();
}
}
}
int pop() {
if (q1.empty()) return q2.front();
else return q1.front();
}
};
总结:
这就像两个食堂窗口,每次有人插队时,都要把前面的人转移到另一个窗口,才能保证“最后来的先打饭”。
五、高阶应用:栈和队列的“跨界合作”
1. 括号匹配问题(栈的经典应用)
bool isValid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c); // 遇到左括号压栈
} else {
if (st.empty()) return false;
char top = st.top();
st.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return st.empty();
}
测试:
如果你输入
([)]
,程序会像食堂阿姨一样怒斥:“括号不匹配,重新来!”
2. 广度优先搜索(BFS)(队列的典型场景)
void BFS(Node* root) {
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node* current = q.front();
q.pop();
// 处理当前节点
for (auto child : current->children) {
q.push(child); // 将子节点入队
}
}
}
总结:
BFS就像食堂的“传菜链”,每一道菜都会被“出队”处理,然后把下一道菜“入队”。
六、结语:程序员的“单行道”人生
栈和队列,是程序员世界中最基础的“交通规则”。它们看似简单,却能构建出复杂的算法和系统。正如食堂阿姨的窗口和洗碗池,程序员的生活也充满了这些“单行道”与“流水线”。
最后送大家一句话:
“人生如栈,越努力越要‘出栈’;人生如队,越耐心越能‘出队’!”