【C++】STL——stack和queue使用及模拟实现

news2024/11/16 11:43:42

🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


文章目录

  • 认识deque
  • stack简介
  • stack常用接口
  • stack模拟实现
  • queue简介
  • queue常用接口
  • queue模拟实现

认识deque

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

image-20230703144301059

deque是双端队列,对标的是vector加list的合体。

template < class T, class Alloc = allocator<T> > class deque;

image-20230703084221839

vector的优缺点:

优点:支持下标随机访问;

缺点:头部或者中部插入删除效率低下;扩容

list的优缺点:

优点:任意位置插入删除效率高效;

缺点:不能支持下标随机访问

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

deque容器是开一个一个的小数组,当数组满了之后再开一个小数组;将这些小数组管理起来的数组是指针数组(中控(指针数组)),最开始使用的指针是中控指针数组中间位置的指针,当进行头插、尾插的时候,就可以直接使用前一个、后一个指针指向新开辟的空间。

image-20230703085105000

中控满了之后就扩容,而且deque扩容给代价低,一个小数组对应一个中控数组中的指针,扩容代价低是因为只需要拷贝指针数组中的指针,然后再将原来的空间释放。

头插插入数组尾部,尾插在数组头部:

image-20230703085637560

deque支持下标的随机访问,但是效率没有vector高。

deque的下标访问(每个数组大小固定): 假设每个小数组的容量为 10 ,我们想要找到下标为 25 的元素,用下标减去第一个数组内元素的个数,再除以每个小数组的容量就能找到其所在哪一个小数组。比如上图中就用(25 - 3) / 10 。找到对应元素存在于第 2 个数组后,再用 (25 - 3) % 10 就可以知道对应元素是在该小数组中的第几个。

deque容器中间插入删除时候很难搞,可以用size和capacity记录每个数组,可以每个数组不一样大,但是此时随机访问就麻烦了。

与vector比较,deque的优势是:头部插入和删除时,不需要移动元素,效率特别高,而且在扩容时,也不需要移动大量的元素,因此其效率是比vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

所以deque优点有:

  1. 相比vector,deque扩容代价低;
  2. 头插头删、尾插尾删效率高;
  3. 支持下标随机访问

缺点:

1.中间插入删除后很难搞(当每个数组不一样大时,中间插入删除的效率会变高但是随机访问的效率变低;当每个数组大小固定时,牺牲中间插入删除的效率,随机访问的效率就变高了);

2.没有vector和list优点极致

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

deque适用于什么情况?

如果头插头删多,尾插尾删多可以使用deque容器,所以很适合用作栈和队列的默认底层容器。所以默认作为栈和队列的底层适配容器。

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);

queue中的元素增长时,deque不仅效率高,而且内存使用率高。结合了deque的优点,而完美的避开了其缺陷

stack简介

stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。

stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

image-20230702210057188

stack常用接口

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

库中stack定义方式接口:

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

缺省值为deque容器,说明底层容器为deque,我们也可以根据情况定义,所以定义方式有以下两种:

1.使用默认的适配器定义栈。

stack<int> st;//定义一个存储int类型的栈

2.:使用特定的适配器定义栈。

stack<int, vector<int>> st;//定义一个用vector<int>作为底层容器存储int类型的栈
stack<int, list<int>> st;//定义一个用list<int>作为底层容器存储int类型的栈

使用:

#include <iostream>
#include <stack>
using namespace std;
int main()
{
	stack<int> st;//构造
	//判断是不是空
	cout << st.empty() << endl;//0
	//插入元素
	for (int i = 0; i < 4; i++)
	{
		st.push(i);
	}
	//判断是不是空
	cout << st.empty() << endl;//1
	//打印栈中元素个数
	cout << st.size() << endl;
	//打印,stack不支持迭代器
	for (int i = 0; i < 4; i++)
	{
		cout << st.top() << " ";//打印栈顶元素
		st.pop();//删除
	}
	//输出3 2 1 0
}

stack没有迭代器,所以遍历的时候不能使用范围for。

