C++ stack 和 queue 完全指南:适配器模式与双端队列的奥秘

一、stack ------ 后进先出的栈

1.1 stack 的介绍

std::stack 是一个容器适配器,它提供 后进先出(LIFO) 的数据结构。你可以把它想象成一摞盘子:最后放上去的盘子最先被取走。栈只允许在顶部 进行插入(push)和删除(pop)操作,访问也只能访问顶部元素(top)。

栈的典型应用场景:

  • 函数调用栈(保存返回地址、局部变量)

  • 表达式求值(中缀转后缀、逆波兰表达式计算)

  • 括号匹配、撤销操作(Undo)等

1.2 stack 的常用接口

函数 说明
push(x) 将元素 x 压入栈顶
pop() 弹出栈顶元素(无返回值)
top() 返回栈顶元素的引用
empty() 判断栈是否为空
size() 返回栈中元素个数
复制代码
#include <stack>
#include <iostream>
using namespace std;

int main() {
    stack<int> st;
    st.push(10);
    st.push(20);
    st.push(30);
    
    cout << st.top() << endl;   // 30
    st.pop();
    cout << st.top() << endl;   // 20
    cout << st.size() << endl;  // 2
    
    return 0;
}

1.3 经典 OJ 题:最小栈

题目要求:实现一个能在 O(1) 时间内获取栈中最小元素的栈。思路:使用两个栈,一个正常存元素,另一个存当前最小值。

复制代码
class MinStack {
public:
    void push(int x) {
        _elem.push(x);
        if (_min.empty() || x <= _min.top())
            _min.push(x);
    }
    void pop() {
        if (_elem.top() == _min.top())
            _min.pop();
        _elem.pop();
    }
    int top() { return _elem.top(); }
    int getMin() { return _min.top(); }
private:
    stack<int> _elem;
    stack<int> _min;
};

1.4 栈的弹出压入序列

给定入栈序列和出栈序列,判断是否可能是同一个栈的弹出顺序。模拟入栈过程,当栈顶等于出栈当前元素时即弹出。

复制代码
bool IsPopOrder(vector<int> pushV, vector<int> popV) {
    if (pushV.size() != popV.size()) return false;
    stack<int> s;
    int pushIdx = 0, popIdx = 0;
    while (popIdx < popV.size()) {
        while (s.empty() || s.top() != popV[popIdx]) {
            if (pushIdx < pushV.size())
                s.push(pushV[pushIdx++]);
            else
                return false;
        }
        s.pop();
        popIdx++;
    }
    return true;
}

1.5 逆波兰表达式求值

逆波兰表达式(后缀表达式)无需括号,运算符在操作数之后。用栈求值:遇到数字压栈,遇到运算符则弹出两个数字计算后压回。

复制代码
int evalRPN(vector<string>& tokens) {
    stack<int> s;
    for (auto& str : tokens) {
        if (str == "+" || str == "-" || str == "*" || str == "/") {
            int right = s.top(); s.pop();
            int left = s.top(); s.pop();
            switch (str[0]) {
                case '+': s.push(left + right); break;
                case '-': s.push(left - right); break;
                case '*': s.push(left * right); break;
                case '/': s.push(left / right); break;
            }
        } else {
            s.push(atoi(str.c_str()));
        }
    }
    return s.top();
}

1.6 stack 的模拟实现

stack 本身不存储数据,而是封装一个底层容器(默认 deque),所有操作都转发给底层容器的相应成员。

复制代码
namespace bit {
    template<class T, class Container = deque<T>>
    class stack {
    public:
        void push(const T& x) { _c.push_back(x); }
        void pop() { _c.pop_back(); }
        T& top() { return _c.back(); }
        const T& top() const { return _c.back(); }
        size_t size() const { return _c.size(); }
        bool empty() const { return _c.empty(); }
    private:
        Container _c;
    };
}

二、queue ------ 先进先出的队列

2.1 queue 的介绍

