容器适配器深度解析:从STL的stack、queue到优先队列的底层实现

容器适配器深度解析:从STL的stack、queue到优先队列的底层实现
在这里插入图片描述

文章目录

容器适配器深度解析:从STL的stack、queue到优先队列的底层实现

1. 栈的介绍和使用

1.1 栈的介绍

栈(stack)是一种后进先出(LIFO)的数据结构,它允许在一端进行插入和删除操作。在C++ STL中,stack是一个容器适配器,提供了一组特定的成员函数来访问其元素。

1.2 栈的使用

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

最小栈实现

最小栈能够在O(1)时间内获取栈中的最小元素。

classMinStack{public:voidpush(int x){// 压栈时,先将元素保存到_elem _elem.push(x);// 如果x小于_min中栈顶的元素,将x再压入_min中if(_min.empty()|| x <= _min.top()) _min.push(x);}voidpop(){// 如果_min栈顶的元素等于出栈的元素,_min顶的元素要移除if(_min.top()== _elem.top()) _min.pop(); _elem.pop();}inttop(){return _elem.top();}intgetMin(){return _min.top();}private: std::stack<int> _elem;// 保存栈中的元素 std::stack<int> _min;// 保存栈的最小值};

栈的弹出压入序列

验证一个序列是否是栈的弹出序列。

在这里插入图片描述
classMinStack{public:MinStack(){}voidpush(int val){ st.push(val);if(minst.empty()||minst.top()>=val){ minst.push(val);}}voidpop(){if(st.top()==minst.top()){ minst.pop();} st.pop();}inttop(){return st.top();}intgetMin(){return minst.top();}private: stack<int> st; stack<int> minst;};

逆波兰表达式求值

classSolution{public:intevalRPN(vector<string>& tokens){ stack<int> s;for(size_t i =0; i < tokens.size();++i){ string& str = tokens[i];// str为数字if(!("+"== str ||"-"== str ||"*"== str ||"/"== str)){ s.push(atoi(str.c_str()));}else{// 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;}}}return s.top();}};

1.3 stack的模拟实现

以下是使用容器适配器模式实现的stack,支持自定义底层容器(默认使用deque):

#pragmaonce#include<deque>namespace bit {// container适配器实现stacktemplate<classT,classContainer= deque<T>>classstack{public:voidpush(const T& x){ _con.push_back(x);}voidpop(){ _con.pop_back();}const T&top()const{return _con.back();} size_t size()const{return _con.size();}boolempty(){return _con.empty();}private: Container _con;};}

2. 队列的介绍和使用

2.1 队列的介绍

队列是一种先进先出(FIFO)的数据结构,元素从队尾入队列,从队头出队列。在C++ STL中,queue是一个容器适配器。

queue的特点:

  1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作
  2. 元素从队尾入队列,从队头出队列
  3. 底层容器可以是deque或list,默认使用deque

2.2 queue的使用

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

2.3 queue的模拟实现

使用容器适配器模式实现的queue,支持自定义底层容器:

#pragmaonce#include<deque>namespace bit {template<classT,classContainer= deque<T>>classqueue{public:voidpush(const T& x){ _con.push_back(x);}voidpop(){ _con.pop_front();}const T&front()const{return _con.front();}const T&back()const{return _con.back();} size_t size()const{return _con.size();}boolempty(){return _con.empty();}private: Container _con;};}

3. 优先队列的介绍和使用

3.1 priority_queue的介绍

优先队列是一种容器适配器,它的第一个元素总是它所包含的元素中最大的(默认大堆)。

priority_queue的特点:

  1. 第一个元素总是最大的(默认大堆)
  2. 类似于堆,可以随时插入元素,只能检索最大堆元素
  3. 默认使用vector作为底层容器
  4. 支持随机访问迭代器

3.2 priority_queue的使用

函数声明接口说明
priority_queue()构造一个空的优先级队列
priority_queue(first, last)用迭代器范围构造优先级队列
empty()检测优先级队列是否为空
top()返回优先级队列中最大(最小)元素
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素

使用示例:

#include<vector>#include<queue>#include<functional>// greater算法的头文件voidTestPriorityQueue(){// 默认情况下,创建的是大堆,其底层按照小于号比较 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比较方式 priority_queue<int, vector<int>, greater<int>>q2(v.begin(), v.end()); cout << q2.top()<< endl;// 输出0}

注意:

  1. 默认情况下,priority_queue是大堆
  2. 如果要创建小堆,需要指定第三个模板参数为greater<T>
  3. 如果存放自定义类型,需要在自定义类型中提供><的重载

3.3 priority_queue的模拟实现

以下是完整的优先队列模拟实现,包含大堆和小堆支持:

#pragmaonce#include<vector>namespace bit {// 仿函数类:Less(升序/大堆)template<classT>classLess{public:booloperator()(const T& x,const T& y){return x < y;}};// 仿函数类:Greater(降序/小堆)template<classT>classGreater{public:booloperator()(const T& x,const T& y){return x > y;}};// 优先队列实现// 模板参数:// T - 元素类型// Container - 底层容器,默认vector<T>// Compare - 比较器,默认Less<T>(大堆)template<classT,classContainer= vector<T>,classCompare= Less<T>>classpriority_queue{public:// 向上调整(插入时使用)voidAdjustUp(int child){ Compare com; size_t parent =(child -1)/2;while(child >0){// 使用仿函数进行比较if(com(_con[parent], _con[child])){swap(_con[child], _con[parent]); child = parent; parent =(child -1)/2;}else{break;}}}// 向下调整(删除时使用)voidAdjustDown(int parent){int child = parent *2+1; Compare com;while(child < _con.size()){// 找出较大(或较小)的孩子if(child +1< _con.size()&&com(_con[child], _con[child +1])){++child;}// 如果父节点小于(或大于)孩子节点,交换if(com(_con[parent], _con[child])){swap(_con[child], _con[parent]); parent = child; child = parent *2+1;}else{break;}}}// 插入元素voidpush(const T& x){ _con.push_back(x);AdjustUp(_con.size()-1);}// 删除堆顶元素voidpop(){swap(_con[0], _con[_con.size()-1]); _con.pop_back();AdjustDown(0);}// 获取堆顶元素const T&top(){return _con[0];}// 获取元素个数 size_t size()const{return _con.size();}// 判断是否为空boolempty()const{return _con.empty();}private: Container _con;};}

4. 容器适配器

4.1 什么是适配器

适配器是一种设计模式,将一个类的接口转换成客户希望的另外一个接口。

4.2 STL标准库中stack和queue的底层结构

stack和queue在STL中称为容器适配器,它们只是对其他容器的接口进行了包装。STL中stack和queue默认使用deque作为底层容器。

4.3 deque的简单介绍

4.3.1 deque的原理介绍

deque(双端队列)是一种双开口的"连续"空间的数据结构:

  • 可以在头尾两端进行插入和删除操作,时间复杂度为O(1)
  • 与vector比较,头插效率高,不需要搬移元素
  • 与list比较,空间利用率比较高

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际类似于一个动态的二维数组。

4.3.2 deque的缺陷
  • 不适合遍历,因为迭代器要频繁检测是否移动到小空间边界
  • 在实际中,需要线性结构时,大多数情况下优先考虑vector和list
  • deque的应用并不多,主要作为stack和queue的底层数据结构

4.4 为什么选择deque作为stack和queue的底层默认容器

  1. stack和queue不需要遍历(因此没有迭代器),只需要在固定的一端或两端操作
  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据)
  3. queue中的元素增长时,deque不仅效率高,而且内存使用率高

4.5 容器适配器设计的关键点

从上面的模拟实现可以看出容器适配器的核心设计思想:

  1. 模板参数灵活性
    • stack和queue都支持自定义底层容器
    • priority_queue支持自定义比较器,实现大堆/小堆切换
  2. 接口统一性
    • 所有适配器都提供push、pop、top/front/back、size、empty等标准接口
    • 隐藏底层容器的具体实现细节
  3. 代码复用
    • 通过组合已有的容器实现新功能
    • 减少代码重复,提高开发效率

总结

栈、队列和优先队列是三种基础且重要的数据结构,在C++ STL中通过容器适配器的方式实现。理解它们的底层原理、使用场景和实现方式,对于提高编程能力和解决实际问题都有重要意义。

通过模拟实现这些数据结构,我们可以:

  1. 深入理解容器适配器设计模式
  2. 掌握仿函数在泛型编程中的应用
  3. 理解堆算法的实现原理
  4. 学会如何设计灵活的、可扩展的数据结构

在实际开发中,根据具体需求选择合适的数据结构:

  • 需要后进先出操作时选择栈
  • 需要先进先出操作时选择队列
  • 需要快速获取最大/最小值时选择优先队列

Read more

【Linux系统】C/C++的调试器gdb/cgdb,从入门到精通

【Linux系统】C/C++的调试器gdb/cgdb,从入门到精通

各位读者大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、调试前的预备知识 * 二、gdb/cgdb的使用 * 1. 启动,查看代码 * 2. 基础调试命令 * 3. 监视变量相关命令 * 4. 设置条件断点 一、调试前的预备知识 程序发布的方式有两种,debug模式和release模式。 * debug模式:生成的可执行程序中会包含程序的调试信息,便于程序员进行调试代码。 * release模式:会剥离或不生成这些调试信息。这使得文件更小,但也意味着调试器几乎无法工作,release版本程序无法进行调试。 Linux的gcc/g++,按照我们之前的写法gcc -o $@ $^,默认生成的是release版本的程序,是无法进行调试的。要在命令后加-g选项,指定以debug方式发布,debug模式下的程序我们才能进行调试。 gcc -o $@ $^ -g 二、gdb/cgdb的使用

By Ne0inhk
【C++】模板编程入门指南:零基础掌握泛型编程核心(初阶)

【C++】模板编程入门指南:零基础掌握泛型编程核心(初阶)

文章目录 * 一、泛型编程 * 二、函数模板 * 1. 函数模板的概念和格式 * 2. 函数模板的原理 * 3. 函数模板的实例化 * 隐式实例化 * 显式实例化 * 三、类模板 一、泛型编程 泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础,可能不太好理解,这里我给大家举一个现实生活中的例子,我们想做很多个草莓形状的橡皮泥玩具,并且这些草莓玩具颜色不同,效果如下: 问题来了,我们该怎么解决这个问题呢?难道拿出不同颜色的橡皮泥开始一个一个捏吗?但是这样的话效率是不是很低呢?所以我们会这样想,既然这些草莓玩具的形状相同,只是颜色不同,我们是不是可以做一个草莓模具,当我们想做一个草莓玩具的时候,就可以将对应颜色的橡皮泥填充模具,最终得到这个草莓,如下: 这样我们有了模具以后,只需要使用对应颜色的橡皮泥就可以批量制作草莓了,非常高效,这就属于泛型编程的思维,大家可能还是感受不到,我们再举一个有关编程的例子,也就是使用C语言实现两个变量的交换,如下: voidSwap(int& x,int&

By Ne0inhk
C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)

文章目录 * 前言 * C++的类型转换 * 四种命名的强制类型转换操作符 * static_cast * reinterpret_cast * const_cast * dynamic_cast * RTTI(这个了解一下就行了) * C++的IO流 * C++文件的IO流 * stringstream 前言 在 C++ 编程体系中,类型转换与 IO 流是支撑程序数据处理与交互的两大核心环节。类型转换关乎数据在不同类型间的安全传递与运算适配,而 IO 流则负责程序与外部设备(如键盘、屏幕、文件)之间的数据输入与输出,二者共同构成了 C++ 程序实现功能、交互信息的基础框架。 C 语言中的类型转换方式虽简洁,却存在可视性差、难以追踪的问题,容易在复杂程序中引发潜在的逻辑错误。为解决这一痛点,C++ 引入了四种命名明确的强制类型转换操作符 ——static_cast、reinterpret_

By Ne0inhk
【C++笔记】STL知识铺垫

【C++笔记】STL知识铺垫

前言:          在前面的学习中,我们已经掌握了C++的基础语法和编程概念,本文将深入探讨C++标准库的使用,并详细介绍迭代器、auto关键字以及范围for循环等相关知识。          一、STL简介          1.1 什么是STL          STL(Standard Template Library,标准模板库)是C++标准库的核心组成部分,它不仅提供了可复用的组件库,更是一个集成了高效数据结构与算法的软件框架。          1.2 STL的六大组件          由于历史原因,string 类型先于 STL 出现,STL 后来由惠普实验室开发并开源,因此人们通常不将 string 归入 STL 范畴。                   二、迭代器                  迭代器(Iterator)是 C++ STL 中最精妙的设计之一,如果把 STL 的容器比作各种不同类型的仓库(数组、链表、

By Ne0inhk