【C++初阶】STL—— Stack & Queue 从入门到精通:容器适配器、迭代器与经典面试题

🎈主页传送门:良木生香

🔥个人专栏:《C语言》《数据结构-初阶》《鼠鼠的C++学习之路》《Linux系统编程》

🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离


一、前言

在 C++ STL 中,stackqueue 是非常常用的容器。但很多人只停留在"会用"的层面,对其底层实现、设计思想以及经典应用场景并不熟悉。那么今天就从容器适配器 的设计思想出发,深入讲解 stackqueue 的底层原理,并配合经典面试题,帮助大家彻底掌握这部分知识。


二、Stack 与 Queue 的本质:容器适配器

2.1 什么是容器适配器?

适配器(Adapter) 是一种设计模式:将一个类的接口转换成用户期望的另一个接口。

stackqueue 本质上不是独立的容器,而是在已有容器基础上封装的适配器。它们不自己管理数据,而是复用底层容器的接口,只暴露栈/队列特有的操作。

cpp 复制代码
// stack 的声明:底层默认用 deque
template <class T, class Container = deque<T>>
class stack;

// queue 的声明:底层默认也用 deque
template <class T, class Container = deque<T>>
class queue;

2.2 为什么叫"适配器"?

复制代码
    底层容器(Vector/List/Deque)
    ┌─────────────────────────────┐
    │  push_back()     pop_back() │
    │  push_front()    pop_front()│
    │  insert()        erase()    │
    │  operator[]      front()    │
    │  ...                        │
    └──────────┬──────────────────┘
               │ 适配(限制接口)
               ↓
    栈接口(只暴露这几个)
    ┌─────────────┐
    │  push()     │  ← 底层调用 push_back
    │  pop()      │  ← 底层调用 pop_back
    │  top()      │  ← 底层调用 back
    │  size()     │
    │  empty()    │
    └─────────────┘

2.3 底层容器的选择