std::queue 也是一个容器适配器,提供 先进先出(FIFO) 的行为,就像排队一样:队尾入队(push),队头出队(pop),可以访问队头(front)和队尾(back)。

应用场景:

  • 任务队列(生产者-消费者模型)

  • 广度优先搜索(BFS)

  • 打印机任务缓冲等

2.2 queue 的常用接口

函数 说明
push(x) 在队尾插入元素 x
pop() 弹出队头元素
front() 返回队头元素的引用
back() 返回队尾元素的引用
empty() 判空
size() 返回元素个数
复制代码
#include <queue>
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << q.front() << endl;  // 1
q.pop();
cout << q.front() << endl;  // 2

2.3 用队列实现栈(OJ 练习)

使用两个队列模拟栈的后进先出行为。思路:每次压入时,将新元素放入非空队列,然后把另一个队列的所有元素依次搬过来,保证新元素在队头。

2.4 queue 的模拟实现

queue 需要支持尾插和头删,因此底层容器不能是 vector(头删效率低),通常用 dequelist

复制代码
namespace bit {
    template<class T, class Container = deque<T>>
    class queue {
    public:
        void push(const T& x) { _c.push_back(x); }
        void pop() { _c.pop_front(); }
        T& front() { return _c.front(); }
        const T& front() const { return _c.front(); }
        T& back() { return _c.back(); }
        const T& back() const { return _c.back(); }
        size_t size() const { return _c.size(); }
        bool empty() const { return _c.empty(); }
    private:
        Container _c;
    };
}

三、priority_queue ------ 优先级队列(堆)

3.1 介绍

priority_queue 是一个优先队列 ,它的底层是 (默认大顶堆)。每次 top() 返回的是优先级最高的元素(最大或最小)。它的接口与 stackqueue 类似,但内部使用了堆算法(make_heappush_heappop_heap)。

3.2 使用

默认是大堆,即最大元素在堆顶。若要小堆,可以指定 greater 比较器。

复制代码
#include <queue>
#include <vector>
#include <functional>  // greater

priority_queue<int> maxHeap;   // 大堆
maxHeap.push(3); maxHeap.push(1); maxHeap.push(4);
cout << maxHeap.top() << endl; // 4

// 小堆:参数3个,类型、容器、比较类
priority_queue<int, vector<int>, greater<int>> minHeap;
minHeap.push(3); minHeap.push(1); minHeap.push(4);
cout << minHeap.top() << endl; // 1

对于自定义类型,需要重载 <> 运算符(或者传入仿函数)。

3.3 priority_queue 模拟实现

底层是一个数组(如 vector),利用堆的向下调整和向上调整算法。

复制代码
namespace bit {
    template<class T, class Container = vector<T>, class Compare = less<T>>
    class priority_queue {
    public:
        void push(const T& x) {
            _c.push_back(x);
            push_heap(_c.begin(), _c.end(), Compare());
        }
        void pop() {
            pop_heap(_c.begin(), _c.end(), Compare());
            _c.pop_back();
        }
        T& top() { return _c.front(); }
        bool empty() const { return _c.empty(); }
        size_t size() const { return _c.size(); }
    private:
        Container _c;
    };
}

实际上,STL 提供了 push_heap / pop_heap 算法,我们也可以手写 adjust_up / adjust_down

四、容器适配器与 deque

4.1 什么是适配器?

适配器 是一种设计模式,它将一个类的接口转换为用户期望的另一个接口。在 STL 中,stackqueue 没有自己的数据结构,它们只是对已有容器(如 deque)的接口进行限制和包装,从而提供特定的行为。

4.2 为什么选择 deque 作为默认底层?

