【初阶C++】stack和queue

目录

​编辑

本节学习目标

[1 stack 栈](#1 stack 栈)

[1.1 栈概念](#1.1 栈概念)

[1.2 stack 常用成员接口](#1.2 stack 常用成员接口)

示例基础使用

[1.3 OJ 经典栈例题](#1.3 OJ 经典栈例题)

[例题 1:最小栈 MinStack](#例题 1:最小栈 MinStack)

[例题 2:栈的压入、弹出序列](#例题 2:栈的压入、弹出序列)

[例题 3:逆波兰表达式求值](#例题 3:逆波兰表达式求值)

[1.4 stack 模拟实现(容器适配器)](#1.4 stack 模拟实现(容器适配器))

[2 queue 队列](#2 queue 队列)

[2.1 队列概念](#2.1 队列概念)

[2.2 queue 接口](#2.2 queue 接口)

[2.3 queue 模拟实现](#2.3 queue 模拟实现)

[3 priority_queue 优先级队列(堆)](#3 priority_queue 优先级队列(堆))

[3.1 概念](#3.1 概念)

[3.2 priority_queue 接口](#3.2 priority_queue 接口)

[示例 1:内置类型,大堆、小堆](#示例 1:内置类型,大堆、小堆)

[示例 2:自定义类型放入 priority_queue](#示例 2:自定义类型放入 priority_queue)

[OJ 例题:数组第 K 个最大元素](#OJ 例题:数组第 K 个最大元素)

[3.3 priority_queue 模拟实现](#3.3 priority_queue 模拟实现)

[4 容器适配器](#4 容器适配器)

[4.1 适配器模式设计思想](#4.1 适配器模式设计思想)

[4.2 deque 双端队列底层原理(了解)](#4.2 deque 双端队列底层原理(了解))

[4.3 为什么 stack 和 queue 默认选择 deque 作为底层?](#4.3 为什么 stack 和 queue 默认选择 deque 作为底层?)

[4.4 stack、queue 模板模拟完整版本(支持自定义底层容器)](#4.4 stack、queue 模板模拟完整版本(支持自定义底层容器))

📔本篇核心考点总结


本节学习目标

  1. 理解栈 stack 后进先出 LIFO 特性,掌握常用接口;会写最小栈、栈的压入弹出序列、逆波兰表达式
  2. 理解队列 queue 先进先出 FIFO,掌握接口,理解底层容器适配器
  3. 掌握priority_queue优先级队列(堆),分清大堆、小堆,自定义类型比较规则,OJ 找第 K 大元素
  4. 搞懂容器适配器模式 ,理解为什么 STL stack/queue 默认底层选用deque双端队列
  5. 了解 deque 双端队列底层结构、优缺点
  6. 看懂 stack、queue 模板模拟实现代码

1 stack 栈

1.1 栈概念

栈:后进先出 LIFO(Last‑In‑First‑Out) 。 只能在栈顶 top进行插入(push 入栈)、删除(pop 出栈)操作。

  • push():入栈,元素放到栈的顶部
  • pop():出栈,删除栈顶元素
  • top():获取栈顶元素,不会删除元素

类比弹夹:后压进去的子弹,最先打出去。

1.2 stack 常用成员接口

头文件:#include <stack>

表格

函数 功能说明
stack<T> st; 构造空栈对象
empty() 判断栈是否为空,空返回 true,非空 false
size() 返回栈中有效元素个数
top() 返回栈顶元素引用,栈空调用会报错
push(val) 元素 val 入栈,压到栈顶
pop() 删除栈顶元素;无返回值!栈空调用会报错

⚠️注意:

  1. pop()只删除,不返回栈顶元素 ;要拿栈顶先调用top()
  2. 调用top()pop()前建议先!st.empty()判空,否则程序崩溃。

示例基础使用

cpp 复制代码
#include <iostream>
#include <stack>
using namespace std;

int main()
{
    stack<int> st;
    st.push(10);
    st.push(20);
    st.push(30);

    cout << st.size() << endl; //3
    cout << st.top() << endl; //30

    st.pop();
    cout << st.top() << endl; //20

    while(!st.empty())
    {
        cout << st.top() << " ";
        st.pop();
    }
    return 0;
}

1.3 OJ 经典栈例题

例题 1:最小栈 MinStack

需求:实现栈,支持 push、pop、top,还可以getMin()快速获取栈内最小值。 思路:双栈方案

  • _elem:保存全部真实元素
  • _min:专门保存最小值序列;入栈的时候,如果新元素小于等于_min 栈顶,就压入_min;出栈的时候,如果出栈元素等于_min 栈顶,_min 也要同步 pop。
cpp 复制代码
#include<stack>
class MinStack
{
public:
    void push(int x)
    {
        _elem.push(x);
        // 最小栈为空 或者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;
};

例题 2:栈的压入、弹出序列

输入两个序列:pushV 入栈序列,popV 出栈序列,判断 popV 是否是合法的出栈顺序。 模拟思路:

  1. 遍历 popV 出栈序列;
  2. 如果栈为空 或者栈顶不等于当前要出栈的元素,就持续把 pushV 元素压栈;
  3. 如果 pushV 全部压完,栈顶还不等于 popV 当前元素 → 不合法返回 false;
  4. 相等就 pop,移动出栈下标;全部处理完成返回 true。
cpp 复制代码
#include <stack>
#include <vector>
using namespace std;
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size() != popV.size())
            return false;

        stack<int> s;
        int outIdx = 0; //出栈序列下标
        int inIdx = 0;  //入栈序列下标

        while(outIdx < popV.size())
        {
            //栈空 或者栈顶不等于要弹出的元素,持续入栈
            while(s.empty() || s.top() != popV[outIdx])
            {
                if(inIdx < pushV.size())
                {
                    s.push(pushV[inIdx++]);
                }
                else
                {
                    //入栈全部用完,还不匹配,非法
                    return false;
                }
            }
            //栈顶匹配,执行出栈
            s.pop();
            outIdx++;
        }
        return true;
    }
};

例题 3:逆波兰表达式求值

逆波兰(后缀表达式),遇到数字压栈;遇到运算符,弹出右操作数、左操作数,计算后结果重新压回栈。 注意:先 pop 出来是右操作数!顺序不能写反。

cpp 复制代码
#include <stack>
#include <vector>
#include <string>
using namespace std;

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        for(size_t i = 0; i < tokens.size(); ++i)
        {
            string& str = tokens[i];
            if(str != "+" && str != "-" && str != "*" && str != "/")
            {
                //数字,转int压栈
                s.push(atoi(str.c_str()));
            }
            else
            {
                //运算符:先弹出右操作数,再弹出左操作数
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();

                int res;
                switch(str[0])
                {
                case '+': res = left + right; break;
                case '-': res = left - right; break;
                case '*': res = left * right; break;
                case '/': res = left / right; break;
                default: res = 0;
                }
                s.push(res);
            }
        }
        return s.top();
    }
};

课后练习 OJ:用两个栈实现队列

1.4 stack 模拟实现(容器适配器)

stack 本身不存储数据,复用其他容器接口。可以用vector/deque/list作为底层。

vector 的push_backpop_backback()刚好可以实现栈全部功能。

cpp 复制代码
#include<vector>
namespace bite
{
template<class 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:
    std::vector<T> _c;
};
}

2 queue 队列

2.1 队列概念

队列:先进先出 FIFO First‑In‑First‑Out

  • 队尾 back:入队列 push ();
  • 队头 front:出队列 pop ()。

排队买票:先排队的人,先处理离开。

queue 属于容器适配器 ,底层默认使用deque双端队列。 底层容器必须支持:emptysizefrontbackpush_backpop_front

list、deque 都满足要求;vector 不支持高效头删 pop_front,不能做 queue 底层。

2.2 queue 接口

头文件#include <queue>

表格

函数 功能说明
queue<T> q; 构造空队列
empty() 判空,空返回 true
size() 返回元素个数
front() 获取队头元素引用,不删除
back() 获取队尾元素引用,不删除
push(val) 队尾入队列
pop() 删除队头元素,无返回值

⚠️pop () 只删除队头,不返回;取队头必须调用 front ();操作前建议判空。

基础示例:

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;

int main()
{
    queue<int> q;
    q.push(1);
    q.push(2);
    q.push(3);

    cout << q.front() << endl; //1
    cout << q.back() << endl;  //3

    q.pop();
    cout << q.front() << endl; //2

    while(!q.empty())
    {
        cout << q.front() << " ";
        q.pop();
    }
    return 0;
}

课后练习 OJ:用两个队列实现栈

2.3 queue 模拟实现

底层选用std::list,list 支持高效头删 pop_front。

cpp 复制代码
#include <list>
namespace bite
{
template<class T>
class queue
{
public:
    void push(const T& x) { _c.push_back(x); }
    void pop() { _c.pop_front(); }

    T& back() { return _c.back(); }
    const T& back()const { return _c.back(); }

    T& front() { return _c.front(); }
    const T& front()const { return _c.front(); }

    size_t size()const { return _c.size(); }
    bool empty()const { return _c.empty(); }
private:
    std::list<T> _c;
};
}

3 priority_queue 优先级队列(堆)

3.1 概念

priority_queue优先级队列,本质就是堆(heap),容器适配器

默认:大堆(大顶堆),堆顶 top () 永远是容器里面最大的元素 。 底层默认容器是vector;内部调用堆算法make_heappush_heappop_heap维护堆结构。
注意:

  1. pop () 删除的是堆顶最大 / 最小元素,不是最早入队的元素。
  2. 默认大堆;要小堆,需要传入比较仿函数greater<T>

3.2 priority_queue 接口

头文件#include <queue>#include <vector>,小堆需要#include <functional>

表格

函数 说明
priority_queue<T> 默认大堆
priority_queue<T,vector<T>,greater<T>> 小堆
empty() 判空
size() 元素个数
top() 获取堆顶元素(最大 / 最小)
push(x) 插入元素,自动调整堆结构
pop() 删除堆顶元素

示例 1:内置类型,大堆、小堆

cpp 复制代码
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

int main()
{
    vector<int> v{3,2,7,6,0,4,1,9,8,5};
    //默认大堆
    priority_queue<int> q1;
    for(auto e : v)
        q1.push(e);
    cout << q1.top() << endl; //9

    //小堆,第三个模板参数 greater<int>
    priority_queue<int,vector<int>,greater<int>> q2(v.begin(),v.end());
    cout << q2.top() << endl; //0
    return 0;
}

示例 2:自定义类型放入 priority_queue

大堆默认使用<小于比较,自定义类需要重载operator<; 小堆greater需要重载operator>

cpp 复制代码
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        :_year(year),_month(month),_day(day)
    {}

    //大堆需要重载 <
    bool operator<(const Date& d)const
    {
        return (_year < d._year)
            || (_year == d._year && _month < d._month)
            || (_year == d._year && _month == d._month && _day < d._day);
    }
    //小堆greater需要重载 >
    bool operator>(const Date& d)const
    {
        return (_year > d._year)
            || (_year == d._year && _month > d._month)
            || (_year == d._year && _month == d._month && _day > d._day);
    }

    friend ostream& operator<<(ostream& cout, const Date& d);
private:
    int _year;
    int _month;
    int _day;
};

ostream& operator<<(ostream& cout, const Date& d)
{
    cout << d._year << "-" << d._month << "-" << d._day;
    return cout;
}

int main()
{
    //大堆
    priority_queue<Date> q1;
    q1.push(Date(2018,10,29));
    q1.push(Date(2018,10,28));
    q1.push(Date(2018,10,30));
    cout << q1.top() << endl; //2018‑10‑30

    //小堆
    priority_queue<Date,vector<Date>,greater<Date>> q2;
    q2.push(Date(2018,10,29));
    q2.push(Date(2018,10,28));
    q2.push(Date(2018,10,30));
    cout << q2.top() << endl; //2018‑10‑28
    return 0;
}

OJ 例题:数组第 K 个最大元素

思路:把全部元素放进大堆,弹出 k‑1 次,堆顶就是第 K 大。

cpp 复制代码
#include <queue>
#include <vector>
using namespace std;
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> p(nums.begin(), nums.end());
        for(int i = 0; i < k-1; ++i)
        {
            p.pop();
        }
        return p.top();
    }
};

优化思路:可以维护大小为 k 的小堆,适合海量数据,不用全部加载内存。

3.3 priority_queue 模拟实现

底层基于堆算法,封装 vector,调用make_heap/push_heap/pop_heap,适配器模式。


4 容器适配器

4.1 适配器模式设计思想

适配器模式:把已有类的接口,包装转换成客户需要的另一套接口。 生活例子:不同国家电源插座,使用适配器转换插头接口。

stack、queue、priority_queue不是真正容器!是容器适配器。 本身不存放数据,包装其他底层容器,对外提供一套限制后的接口。

模板原型:

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

template <class T, class Container = deque<T>>
class queue;

stack、queue默认底层容器是 deque 双端队列

4.2 deque 双端队列底层原理(了解)

deque( 双端队列 ) :是一种双开口的 " 连续 " 空间的数据结构 ,双开口的含义是:可以在头尾两端
进行插入和删除操作,且时间复杂度为 O(1) ,与 vector 比较,头插效率高,不需要搬移元素;与
list 比较,空间利用率比较高。

deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个
动态的二维数组 ,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其 " 整体连续 " 以及随机访问
的假象,落在了 deque 的迭代器身上, 因此 deque 的迭代器设计就比较复杂,如下图所示:


那 deque 是如何借助其迭代器维护其假想连续的结构呢?

特性:

  1. 头插、尾插 O (1) 时间复杂度,不需要大量搬移元素,对比 vector 头插效率极高。
  2. 扩容只需要新增 buffer,不需要拷贝整块大数据。
  3. 缺点:迭代器复杂,遍历效率差;每移动迭代器都要判断是否跨 buffer 边界,不适合大量遍历场景

deque 很少直接业务使用;最大用处就是作为 stack、queue 的底层适配器容器。

4.3 为什么 stack 和 queue 默认选择 deque 作为底层?

  1. stack、queue不需要迭代器,不需要遍历,刚好避开 deque 遍历慢的致命缺点。
  2. stack:只需要尾插尾删;deque 扩容不需要搬移大量数据,比 vector 扩容开销小。
  3. queue:需要尾插 push_back、头删 pop_front;vector 没有高效头删;list 内存碎片多;deque 兼顾效率和内存开销。

4.4 stack、queue 模板模拟完整版本(支持自定义底层容器)

cpp 复制代码
#include<deque>
namespace bite
{
//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(); }
    const T& top()const { 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& back() { return _c.back(); }
    const T& back()const { return _c.back(); }

    T& front() { return _c.front(); }
    const T& front()const { return _c.front(); }

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

📔本篇核心考点总结

  1. stack 栈,后进先出 LIFO;push 入栈,pop 出栈(无返回),top 拿栈顶;OJ:最小栈、压入弹出序列、逆波兰表达式。
  2. queue 队列,先进先出 FIFO;push 队尾入,pop 队头出,front 队头,back 队尾。
  3. priority_queue 优先级队列,底层堆;默认大堆;小堆需要greater<T>;自定义类型要重载比较运算符。
  4. stack、queue、priority_queue 属于容器适配器,不是原生容器,包装其他容器对外提供受限接口。
  5. 默认底层容器deque双端队列;deque 头插尾插 O (1),扩容代价小,但是遍历性能差。
  6. stack 可以用 vector/list/deque;queue 底层不能用 vector,必须支持 pop_front。

面试简答 Q:stack 是容器吗?底层默认什么容器?

A:不是容器,是容器适配器;默认底层 deque。

Q:priority_queue 默认是大堆还是小堆?怎么改成小堆?

A:默认大堆;传入第三个模板参数greater<T>实现小堆。

Q:为什么 stack/queue 不用 vector 做默认底层?

A:queue 需要头删 pop_front,vector 头删效率 O (N);stack 虽然 vector 可以用,但 deque 扩容不需要拷贝大量元素,效率更好。
Q:逆波兰计算的时候 pop 顺序?

A:先 pop 得到右操作数,再 pop 左操作数,顺序不能颠倒。

相关推荐
qq_199886871 小时前
第8板块·第1节:主机与设备内存管理基础与统一内存
c++·人工智能·gpu算力·cuda
爱喝水的鱼丶1 小时前
SAP-ABAP:MM 模块供应商主数据开发:字段扩展、分级管控与同步接口开发
开发语言·性能优化·接口·sap·abap·增强·经验交流
小小数媒成员1 小时前
UGUI 性能优化(详细,全,深入)
开发语言·unity·游戏引擎
h_a_o777oah1 小时前
【图论】网络流:Dinic 算法模板实现原理及解题技巧
c++·算法·图论·acm·网络流·最小割·dinic算法
大黄说说1 小时前
告别卡顿:Android UI 性能优化,布局层级过度绘制实战排查
开发语言
千谦阙听1 小时前
【 C++篇】:模板初阶——泛型编程、函数模板与类模板
开发语言·c++·学习·visual studio
SEO_juper1 小时前
2026年用Python检测网站薄内容与重复页:用向量相似度找出“搜索引擎眼中一样“的页面(附完整代码)
开发语言·前端·seo·独立站·谷歌优化
老王爱玩车1 小时前
第6讲:数组和函数实践-----控制台扫雷
c语言·开发语言·学习
我不会起名字3221 小时前
一天一道力扣Hot100(37):深度优先算法--括号生成
java·数据结构·c++·后端·python·算法·go