容器适配器 默认底层 可选底层 说明
stack deque<T> vector<T>list<T> 只需要尾插尾删
queue deque<T> list<T> 需要头删尾插,不能用 vector (没有 pop_front

⚠️ 队列不能用 vector 的 pop_front(),因为 vector 头删需要 O(n) 移动所有元素。


三、Stack:后进先出(LIFO)

3.1 核心特性

复制代码
          push(5)
          ↓
     ┌─────────┐
     │    5    │ ← 栈顶(最后入栈,最先出栈)
     ├─────────┤
     │    4    │
     ├─────────┤
     │    3    │
     ├─────────┤
     │    2    │
     ├─────────┤
     │    1    │ ← 栈底(最先入栈,最后出栈)
     └─────────┘

3.2 模拟实现(基于 Vector)

cpp 复制代码
template<typename T, typename Container = vector<T>>
class Stack {
public:
    void push(const T& x) { _con.push_back(x); }
    void pop() { _con.pop_back(); }
    T& top() { return _con.back(); }
    size_t size() const { return _con.size(); }
    bool empty() const { return _con.empty(); }
private:
    Container _con;
};

3.3 时间复杂度

操作 复杂度 说明
push O(1) 尾插
pop O(1) 尾删
top O(1) 访问末尾元素

四、Queue:先进先出(FIFO)

4.1 核心特性

复制代码
    出队(pop)              入队(push)
         ↓                      ↓
    ┌───────┬───────┬───────┬───────┐
    │  1    │  2    │  3    │  4    │
    └───────┴───────┴───────┴───────┘
         ↑                      ↑
       front                  back

    入队顺序:1 → 2 → 3 → 4
    出队顺序:1 → 2 → 3 → 4(完全相同)

4.2 模拟实现(基于 List)

cpp 复制代码
template<typename T, typename Container = list<T>>
class Queue {
public:
    void push(const T& x) { _con.push_back(x); }   // 队尾入
    void pop() { _con.pop_front(); }                  // 队头出
    T& front() { return _con.front(); }
    T& back() { return _con.back(); }
    size_t size() const { return _con.size(); }
    bool empty() const { return _con.empty(); }
private:
    Container _con;
};

4.3 循环队列(数组实现)

cpp 复制代码
template<typename T, size_t N>
class CircularQueue {
    T _data[N];
    size_t _front = 0, _rear = 0, _size = 0;
public:
    bool push(const T& x) {
        if (_size == N) return false;
        _data[_rear] = x;
        _rear = (_rear + 1) % N;
        ++_size;
        return true;
    }
    bool pop() {
        if (_size == 0) return false;
        _front = (_front + 1) % N;
        --_size;
        return true;
    }
    T& front() { return _data[_front]; }
    bool empty() const { return _size == 0; }
    bool full() const { return _size == N; }
};

五、List 迭代器的 operator-> 重载

5.1 问题场景

当 List 中存储的是结构体时,如何通过迭代器访问成员?

cpp 复制代码
struct Point {
    int _x, _y;
};

List<Point> points;
points.push_back({1, 2});

auto it = points.begin();
// (*it)._x = 10;   // 方式1:先解引用再访问成员
// it->_x = 10;      // 方式2:更简洁,需要 operator-> 支持

6.2 operator-> 的实现

cpp 复制代码
template<typename T, typename Ref, typename Ptr>
struct __ListIterator {
    Node* _node;
    
    // 解引用,返回 data 的引用
    Ref operator*() { return _node->_data; }
    
    // 返回 data 的指针
    Ptr operator->() { return &_node->_data; }
};

// it->x 的调用过程:
// it.operator->() → &_node->_data → (&_node->_data)->x
// 编译器会自动优化为直接访问成员

六、经典面试题详解

6.1 最小栈(O(1) 获取最小值)

思路:用两个栈,一个存数据,一个存最小值。

cpp 复制代码
class MinStack {
    stack<int> _data;   // 数据栈
    stack<int> _min;    // 辅助栈:存当前最小值
    
public:
    void push(int x) {
        _data.push(x);
        // 如果 x 比之前的最小值还小(或等于),压入 x
        if (_min.empty() || x <= _min.top()) {
            _min.push(x);
        } else {
            _min.push(_min.top());  // 重复压入当前最小值
        }
    }
    
    void pop() {
        _data.pop();
        _min.pop();
    }
    
    int top() { return _data.top(); }
    int getMin() { return _min.top(); }
};

// push 5: _data=[5],   _min=[5]
// push 2: _data=[5,2], _min=[5,2]
// push 7: _data=[5,2,7], _min=[5,2,2]
// push 1: _data=[5,2,7,1], _min=[5,2,2,1]
// getMin() → 1
// pop() → _data=[5,2,7], _min=[5,2,2]
// getMin() → 2

⚠️ 注意 :当 data > min.top() 时,minst 不 push。但相同 min 时两边同时进,否则删除后 minst 不更新!

6.2 栈的压入/弹出序列验证

思路:用辅助栈模拟入栈过程,同时比较出栈序列。

cpp 复制代码
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
    stack<int> st;
    int j = 0;  // 指向 popped 的索引
    
    for (int x : pushed) {
        st.push(x);  // 入栈序列入栈
        
        // 栈顶与出栈序列比较
        while (!st.empty() && st.top() == popped[j]) {
            st.pop();
            ++j;  // 匹配成功,继续比较下一个
        }
    }
    
    // 当入栈序列走完,但出栈序列不为空,则为 false
    return st.empty();
}

6.3 逆波兰表达式求解

中缀转后缀示例

复制代码
中缀:1 + 2 × (3 + 4) / 5
后缀:1 2 3 4 + × 5 / +