stack模拟实现

我们用适配器模式/配接器模式,本质是转换也就是把已有的东西进行转换,就好比我们的手机充电器并不能直接使用220v电压,所以提供了一个转换器,将电压转换为适合我们使用的。

设计模式就是指把常见的设计方法进行总结,适配器也是一种设计模式。

我们用已有的容器封装:可以这样定义类模板template<class T,class Container>,Container就是符合我们要求的一个容器。

stack模拟实现主要依赖底层容器中的函数,所以stack模拟实现比较简单,将类模板中的第二个参数默认值为deque容器,目的是让deque作为默认底层容器。

namespace Niu {
	//给类模板中的第二个参数默认值为deque容器
	template<class T,class Container = deque<T>>
	class stack {
	public:
		//实现push
		void push(const T& val)
		{
			//调用容器的push_back函数
			_con.push_back(val);
		}
		//实现pop
		void pop()
		{
			//调用容器的pop_back函数
			_con.pop_back();
		}

		const T& top()
		{
			//调用容器的back函数返回容器最后一个数据
			return _con.back();
		}
		size_t size()
		{
			//调用容器的size函数
			return _con.size();
		}
		bool empty()
		{
			//调用容器的empty函数
			return _con.empty();
		}
	private:
		Container _con;
	};
}

queue简介

队列是一种容器适配器,专门用于在FIFO上下文(先进先出)的环境中操作,其中从容器一端插入元素,另一端提取元素。

队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。

底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:

  • empty:检测队列是否为空
  • size:返回队列中有效元素的个数
  • front:返回队头元素的引用
  • back:返回队尾元素的引用
  • push_back:在队列尾部入队列
  • pop_front:在队列头部出队列

标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

queue常用接口

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

库中queue定义方式接口:

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

缺省值为deque容器,说明底层容器为deque,我们也可以根据情况定义,所以定义方式有以下两种:

1.使用默认的适配器定义栈。

queue<int> st;//定义一个存储int类型的栈

2.:使用特定的适配器定义栈。

注意queue中底层容器仅可使用deque和list容器,其他容器没有符合的对应操作。

queue<int, list<int>> st;//定义一个用list<int>作为底层容器存储int类型的栈

队列能不能用vector适配?不能,vector不适合头删,使用erase的话效率低。

使用:

#include <iostream>
#include <queue>
using namespace std;
int main()
{
	queue<int> q;
	//判断q队列中是不是为空
	cout << q.empty() << endl;//0
	// 插入元素
	for (int i = 0; i < 5; i++)
	{
		q.push(i);
	}
	//获取队头元素
	cout << q.front() << endl; //0
	//获取队尾元素
	cout << q.back() << endl; //4

	//判断队列是否为空
	cout << q.empty() << endl; //1
	//队列中元素个数
	cout << q.size() << endl; //5
	//删除元素
	for (int i = 0; i < 5; i++)
	{
		cout << q.front() << " ";
		q.pop();//从队头开始删除
	}
	//输出0 1 2 3 4

	return 0;
}

queue模拟实现

queue模拟实现和stack模拟实现差不多,都是通过调用容器的函数来完成对应的功能。

模板声明和定义分离,在不同的文件中,要单独加模板参数,即便加了之后也可能会出错。

模板声明和定义分离会有很多的问题,会产生链接错误。所以模板都定义在.h中就可以,不然会产生未知错误。

namespace Niu {
	template<class T , class Container = deque<T>>
	class queue {
	public:
		//判空
		bool empty()
		{
			return _con.empty();
		}
		//在队尾插入元素
		void push(const T& val)
		{
			_con.push_back(val);
		}
		//删除队头元素
		void pop()
		{
			_con.pop_front();
		}
		//返回size大小
		size_t size()
		{
			return _con.size();
		}
		//返回队头元素
		T& front()
		{
			return _con.front();
		}
		//返回队头元素
		const T& front() const
		{
			return _con.front();
		}
		//返回队尾元素