STL 中 stackqueue 的默认底层容器是 deque(双端队列)。deque 具有以下特点:

  • 支持 O(1) 的头尾插入/删除

  • 支持随机访问(但效率不如 vector

  • 在内存布局上,它是分段连续的(由多个固定大小的缓冲区组成,并由一个中控数组管理)

对于 stack 只需要尾端操作,对于 queue 需要头尾操作,deque 恰好都能高效支持。为什么不直接用 vectorstack?因为 vector 也能做,但 deque 在扩容时不需要搬移所有元素(只需分配新的缓冲区),且 deque 提供了 push_front,通用性更好。为什么不直接用 listlist 每个节点额外占用指针空间,缓存不友好,而 deque 空间利用率更高。

4.3 deque 的原理简述

deque 逻辑上连续,物理上分段。它维护一个中控数组(map),每个元素是一个指针,指向一块固定大小的缓冲区(buffer)。当在头部或尾部插入时,如果当前缓冲区满了,就分配新的缓冲区并更新中控数组。随机访问时,通过中控数组和缓冲区偏移量计算实际地址。

deque 的迭代器非常复杂,需要维护当前缓冲区指针、缓冲区起始/结束边界,以及中控数组的位置,以实现跨缓冲区的自增/自减。

4.4 deque 的缺陷

  • 遍历效率低 :因为迭代器需要频繁判断是否到达缓冲区边界,比 vector 的连续内存遍历慢。

  • 随机访问虽为 O(1),但常数较大:需要两次解引用(中控+缓冲区)。

  • 中间插入/删除慢 :需要搬移元素,不如 list 高效。

因此,除非需要头尾高效操作且偶尔随机访问,否则一般场景优先用 vectorlist

五、stack/queue 与 vector/list 的对比

特性 stack queue vector list
数据结构 适配器 适配器 动态数组 双向链表
访问方式 仅顶部 仅头尾 随机访问 双向顺序访问
插入删除 仅顶部 O(1) 头尾 O(1) 尾部 O(1) 均摊,中间 O(n) 任意位置 O(1)
底层容器 deque(默认) deque(默认) 自身 自身
迭代器 随机访问迭代器 双向迭代器

六、总结

  • stack:后进先出,适合需要"回溯"或"撤销"的场景。

  • queue:先进先出,适合任务排队、广度优先遍历。

  • priority_queue:堆结构,适合动态获取最值的场景。

  • 适配器模式:通过限制已有容器的接口,实现新的语义,降低了代码复用成本。

  • deque 作为默认底层容器,平衡了内存连续性和头尾操作效率,是 stackqueue 的理想搭档。

掌握这些容器适配器,不仅能写出更清晰、高效的代码,还能深入理解 STL 的设计哲学。在接下来的学习中,你可以尝试用 stack 解决括号匹配、表达式求值,用 queue 实现层序遍历,用 priority_queue 解决 TopK 问题。下一篇我们将探讨关联式容器 mapset,敬请期待!

练习题推荐

  • LeetCode 20. 有效的括号

  • LeetCode 150. 逆波兰表达式求值

  • LeetCode 232. 用栈实现队列

  • LeetCode 225. 用队列实现栈

  • LeetCode 215. 数组中的第K个最大元素(优先队列)

相关推荐
casual~1 小时前
十六届蓝桥杯国赛个人题解
经验分享·学习·算法·蓝桥杯
代码改善世界1 小时前
【C++进阶】红黑树模拟实现mymap和myset
开发语言·c++
方也_arkling1 小时前
【Java-Day18】API篇-Arrays
java·算法·排序算法
吴可可1231 小时前
Curve.GetSplitCurves高效分割技巧
算法
断点之下1 小时前
从C的struct到C++的class:封装、this指针、三大特性入门
开发语言·c++
誰能久伴不乏2 小时前
工业级 Modbus 上位机架构:基于滴答引擎与状态锁的高并发调度器
c++·qt·架构
硅谷秋水2 小时前
Qwen-VLA:跨任务、环境与机器人形态的视觉-语言-动作统一建模
人工智能·深度学习·算法·计算机视觉·语言模型·机器人
谷谷地图下载器2 小时前
全球、台湾省的无水印·街景数据(离线数据),专为可视化项目定制,支持国产化
javascript·c++·3d·arcgis·sqlite