计算过程:
遇到 1:压栈 [1]
遇到 2:压栈 [1, 2]
遇到 3:压栈 [1, 2, 3]
遇到 4:压栈 [1, 2, 3, 4]
遇到 +:弹出 4 和 3,计算 3+4=7,压栈 [1, 2, 7]
遇到 ×:弹出 7 和 2,计算 2×7=14,压栈 [1, 14]
遇到 5:压栈 [1, 14, 5]
遇到 /:弹出 5 和 14,计算 14/5=2,压栈 [1, 2]
遇到 +:弹出 2 和 1,计算 1+2=3,压栈 [3]
结果:3
cpp 复制代码
int evalRPN(vector<string>& tokens) {
    stack<int> st;
    for (const string& token : tokens) {
        if (token == "+" || token == "-" || token == "*" || token == "/") {
            int b = st.top(); st.pop();
            int a = st.top(); st.pop();
            if (token == "+") st.push(a + b);
            else if (token == "-") st.push(a - b);
            else if (token == "*") st.push(a * b);
            else if (token == "/") st.push(a / b);
        } else {
            st.push(stoi(token));
        }
    }
    return st.top();
}

6.4 用两个栈实现队列

cpp 复制代码
class MyQueue {
    stack<int> _in;   // 入队栈
    stack<int> _out;  // 出队栈
    
public:
    void push(int x) { _in.push(x); }
    
    int pop() {
        peek();
        int val = _out.top();
        _out.pop();
        return val;
    }
    
    int peek() {
        if (_out.empty()) {
            while (!_in.empty()) {
                _out.push(_in.top());
                _in.pop();
            }
        }
        return _out.top();
    }
    
    bool empty() { return _in.empty() && _out.empty(); }
};

6.5 二叉树的层序遍历(队列应用)

cpp 复制代码
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> result;
    if (!root) return result;
    
    queue<TreeNode*> q;
    q.push(root);
    
    while (!q.empty()) {
        int levelSize = q.size();
        vector<int> level;
        
        for (int i = 0; i < levelSize; ++i) {
            TreeNode* node = q.front();
            q.pop();
            level.push_back(node->val);
            
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        result.push_back(level);
    }
    return result;
}

七、总结

容器适配器 核心特性 底层结构 典型应用
stack LIFO deque/vector/list 括号匹配、DFS、函数调用、撤销
queue FIFO deque/list BFS、消息队列、任务调度

关键要点回顾

  1. 容器适配器stackqueue 不是独立容器,而是复用底层容器的适配器
  2. 队列不能用 vector :因为 vector 没有高效的 pop_front()
  3. 默认底层stackqueue 默认用 deque
  4. 最小栈:双栈实现,辅助栈同步压入当前最小值
  5. 逆波兰表达式:遇到数字入栈,遇到运算符弹出两个数计算
  6. 栈序列验证:辅助栈模拟入栈,同时比较出栈序列

那么以上就是本次所有的内容了

文章是自己写的哈,有什么描述不对的、不恰当的地方,恳请大佬指正,看到后会第一时间修改,感谢您的阅读~~~~


相关推荐
lbb 小魔仙19 小时前
Python 性能分析工具实战:cProfile、memory_profiler、line_profiler 使用指南
开发语言·python
小小晓.19 小时前
C++小白记:vector
开发语言·c++·算法
tkevinjd19 小时前
力扣72-编辑距离
算法·leetcode·职场和发展
xgc_java19 小时前
在Java里把ONNX/OpenCV/FFmpeg跑稳:28篇bytedeco实战小册完整指南
java·opencv·ffmpeg
Lhappy嘻嘻19 小时前
网络(四)|全网最细网络层原理:IP 协议、IP 地址分类、子网划分、路由转发、NAT 穿透详解
java·笔记·网络协议·计算机网络
小刘学技术19 小时前
AI人工智能决策树分类器:原理、实现与应用
开发语言·人工智能·python·算法·决策树·机器学习·数据挖掘
脱胎换骨-军哥19 小时前
C++ 嵌入式编程实例:从寄存器操作到底层驱动开发
开发语言·c++·驱动开发
大模型码小白19 小时前
Milvus 架构设计:从向量索引到分布式检索,AI 原生存储引擎的内部机制
java·大数据·人工智能·分布式·深度学习·milvus
稚南城才子,乌衣巷风流19 小时前
链表(Linked List):数据结构详解与实战应用
java