		const T& back() const 
		{
			return _con.back();
		}
		//返回队尾元素
		T& back()
		{
			return _con.back();
		}
	private:
		Container _con;
	};
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/738350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ICG试剂大合集:ICG-COOH/NHS/NH2/Maleimide/alkyne/N3

荧光染料及其荧光标记技术一直是生物领域常用的产品和技术&#xff0c;标记荧光的波长从300nm到1000nm&#xff0c;除了常见的FITC、FAM等荧光标记染料外&#xff0c;还包括Cy3&#xff0c;Cy3.5&#xff0c;Cy5, Cy5.5&#xff0c;Cy7&#xff0c;Cy7.5&#xff0c;ICG各种衍生…

vue2登录存储案例:sessionStorage会话存储+localStorage本地存储

vue2可以通过sessionStorage来实现登录以后的数据存储&#xff0c;是H5提供的一个API&#xff0c;可以在浏览器会话期间保持数据 简单模拟一个登录后的存储功能 目录 一、sessionStorage存储 二、localStorage本地存储 一、sessionStorage存储 1、登录的HTML方法 HTML &l…

Java基础---注解

目录 典型回答 什么是元注解 如何判断注解 典型回答 Java 注解用于为 Java 代码提供元数据作为元数据&#xff0c;注解不直接影响你的代码执行&#xff0c;但也有一些类型的注解实际上可以用于这一目的Java注解是从 Java5 开始添加到 Java 的Java的注解&#xff0c;可以说是…

GitHub Pages + Hexo

步骤 参考如下步骤&#xff1a;https://blog.csdn.net/yaorongke/article/details/119089190 出现的问题 1 Fluid主题 其更换Fluid主题时&#xff1a; 下载最新 release 版本 解压到 themes 目录&#xff0c;并将解压出的文件夹重命名为 fluid 按照上面执行后&#xff0c;后…

Michael.W基于Foundry精读Openzeppelin第7期——Timers.sol

Michael.W基于Foundry精读Openzeppelin第7期——Timers.sol 0. 版本0.1 Timers.sol 1. 目标合约2. 代码精读2.1 区块链时间戳维度2.1.1 Timestamp结构体2.1.2 setDeadline(Timestamp storage, uint64) && getDeadline(Timestamp memory)2.1.3 reset(Timestamp storage)…

Linux:基于PXE的kickstart无人值守技术

*创建应答文件&#xff0c;预先定义好各种安装设置 *免去交互设置过程&#xff0c;从而实现全自动化安装 *通过添加%post脚本&#xff0c;完成安装后的各种配置操作 需要的环境为 pxe&#xff08;dhcp&#xff0c;tftp&#xff0c;yum&#xff09; Linux&#xff1a;PXE网络装…

深度学习——批数据训练

代码与详细注释&#xff1a; BATCH_SIZE 5&#xff0c;shuffleTrue import torch import torch.utils.data as Data# 添加随机种子以使结果可复现 torch.manual_seed(1) # reproducible# 批大小 BATCH_SIZE 5 # BATCH_SIZE 8x torch.linspace(1, 10, 10) # this…

VScode 终端无法识别npm以及Missing script: “serve“ 问题

无法识别npm解决办法&#xff1a; 原因是没有全局安装npm 1.通过终端查看已经全局安装的模块 npm list --depth0 -global 2.全局安装npm npm install -g npm Missing script: "serve" 问题&#xff1a; 原因是package.json中没有配置&#xff1a;"serve"…

JUC 并发编程

文章目录 JUC 并发编程一、Lock 锁1. 可重入锁2. 公平锁3. 读写锁3.1 ReadWriteLock 接口3.2 ReentrantReadWriteLock 类3.3 锁降级 4. 线程间通信4.1 虚假唤醒4.2 线程通信&#xff08;Condition&#xff09;4.3 定制化线程通信 二、集合线程安全1. CopyOrWrite2. ConcurrentH…

机试刷题记录 2023-7-6

AB问题 题目描述 Time Limit: 1000 ms Memory Limit: 256 mb 输入A,B 输出AB -1,000,000,000<A,B<1,000,000,000 输入输出格式 输入描述: 输入包含两个整数A,B&#xff0c;用一个空格分隔。 输出描述: 输出一个整数&#xff0c;表示AB的值。 输入输出样例 输入…

ResNet网络结构入门

ResNet网络结构入门 一、传统卷积神经存在的问题二、Residual 结构&#xff08;残差结构&#xff09;ResNet 中的残差结构ResNet 中的 short cut 三、Batch Normalization Resnet 网络创新点 提出 Residual 结构&#xff08;残差结构&#xff09;&#xff0c;可搭建超深的网络…

第44节:cesium 大雾效果(cesium自带)(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><!

C#核心知识回顾——12.lambda表达式、List排序、协变和逆变

1.Lambda表达式 可以将lambad表达式理解为匿名函数的简写 它除了写法不同外&#xff0c;使用上和匿名函数一模一样 都是和委托或者事件配合使用的 //匿名函数 //delegate&#xff08;参数列表&#xff09; //{ //} //lambda表达式 //(参数列表) > //{ //函数体 //…

【Axure高保真原型】冻结固定中继器表格首尾两列

今天和大家分享冻结固定中继器表格首尾两列的原型模板&#xff0c;当我们遇到表格内容较多时&#xff0c;可以用这个模板固定第一列和最后一列操作列。这个模板是用中继器制作的&#xff0c;所以使用也很简单&#xff0c;只需要在中继器里填写对应数据即可&#xff0c;具体效果…

游戏渲染技术:前向渲染 vs 延迟渲染 vs Forward+渲染(二)

GTA5 2 前向渲染 前向渲染是三个光照技术中最简单的&#xff0c;也是游戏图形渲染中最常见的技术。出于这个原因&#xff0c;也是光照计算最昂贵的技术&#xff0c;它不允许在场景中出现大量的动态光源。 大部分使用前向渲染的图形引擎会采用一些技术来模拟场景中大量的光源的…

K8S应用流程安全(镜像安全 配置管理 访问安全)

应用流程安全 1 应用流程安全1.1 镜像安全1.1.1 构建原则1.1.2 Dockerfile实践1.1.3 构建进阶1.1.4 镜像检测1.1.5 仓库升级1.1.6 高可用仓库1.1.7 镜像策略 1.2 配置管理1.2.1 配置基础1.2.2 YAML安全1.2.3 kustomize1.2.4 基础实践1.2.5 功能复用1.2.6 配置定制1.2.7 补丁实践…

python接口自动化(二十七)--html 测试报告——上(详解)

简介 上一篇我们批量执行完用例后&#xff0c;生成的测试报告是文本形式的&#xff0c;不够直观&#xff0c;而且报告一般都是发给leader的&#xff0c;所以最好是直观一目了然&#xff0c;为了更好的展示测试报告&#xff0c;最好是生成 HTML 格式的。unittest 里面是不能生成…

SpringBoot——加载测试专用的配置类

加载测试专用的配置类 之前我们介绍了如何在测试类中加载专用的测试属性&#xff0c;这次我们来看如何在测试类中加载专用的测试类。 创建配置类 首先创建一个配置类&#xff0c;并且创建一个第三方的Bean模拟这是一个要在测试用例中引用的第三方Bean 创建测试用例 创建一个…

第四十三章Java匿名对象

经过前面的学习&#xff0c;我们知道创建对象的标准格式如下&#xff1a; 类名称 对象名 new 类名称(); 每次 new 都相当于开辟了一个新的对象&#xff0c;并开辟了一个新的物理内存空间。如果一个对象只需要使用唯一的一次&#xff0c;就可以使用匿名对象&#xff0c;匿名对…

一起来了解一下Java中的String类吧!!!

简单认识Java中的String类 一、认识String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&#xff0c;可以使用标准库提 供的字符串系列函数完成大部分操作&#xff0c;但是这种将数据和操作数据方法分离开的方式…