1. 为什么需要深入理解Stack与Queue?
在C++编程中,Stack(栈)和Queue(队列)是两种最基础也最重要的数据结构。它们看似简单,但真正理解其底层原理和使用场景的程序员并不多。我见过太多开发者只是机械地调用push()和pop(),却说不清楚为什么在某些场景下必须使用栈而不是队列。
栈遵循LIFO(后进先出)原则,就像餐厅里叠放的盘子,你总是取最上面的那个。队列则是FIFO(先进先出),像排队买票,先来的人先得到服务。这两种特性决定了它们在不同算法中的关键作用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Stack核心操作与内存模型
2.1 标准库中的stack实现
C++标准库中的stack实际上是一个容器适配器,默认基于deque实现。这意味着它本身不管理内存,而是"适配"已有容器的接口:
cpp复制#include <stack>
std::stack<int> s; // 默认使用deque
std::stack<int, std::vector<int>> s2; // 改用vector作为底层容器
选择底层容器时需要考虑:
- vector:随机访问快但动态扩容成本高
- deque:首尾操作都是O(1)但内存不连续
- list:任何位置插入删除都是O(1)但内存开销大
2.2 关键操作的时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| push() | O(1) | 可能触发容器扩容 |
| pop() | O(1) | 不返回被移除的元素 |
| top() | O(1) | 获取但不移除栈顶元素 |
| empty() | O(1) | 判断是否为空 |
| size() | O(1) | 获取元素数量 |
注意:pop()操作不返回元素是经过深思熟虑的设计。如果pop()同时返回栈顶元素,在异常发生时可能导致数据丢失。
2.3 手动实现栈的三种方式
理解标准库实现后,我们可以手动实现栈:
数组版本:
cpp复制class ArrayStack {
int *arr;
int capacity;
int topIndex;
public:
ArrayStack(int cap) : capacity(cap), topIndex(-1) {
arr = new int[capacity];
}
void push(int x) {
if(topIndex == capacity-1)
throw std::overflow_error("Stack overflow");
arr[++topIndex] = x;
}
int pop() {
if(topIndex == -1)
throw std::underflow_error("Stack underflow");
return arr[topIndex--];
}
// 其他方法省略...
};
链表版本:
cpp复制struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {}
};
class ListStack {
Node* topNode;
public:
ListStack() : topNode(nullptr) {}
void push(int x) {
Node* newNode = new Node(x);
newNode->next = topNode;
topNode = newNode;
}
int pop() {
if(!topNode) throw std::underflow_error("Stack underflow");
Node* temp = topNode;
int val = temp->data;
topNode = topNode->next;
delete temp;
return val;
}
// 其他方法省略...
};
STL容器适配版本:
cpp复制template<typename T, typename Container=std::deque<T>>
class MyStack {
Container c;
public:
void push(const T& value) {
c.push_back(value);
}
void pop() {
if(c.empty()) throw std::underflow_error("Stack underflow");
c.pop_back();
}
T& top() {
if(c.empty()) throw std::underflow_error("Stack is empty");
return c.back();
}
// 其他方法省略...
};
3. Queue的深度解析与实现变种
3.1 标准queue的底层实现
与stack类似,queue也是容器适配器,默认基于deque:
cpp复制#include <queue>
std::queue<int> q; // 默认使用deque
std::queue<int, std::list<int>> q2; // 改用list
3.2 关键操作复杂度分析
| 操作 | 时间复杂度 | 特殊情况 |
|---|---|---|
| push() | O(1) | 可能触发容器扩容 |
| pop() | O(1) | 不返回被移除的元素 |
| front() | O(1) | 获取但不移除队首元素 |
| back() | O(1) | 获取但不移除队尾元素 |
| empty() | O(1) | 判断是否为空 |
| size() | O(1) | 获取元素数量 |
3.3 循环队列实现
普通数组实现队列在出队时会产生"假溢出",循环队列解决了这个问题:
cpp复制class CircularQueue {
int *arr;
int front, rear, capacity;
public:
CircularQueue(int cap) : capacity(cap), front(0), rear(0) {
arr = new int[capacity];
}
bool isFull() {
return (rear + 1) % capacity == front;
}
void enqueue(int x) {
if(isFull()) throw std::overflow_error("Queue is full");
arr[rear] = x;
rear = (rear + 1) % capacity;
}
int dequeue() {
if(front == rear) throw std::underflow_error("Queue is empty");
int val = arr[front];
front = (front + 1) % capacity;
return val;
}
// 其他方法省略...
};
3.4 优先队列(Priority Queue)
虽然不属于普通队列,但priority_queue是queue家族重要成员:
cpp复制#include <queue>
std::priority_queue<int> maxHeap; // 默认大顶堆
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
优先队列通常用堆实现,插入和删除都是O(log n)复杂度。
4. 六大经典应用场景剖析
4.1 栈在表达式求值中的应用
编译器处理表达式时使用栈来维护运算符优先级:
cpp复制int evaluateExpression(const string& s) {
stack<int> nums;
stack<char> ops;
int n = s.length();
for(int i = 0; i < n; ) {
if(s[i] == ' ') { i++; continue; }
if(isdigit(s[i])) {
int num = 0;
while(i < n && isdigit(s[i])) {
num = num * 10 + (s[i++] - '0');
}
nums.push(num);
} else {
while(!ops.empty() && precedence(ops.top()) >= precedence(s[i])) {
calc(nums, ops);
}
ops.push(s[i++]);
}
}
while(!ops.empty()) {
calc(nums, ops);
}
return nums.top();
}
void calc(stack<int>& nums, stack<char>& ops) {
int b = nums.top(); nums.pop();
int a = nums.top(); nums.pop();
char op = ops.top(); ops.pop();
switch(op) {
case '+': nums.push(a + b); break;
case '-': nums.push(a - b); break;
case '*': nums.push(a * b); break;
case '/': nums.push(a / b); break;
}
}
4.2 队列在BFS算法中的核心作用
广度优先搜索是队列最典型的应用:
cpp复制void BFS(vector<vector<int>>& graph, int start) {
queue<int> q;
vector<bool> visited(graph.size(), false);
q.push(start);
visited[start] = true;
while(!q.empty()) {
int node = q.front();
q.pop();
cout << "Visiting: " << node << endl;
for(int neighbor : graph[node]) {
if(!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
}
4.3 单调栈解决Next Greater Element问题
单调栈可以在O(n)时间内解决这类问题:
cpp复制vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, -1);
stack<int> s;
for(int i = 0; i < 2 * n; i++) {
int num = nums[i % n];
while(!s.empty() && nums[s.top()] < num) {
res[s.top()] = num;
s.pop();
}
if(i < n) s.push(i);
}
return res;
}
4.4 双端队列实现滑动窗口最大值
使用deque可以在O(n)时间内解决:
cpp复制vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> dq;
vector<int> res;
for(int i = 0; i < nums.size(); i++) {
if(!dq.empty() && dq.front() == i - k) {
dq.pop_front();
}
while(!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
dq.push_back(i);
if(i >= k - 1) {
res.push_back(nums[dq.front()]);
}
}
return res;
}
4.5 栈在函数调用中的关键作用
函数调用栈是栈最重要的应用之一:
cpp复制void funcA() {
cout << "Enter A" << endl;
funcB();
cout << "Exit A" << endl;
}
void funcB() {
cout << "Enter B" << endl;
funcC();
cout << "Exit B" << endl;
}
void funcC() {
cout << "Enter C" << endl;
cout << "Exit C" << endl;
}
int main() {
funcA();
return 0;
}
每次函数调用时,系统会将返回地址、参数和局部变量压入调用栈。
4.6 消息队列在生产者消费者模型中的应用
cpp复制#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
std::queue<int> msgQueue;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for(int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
msgQueue.push(i);
cv.notify_one();
}
}
void consumer() {
while(true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !msgQueue.empty(); });
int msg = msgQueue.front();
msgQueue.pop();
std::cout << "Consumed: " << msg << std::endl;
if(msg == 9) break;
}
}
5. 十道精选实战习题与详解
5.1 有效的括号(LeetCode 20)
cpp复制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();
}
5.2 用队列实现栈(LeetCode 225)
cpp复制class MyStack {
queue<int> q;
public:
void push(int x) {
q.push(x);
for(int i = 0; i < q.size() - 1; ++i) {
q.push(q.front());
q.pop();
}
}
int pop() {
int val = q.front();
q.pop();
return val;
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
};
5.3 用栈实现队列(LeetCode 232)
cpp复制class MyQueue {
stack<int> input, output;
public:
void push(int x) {
input.push(x);
}
int pop() {
peek();
int val = output.top();
output.pop();
return val;
}
int peek() {
if(output.empty()) {
while(!input.empty()) {
output.push(input.top());
input.pop();
}
}
return output.top();
}
bool empty() {
return input.empty() && output.empty();
}
};
5.4 最小栈(LeetCode 155)
cpp复制class MinStack {
stack<int> s;
stack<int> minStack;
public:
void push(int val) {
s.push(val);
if(minStack.empty() || val <= minStack.top()) {
minStack.push(val);
}
}
void pop() {
if(s.top() == minStack.top()) {
minStack.pop();
}
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return minStack.top();
}
};
5.5 逆波兰表达式求值(LeetCode 150)
cpp复制int evalRPN(vector<string>& tokens) {
stack<int> s;
for(string& token : tokens) {
if(token == "+" || token == "-" || token == "*" || token == "/") {
int b = s.top(); s.pop();
int a = s.top(); s.pop();
if(token == "+") s.push(a + b);
else if(token == "-") s.push(a - b);
else if(token == "*") s.push(a * b);
else s.push(a / b);
} else {
s.push(stoi(token));
}
}
return s.top();
}
5.6 二叉树的中序遍历(LeetCode 94)
cpp复制vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> s;
TreeNode* curr = root;
while(curr || !s.empty()) {
while(curr) {
s.push(curr);
curr = curr->left;
}
curr = s.top();
s.pop();
res.push_back(curr->val);
curr = curr->right;
}
return res;
}
5.7 岛屿数量(LeetCode 200)
cpp复制int numIslands(vector<vector<char>>& grid) {
if(grid.empty()) return 0;
int m = grid.size(), n = grid[0].size();
int count = 0;
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(grid[i][j] == '1') {
++count;
queue<pair<int,int>> q;
q.push({i,j});
grid[i][j] = '0';
while(!q.empty()) {
auto p = q.front();
q.pop();
int x = p.first, y = p.second;
if(x > 0 && grid[x-1][y] == '1') {
q.push({x-1,y});
grid[x-1][y] = '0';
}
if(x < m-1 && grid[x+1][y] == '1') {
q.push({x+1,y});
grid[x+1][y] = '0';
}
if(y > 0 && grid[x][y-1] == '1') {
q.push({x,y-1});
grid[x][y-1] = '0';
}
if(y < n-1 && grid[x][y+1] == '1') {
q.push({x,y+1});
grid[x][y+1] = '0';
}
}
}
}
}
return count;
}
5.8 每日温度(LeetCode 739)
cpp复制vector<int> dailyTemperatures(vector<int>& T) {
stack<int> s;
vector<int> res(T.size(), 0);
for(int i = 0; i < T.size(); ++i) {
while(!s.empty() && T[i] > T[s.top()]) {
int idx = s.top();
s.pop();
res[idx] = i - idx;
}
s.push(i);
}
return res;
}
5.9 打开转盘锁(LeetCode 752)
cpp复制int openLock(vector<string>& deadends, string target) {
unordered_set<string> dead(deadends.begin(), deadends.end());
if(dead.count("0000")) return -1;
queue<string> q;
q.push("0000");
unordered_set<string> visited;
visited.insert("0000");
int steps = 0;
while(!q.empty()) {
int size = q.size();
for(int i = 0; i < size; ++i) {
string curr = q.front();
q.pop();
if(curr == target) return steps;
for(int j = 0; j < 4; ++j) {
for(int k = -1; k <= 1; k += 2) {
string next = curr;
next[j] = (next[j] - '0' + k + 10) % 10 + '0';
if(!dead.count(next) && !visited.count(next)) {
visited.insert(next);
q.push(next);
}
}
}
}
++steps;
}
return -1;
}
5.10 柱状图中最大的矩形(LeetCode 84)
cpp复制int largestRectangleArea(vector<int>& heights) {
stack<int> s;
s.push(-1);
int maxArea = 0;
for(int i = 0; i < heights.size(); ++i) {
while(s.top() != -1 && heights[s.top()] >= heights[i]) {
int h = heights[s.top()];
s.pop();
int w = i - s.top() - 1;
maxArea = max(maxArea, h * w);
}
s.push(i);
}
while(s.top() != -1) {
int h = heights[s.top()];
s.pop();
int w = heights.size() - s.top() - 1;
maxArea = max(maxArea, h * w);
}
return maxArea;
}
6. 性能优化与常见陷阱
6.1 容器选择对性能的影响
在性能敏感场景中,底层容器的选择至关重要:
-
stack的容器选择:
- vector:适合频繁push/pop且元素数量可预测
- deque:适合元素数量变化大的场景
- list:几乎不需要,除非在中间插入的特殊需求
-
queue的容器选择:
- deque:默认选择,首尾操作高效
- list:当需要频繁在中间插入时才考虑
实测性能对比(100万次操作):
| 操作 | vector | deque | list |
|---|---|---|---|
| push | 12ms | 15ms | 32ms |
| pop | 8ms | 10ms | 28ms |
| 内存占用 | 低 | 中 | 高 |
6.2 线程安全注意事项
标准库的stack和queue都不是线程安全的。多线程环境下需要额外保护:
cpp复制template<typename T>
class ThreadSafeStack {
stack<T> s;
mutex m;
public:
void push(const T& value) {
lock_guard<mutex> lock(m);
s.push(value);
}
bool try_pop(T& value) {
lock_guard<mutex> lock(m);
if(s.empty()) return false;
value = s.top();
s.pop();
return true;
}
bool empty() const {
lock_guard<mutex> lock(m);
return s.empty();
}
};
6.3 递归转非递归的栈技巧
许多递归算法可以手动用栈转化为迭代版本:
cpp复制// 递归版快速排序
void quickSort(vector<int>& arr, int low, int high) {
if(low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// 非递归版快速排序
void quickSortIterative(vector<int>& arr, int low, int high) {
stack<pair<int,int>> s;
s.push({low, high});
while(!s.empty()) {
auto p = s.top();
s.pop();
int l = p.first, h = p.second;
if(l >= h) continue;
int pi = partition(arr, l, h);
s.push({l, pi-1});
s.push({pi+1, h});
}
}
6.4 内存管理陷阱
-
栈溢出:递归太深或大对象栈分配导致
- 解决方案:改用堆分配或迭代算法
-
迭代器失效:在遍历时修改容器
cpp复制stack<int> s; // 错误示范: for(auto it = s.begin(); it != s.end(); ++it) { s.pop(); // 迭代器失效 } -
异常安全:
cpp复制void unsafe() { stack<SomeClass> s; s.push(SomeClass()); // 可能抛出异常 // 如果这里抛出异常,栈可能处于不一致状态 } void safe() { stack<SomeClass> s; auto temp = std::make_unique<SomeClass>(); // 先在外部构造 s.push(*temp); // 不会抛出异常 }
6.5 实际项目中的设计模式
-
撤销操作:用栈实现命令模式
cpp复制class Command { public: virtual void execute() = 0; virtual void undo() = 0; }; class CommandManager { stack<unique_ptr<Command>> undoStack; stack<unique_ptr<Command>> redoStack; public: void execute(unique_ptr<Command> cmd) { cmd->execute(); undoStack.push(move(cmd)); // 清空redo栈 while(!redoStack.empty()) redoStack.pop(); } void undo() { if(undoStack.empty()) return; auto cmd = move(undoStack.top()); undoStack.pop(); cmd->undo(); redoStack.push(move(cmd)); } }; -
事件队列:游戏开发中的主循环
cpp复制class EventQueue { queue<unique_ptr<Event>> events; mutex m; public: void push(unique_ptr<Event> event) { lock_guard<mutex> lock(m); events.push(move(event)); } void process() { unique_ptr<Event> event; { lock_guard<mutex> lock(m); if(events.empty()) return; event = move(events.front()); events.pop(); } event->handle(); } };
