stack和queue_介绍、用法及模拟实现

个人主页 : 流年如梦

专栏 : 《零基础轻松入门C语言》 《数据结构:从入门到掌握》 《C++编程研习录》

文章目录

  • 一.stack的介绍和使用
    • [1.1 stack的介绍](#1.1 stack的介绍)
    • [1.2 stack的常用接口](#1.2 stack的常用接口)
      • [1.2.1 最小栈](#1.2.1 最小栈)
      • [1.2.2 栈的弹出序列](#1.2.2 栈的弹出序列)
      • [1.2.3 逆波兰表达式求值](#1.2.3 逆波兰表达式求值)
    • [1.3 stack 的模拟实现](#1.3 stack 的模拟实现)
  • [二.queue 的介绍和使用](#二.queue 的介绍和使用)
    • [2.1 queue的介绍](#2.1 queue的介绍)
    • [2.2 queue的常用接口](#2.2 queue的常用接口)
    • [2.3 queue的模拟实现](#2.3 queue的模拟实现)
  • 三.priority_queue的介绍和使用
    • [3.1 priority_queue的介绍](#3.1 priority_queue的介绍)
    • [3.2 priority_queue的常用接口](#3.2 priority_queue的常用接口)
    • [3.3 OJ 实战:数组中第 K 个最大元素](#3.3 OJ 实战:数组中第 K 个最大元素)
    • [3.4 priority_queue的模拟实现](#3.4 priority_queue的模拟实现)
  • 四.容器适配器与deque
    • [4.1 什么是适配器模式](#4.1 什么是适配器模式)
    • [4.2 STL 适配器的模板定义](#4.2 STL 适配器的模板定义)
    • [4.3 deque简单介绍](#4.3 deque简单介绍)
    • [4.4 为什么选择deque作为默认底层](#4.4 为什么选择deque作为默认底层)
    • [4.5 标准库式模拟实现(可指定底层容器)](#4.5 标准库式模拟实现(可指定底层容器))
  • 🎯总结
  • ⚠️易错点

前言 stack、queue、priority_queue 是 STL 中三类典型的**容器适配器**。它们并不直接实现底层数据结构,而是对已有容器进行封装,对外提供语义明确的操作接口。其中,stack 遵循后进先出(LIFO)原则,仅允许在栈顶进行操作;queue 遵循先进先出(FIFO)原则,队尾入队、队头出队;priority_queue 则基于堆结构,保证堆顶元素始终拥有最高优先级。本文将从接口使用与经典算法场景入手,系统讲解这三类适配器,并逐步完成它们的模拟实现,深入剖析容器适配器设计模式与底层 deque 的原理,同时对比不同底层容器的优劣。

一.stack的介绍和使用

1.1 stack的介绍

stack 是**后进先出(Last In First Out)**的容器适配器,仅支持栈顶一端的插入、删除与访问,不支持遍历与随机访问。默认以 deque 作为底层容器,也可指定 vector、list 作为底层实现

典型应用场景:括号匹配、表达式求值、函数调用栈、模拟递归等

1.2 stack的常用接口

使用 stack 必须包含头文件:#include <stack>

接口 说明
stack() 构造空栈
empty() 检测栈是否为空,空返回 true
size() 返回栈中元素个数
top() 返回栈顶元素的引用
push() 将元素压入栈顶
pop() 弹出栈顶元素

1.2.1 最小栈

题目:

思路:双栈实现,元素栈存数据,辅助栈存每一步的最小值,两个栈同步操作,保证getMin时间复杂度 O (1)

代码:

cpp 复制代码
class MinStack {
public:
    void push(int x) {
        _elem.push(x);
        // 空栈或新元素更小,则压入辅助栈
        if(_min.empty() || x <= _min.top())
            _min.push(x);
    }
    void pop() {
        // 弹出元素等于当前最小值,辅助栈同步弹出
        if(_min.top() == _elem.top())
            _min.pop();
        _elem.pop();
    }
    int top() { return _elem.top(); }
    int getMin() { return _min.top(); }
private:
    stack<int> _elem; // 存储所有元素
    stack<int> _min;  // 维护最小值序列
};

1.2.2 栈的弹出序列

题目:

思路:用辅助栈模拟压入弹出过程,按弹出序列匹配栈顶,匹配成功则弹出,全部匹配完成则合法

代码:

cpp 复制代码
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        if(pushed.size() != popped.size())
            return false;
        
        stack<int> st;
        int inIdx = 0, outIdx = 0;
        while(outIdx < popped.size()) {
            // 栈空或栈顶不匹配,持续压栈
            while(st.empty() || st.top() != popped[outIdx]) {
                if(inIdx >= pushed.size())
                    return false;
                st.push(pushed[inIdx++]);
            }
            // 栈顶匹配则弹出
            st.pop();
            outIdx++;
        }
        return true;
    }
};

1.2.3 逆波兰表达式求值

题目:

思路:遇到数字压栈,遇到运算符弹出两个数计算,结果重新压栈,最终栈顶即为表达式结果。注意操作数顺序,先弹出的是右操作数

代码:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto& str : tokens) {
            // 数字直接入栈
            if(str != "+" && str != "-" && str != "*" && str != "/") {
                st.push(stoi(str));
            }
            else {
                // 先弹出右操作数,再弹出左操作数
                int right = st.top(); st.pop();
                int left = st.top(); st.pop();
                switch(str[0]) {
                    case '+': st.push(left + right); break;
                    case '-': st.push(left - right); break;
                    case '*': st.push(left * right); break;
                    case '/': st.push(left / right); break;
                }
            }
        }
        return st.top();
    }
};

1.3 stack 的模拟实现

栈仅需尾插尾删,使用 vector 封装即可实现

cpp 复制代码
#include <vector>
namespace my
{
template<class T>
class stack {
public:
    stack() {}

    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:
    vector<T> _c; // 底层容器
};
}

二.queue 的介绍和使用

2.1 queue的介绍

queue 是**先进先出(First In First Out)**的容器适配器,支持队尾插入、队头删除,不支持随机访问与遍历。默认以 deque 作为底层容器,也可指定 list 作为底层实现

典型应用场景:广度优先搜索(BFS)、任务队列、消息队列、缓存等

2.2 queue的常用接口

使用 queue 必须包含头文件:#include <queue>

接口 说明
queue() 构造空队列
empty() 检测队列是否为空,空返回 true
size() 返回队列中元素个数
front() 返回队头元素的引用
back() 返回队尾元素的引用
push() 队尾插入元素
pop() 队头弹出元素

2.3 queue的模拟实现

队列需要尾插头删,vector 头删效率为 O (N),因此选用 list 封装实现

cpp 复制代码
#include <list>
namespace my
{
template<class T>
class queue {
public:
    queue() {}

    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:
    list<T> _c; // 底层容器
};
}

三.priority_queue的介绍和使用

3.1 priority_queue的介绍

priority_queue 又称优先级队列,是基于堆结构 的容器适配器,保证队顶元素始终是优先级最高的元素。默认以 vector 作为底层容器,默认构建大堆(顶部元素最大)

典型应用场景:TopK 问题、任务优先级调度、合并有序链表等

3.2 priority_queue的常用接口

使用 priority_queue 必须包含头文件:#include <queue>,使用 greater 比较器需包含 #include <functional>

接口 说明
priority_queue() 构造空优先级队列
priority_queue(first, last) 用迭代器区间构造
empty() 检测是否为空
top() 返回堆顶(优先级最高)元素
push(x) 插入元素并调整堆结构
pop() 删除堆顶元素并调整堆

注意事项

  1. 默认是大堆,比较器为 less<T>;创建小堆需指定第三个模板参数 greater<T>
  2. 自定义类型放入优先级队列,必须重载 <(大堆)或 >(小堆)运算符

举例:

cpp 复制代码
// 默认大堆,顶部元素最大
priority_queue<int> q1;
// 小堆,顶部元素最小
priority_queue<int, vector<int>, greater<int>> q2;

3.3 OJ 实战:数组中第 K 个最大元素

题目:

思路:利用大顶堆特性,所有元素入堆后,依次弹出 k-1 个最大值,堆顶即为第 k 大元素

代码:

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.end());
        // 弹出前k-1个最大元素
        for(int i = 0; i < k - 1; ++i)
            pq.pop();
        return pq.top();
    }
};

3.4 priority_queue的模拟实现

底层基于 vector 存储,封装堆调整算法实现优先级队列

cpp 复制代码
#include <vector>
#include <algorithm>
namespace my
{
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue {
public:
    priority_queue() {}
    
    template<class InputIterator>
    priority_queue(InputIterator first, InputIterator last)
        : _c(first, last)
    {
        make_heap(_c.begin(), _c.end(), _cmp);
    }

    void push(const T& x) {
        _c.push_back(x);
        push_heap(_c.begin(), _c.end(), _cmp);
    }
    void pop() {
        pop_heap(_c.begin(), _c.end(), _cmp);
        _c.pop_back();
    }
    const T& top() const { return _c.front(); }

    size_t size() const { return _c.size(); }
    bool empty() const { return _c.empty(); }

private:
    Container _c;
    Compare _cmp;
};
}

四.容器适配器与deque

4.1 什么是适配器模式

适配器是一种结构型设计模式,核心作用是将一个类的接口转换成客户期望的另一种接口,让原本接口不兼容的类可以协同工作。STL 容器适配器就是封装底层容器,对外提供统一的特定语义接口,屏蔽底层容器的差异

本质是接口转换与封装,属于黑箱复用,耦合度低于继承

4.2 STL 适配器的模板定义

stack 和 queue 第二个模板参数指定底层容器,默认均为 deque:

cpp 复制代码
template<class T, class Container = deque<T>> class stack;
template<class T, class Container = deque<T>> class queue;

priority_queue 默认底层为 vector,比较器默认 less:

cpp 复制代码
template<class T, class Container = vector<T>, class Compare = less<T>> class priority_queue;

4.3 deque简单介绍

deque(双端队列)是支持两端 O (1) 插入删除的序列容器,底层是分段连续的内存块,通过中控器管理多个缓冲区,对外模拟连续空间的假象。

  • 优点:头尾插入删除 O (1),扩容无需大量移动元素,空间利用率高于 list
  • 缺点:迭代器设计复杂,遍历效率低,随机访问性能弱于 vector

4.4 为什么选择deque作为默认底层

stack 和 queue 不需要遍历、不需要随机访问,仅在一端或两端操作,完美避开 deque 遍历效率低的缺点;同时 deque 头尾操作效率高,扩容成本低于 vector,空间利用率优于 list,因此成为栈与队列的默认底层容器

4.5 标准库式模拟实现(可指定底层容器)

cpp 复制代码
// stack 通用版本,支持自定义底层容器
template<class T, class Con = deque<T>>
class stack {
public:
    void push(const T& x) { _c.push_back(x); }
    void pop() { _c.pop_back(); }
    T& top() { return _c.back(); }
    size_t size() const { return _c.size(); }
    bool empty() const { return _c.empty(); }
private:
    Con _c;
};

// queue 通用版本,支持自定义底层容器
template<class T, class Con = deque<T>>
class queue {
public:
    void push(const T& x) { _c.push_back(x); }
    void pop() { _c.pop_front(); }
    T& front() { return _c.front(); }
    T& back() { return _c.back(); }
    size_t size() const { return _c.size(); }
    bool empty() const { return _c.empty(); }
private:
    Con _c;
};

🎯总结

  • stack 是后进先出适配器,仅栈顶操作,默认底层 deque,适用于括号匹配、表达式求值等场景
  • queue 是先进先出适配器,队尾入队头出,默认底层 deque,适用于 BFS、任务队列等场景
  • priority_queue 是堆结构适配器,默认大堆,底层 vector,适用于 TopK、优先级调度等场景
  • 容器适配器本质是接口封装,属于适配器设计模式,本身不实现原生数据结构
  • deque 是分段连续双端队列,头尾操作高效,遍历性能弱,完美适配栈与队列的操作特性
  • 最小栈、弹出序列验证、逆波兰求值、TopK 是栈与优先级队列的经典算法应用
  • 适配器支持自定义底层容器,可根据场景选择 vector、list、deque 实现

⚠️易错点

  • 混淆 stack 和 queue 的进出方向,栈尾进尾出,队列尾进头出
  • priority_queue 默认是大堆,误以为是小堆导致 TopK 结果错误
  • 自定义类型放入 priority_queue,未重载比较运算符导致编译错误
  • 误以为 stack 和 queue 是容器,实际是容器适配器
  • 用 vector 实现 queue,头删操作 O (N) 效率极低
  • deque 随机访问效率低于 vector,遍历场景优先用 vector
  • 逆波兰表达式求值时,操作数左右顺序搞反,减法除法结果出错
  • 最小栈中相等元素不同时压入辅助栈,pop 后最小值错误
  • priority_queue 底层是堆,插入删除都需要调整堆结构,时间复杂度 O (logN)
  • 适配器底层容器不支持对应操作时编译报错,比如用 vector 做 queue 底层会因pop_front失效

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长

❤️ 点赞 鼓励原创,让优质内容被更多人看见

⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅

💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
chenbingjie_c1 小时前
C++ 模板进阶:从 “会用“ 到 “懂底层“(非类型参数・特化・分离式编译)
c++·后端
world78112261 小时前
【C++】set 和 map
java·前端·c++·set·map
lunar_coder2 小时前
哈希扩展 -- 位图思想
c++·算法·哈希算法
kiracrimson3 小时前
编码简述:内存里没有文字,只有一张对照表
开发语言·c++·二进制·计算机组成原理
磊磊cpp3 小时前
CRC8 校验原理与实现详解(附可交互动画演示)
c++·交互
Mortalbreeze4 小时前
0 - 1 BFS 算法详解:从基本原理到经典题目建模
数据结构·c++·算法·图论·宽度优先
进化中的刀盾兽6 小时前
从零实现 C++ Json-Rpc(十):把消息交给真实连接——MuduoConnection 与 ConnectionFactory
c++·rpc
青瓦梦滋12 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http