【C++从0到王者】第十七站:手把手教你写一个stack和queue及deque的底层原理
文章目录
- 一、stack
- 1.利用适配器
- 2.栈的实现
- 二、queue
- 三、deque
- 1.deque介绍
- 2.deque的接口
- 3.deque的基本使用
- 4.deque的效率
- 5.deque的原理
一、stack
1.利用适配器
我们不可能写了一份数组栈以后,还要在手写一个链式栈,这样显得太冗余了。于是我们可以利用适配器,传递一个我们想要使用的类型。这样我们的栈就可以做到数组栈和链式栈的秒切换了。从我们用的角度来说并没有太大差别,但是底层早已大变样了。
template<class T, class Container>class stack{public:private:Container _con;};
2.栈的实现
有了上面的思路,我们就可以很容易的完成栈的接口
#pragma once
#include<vector>
#include<list>
namespace Sim
{template<class T, class Container = vector<T>>class stack{public:void push(const T& val){_con.push_back(val);}void pop(){_con.pop_back();}const T& top(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};void test_stack(){stack<int> st1;stack<int, list<int>> st2;st1.push(1);st1.push(2);st1.push(3);st1.push(4);while (!st1.empty()){cout << st1.top() << " ";st1.pop();}cout << endl;}
};

二、queue
如下所示,是queue的模拟实现,需要注意的是,queue是不可以用vector进行适配的,因为vector并未提供pop_front接口,但是如果想要强制适配的话也是可以的,使用erase接口即可
#pragma once
#pragma once
#include<vector>
#include<list>
namespace Sim
{template<class T, class Container = list<T>>class queue{public:void push(const T& val){_con.push_back(val);}void pop(){_con.pop_front();}const T& front(){return _con.front();}const T& back(){return _con.back();}size_t size(){return _con.size();}bool empty(){return _con.empty();}private:Container _con;};void test_queue(){queue<int> q1;q1.push(1);q1.push(2);q1.push(3);q1.push(4);while (!q1.empty()){cout << q1.front() << " ";q1.pop();}cout << endl;}
};

三、deque
1.deque介绍
虽然我们上面使用的适配器缺省参数都是vector或者list,但是我们会发现,库里面的stack和list它的适配器都是deque。deque听名字好像是个队列,名字是双端队列。但是队列是有先进先出的特性的,它不是那么特别符合队列。

Deque(通常发音像“deck”)是双端队列的不规则缩写。双端队列是具有动态大小的序列容器,可以在两端(前端或后端)扩展或收缩。
特定的库可能以不同的方式实现deque,通常是某种形式的动态数组。但在任何情况下,它们都允许通过随机访问迭代器直接访问单个元素,并根据需要通过扩展和收缩容器来自动处理存储。
因此,它们提供了类似于向量的功能,但在序列的开始,而不仅仅是在序列的末尾,也可以有效地插入和删除元素。但是,与vector不同,deque不能保证将其所有元素存储在连续的存储位置:通过偏移指向另一个元素的指针来访问deque中的元素会导致未定义的行为。
vector和deque都提供了非常相似的接口,可以用于类似的目的,但两者在内部的工作方式却完全不同:vector使用单个数组,偶尔需要为增长重新分配,而deque的元素可以分散在不同的存储块中,容器内部保留必要的信息,以便在恒定时间内使用统一的顺序接口(通过迭代器)直接访问其任何元素。因此,deque在内部比vector更复杂,但这使得它们在某些情况下更有效地增长,特别是对于非常长的序列,重新分配变得更加昂贵。
对于涉及频繁插入或删除除开始或结束位置以外的元素的操作,deque的性能更差,迭代器和引用的一致性也不如列表和前向列表。
2.deque的接口
如下所示,是deque的接口,我们可以发现,它似乎同时具有list和vector的接口。而且它的迭代器还是随机迭代器。

deque的接口像是vector和list的合体。但是它看似很强,实际上效率不是很高。单论头插头删,尾插尾删效率还是不错的,但是综合性不是很好。
3.deque的基本使用
void test_deque()
{deque<int> dq;dq.push_back(1);dq.push_back(2);dq.push_back(3);dq.push_back(4);for (int i = 0; i < dq.size(); i++){cout << dq[i] << " ";}cout << endl;
我们可以得知

4.deque的效率
我们在前面说过,deque的综合效率是不高的。我们可以用下面的代码来看出
void test_op()
{srand(time(0));const int N = 1000000;vector<int> v1;vector<int> v2;v1.reserve(N);v2.reserve(N);deque<int> dq1;deque<int> dq2;for (int i = 0; i < N; ++i){auto e = rand();//v1.push_back(e);//v2.push_back(e);dq1.push_back(e);dq2.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();// 先拷贝到vectorfor (auto& e : dq1){v1.push_back(e);}// 排序sort(v1.begin(), v1.end());// 拷贝回去size_t i = 0;for (auto& e : dq1){e = v1[i++];}int end1 = clock();int begin2 = clock();sort(dq2.begin(), dq2.end());int end2 = clock();printf("deque copy vector sort:%d\n", end1 - begin1);printf("deque sort:%d\n", end2 - begin2);
}

deque效率慢的原因主要就是因为它的随机访问[]的效率太低
5.deque的原理
我们知道:
-
对于数组,可以下标随机访问,但是存在扩容问题,中间和头部插入效率低下

-
对于链表,任意位置插入删除效率合适,按需申请释放,但是不支持随机访问

而现在,我们使用的deque的结构是这样,它是一段一段的开空间,每段空间都是一样大的,然后通过一个中控数组(指针数组)进行连接起来。想要扩容就在连接一块空间即可。当指针数组满了,就中控数组扩容即可。这样一来扩容的代价就很低。不需要拷贝原来的数组。对于头插尾插也很简单,就用专门的两个空间进行头插尾插即可

它相比vector极大的缓解了扩容、头插头删问题。但是它的[]运算符不够极致。它的[]需要计算在哪个buff数组,在哪个buff数组的第几个。如果我们想要使用它的[]运算符,它内部的逻辑会经历一下几个步骤
- 先看在不在第一个buff数组里面,如果在,就直接访问
- 不在第一个buff数组里面,i-=第一个buff数组的size
- 第几个buff=i/buff.size()
- 在这个buff的第几个=i%buff.size()
它相比list,可以支持随机访问,cpu高速缓存访问效率不错,头插尾插删除不错,但是中间位置插入删除效率低下。因为我们需要扩容或者挪动buff的数据。无论哪一种,效率都很低。
根据deque的底层原理,其实对于高频的头插头删,尾插尾删来说,deque还很适合,所以deque用于适配stack和queue来说是很合适的,因为它们只涉及到头部和尾部的插入删除,不涉及中间位置的插入删除
实际上在库里面的deque是更加复杂的,它的迭代器由四个指针组成,这使得deque更加复杂,首先由node指向中控,即指向当前的buff数组,cur指向当前buff数组中的某个数据,first和last指向当前数组的头和尾


好了,本期内容就到这里了
如果对你有帮助的话,不要忘记点赞加收藏哦!!!
相关文章:
【C++从0到王者】第十七站:手把手教你写一个stack和queue及deque的底层原理
文章目录 一、stack1.利用适配器2.栈的实现 二、queue三、deque1.deque介绍2.deque的接口3.deque的基本使用4.deque的效率5.deque的原理 一、stack 1.利用适配器 我们不可能写了一份数组栈以后,还要在手写一个链式栈,这样显得太冗余了。于是我们可以利…...
ffmpeg.c源码与函数关系分析
介绍 FFmpeg 是一个可以处理音视频的软件,功能非常强大,主要包括,编解码转换,封装格式转换,滤镜特效。FFmpeg支持各种网络协议,支持 RTMP ,RTSP,HLS 等高层协议的推拉流,…...
GD32F103待机模式与唤醒
GD32F103待机模式与唤醒,本程序使用RTC报警唤醒。 电源管理单元有3种省电模式:睡眠模式,深度睡眠模式和待机模式; 进入待机模式的步骤如下: 若需要RTC闹钟输出,则需要将TAMPER-RTC映射到PC13引脚; 若需要LXTAL晶振32.768KHz&…...
【Linux初阶】基础IO - 动静态库 | 初识、生成、链接、加载
🌟hello,各位读者大大们你们好呀🌟 🍭🍭系列专栏:【Linux初阶】 ✒️✒️本篇内容:动静态库初识,库的含义,静态库的生成与链接,gcc/g默认链接方式,…...
为Git仓库设置签名信息
前言 在首次使用git版本库或创建新的仓库时,需要为其仓库设定管理员和管理员邮箱。 在为仓库添加管理员和邮箱地址时,有以下两种情况: (1)全局模式:首次创建,后面做为默认使用,对当…...
iOS开发Swift开发UI页面链式调用库推荐
首页链接 https://github.com/zhiguangqiao/ChainableUIKit 安装方法 pod ChainableUIKit调用片段 UIButton import ChainableUIKitprivate let button UIButton().chain.setTitleColor(.init(hex: "#9583EB"), state: .normal).setTitle("全部视频",…...
ClickHouse SQL与引擎--基本使用(一)
1.查看所有的数据库 show databases; 2.创建库 CREATE DATABASE zabbix ENGINE Ordinary; ATTACH DATABASE ck_test ENGINE Ordinary;3.创建本地表 CREATE TABLE IF NOT EXISTS test01(id UInt64,name String,time UInt64,age UInt8,flag UInt8 ) ENGINE MergeTree PARTI…...
2023-08-07力扣今日七题-好题
链接: 剑指 Offer 11. 旋转数组的最小数字 154. 寻找旋转排序数组中的最小值 II 题意: 找一个数组里的最小值,这个数组是有非递减数组旋转而来的,旋转n次表示把前n个数移动到数组末尾 解: 很有趣的二分ÿ…...
支持多用户协同的思维导图TeamMapper
什么是 TeamMapper ? TeamMapper 是基于 Mindmapp 开发的用于绘制思维导图的 Web 应用程序。它使得思维导图变得简单,你可以托管并创建您自己的思维导图。与您的团队分享您的思维导图会议并在思维导图上进行协作。 软件特点: 创建࿱…...
【Vue】Parsing error: No Babel config file detected for ... vue
报错 Parsing error: No Babel config file detected for E:\Study\Vue网站\实现防篡改的水印\demo02\src\App.vue. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. …...
2023-08-07力扣今日五题
链接: 剑指 Offer 53 - II. 0~n-1中缺失的数字 题意: 如题 解: 长度n的递增数组里,要找0到n中没出现的那个数字,那么出现的下标是0到n-1,一一对应即可,都出现了就是n没有 实际…...
ETHERCAT转PROFIBUS连接到300plc的配置方法
由于捷米JM-DP-ECT,是自主研发的一款PROFIBUS从站功能的通讯网关,它的主要功能是将ETHERCAT设备接入到PROFIBUS网络中生产环境比较复杂有多个设备采用不同的协议这极大的阻碍了,各个设备的数据互通。 JM-DP-ECT这个小小的网关可不简单&#x…...
Spring Boot配置文件与日志文件
1. Spring Boot 配置文件 我们知道, 当我们创建一个Spring Boot项目之后, 就已经有了配置文件存在于目录结构中. 1. 配置文件作用 整个项目中所有重要的数据都是在配置文件中配置的,比如: 数据库的连接信息 (包含用户名和密码的设置) ;项目的启动端口;第三方系统的调…...
可解释性分析的一些类别(草稿)(视觉)
目录 1.交互性解释 2. 本身具有解释性的模型 3.如何将可解释性分析应用到生成模型 参考文献 视觉领域从2020年开始可以分为两块,一个是图像分类,一个是图像生成。 图像分类:输入一张图片,输出语义标签,就是这张图…...
HTTPS-RSA握手
RSA握手过程 HTTPS采用了公钥加密和对称加密结合的方式进行数据加密和解密 RSA握手是HTTPS连接建立过程中的一个关键步骤,用于确保通信双方的身份验证和生成对称加密所需的密钥 通过RSA握手过程,客户端和服务器可以协商出一个共享的对称密钥,…...
bigemap国土管理行业应用
由于国营企业单位,管理土地,必须要有这样的软件套图 客户之前用的谷歌,后来不能访问了,通过其他途径搜索到我们 客户使用软件一般都用于套坐标以及空间规划图,方便于项目选址和居民建房报建在卫星图上找到用地范围&am…...
深入探索 Splashtop Enterprise 的潜力
在当今高度技术化的环境中,远程访问解决方案已成为无数组织的支柱。远程访问解决方案缩短了员工与工作之间的地理差距,提高了工作的效率和灵活性,促进形成了无缝的工作体验。在众多远程访问解决方案中,Splashtop Enterprise 作为远…...
创建型模式-单例模式
文章目录 一、创建型模式1. 单例设计模式1.1 单例模式的结构1.2 单例模式的实现(1)饿汉式-方式1(静态变量方式)(2)饿汉式-方式2(静态代码块方式)(3)懒汉式-方…...
2. Linux安装Git
yum安装 查看版本 版本太低,所以我们采用自己上传编译的方式进行 删除已安装的git yum remove git 下载最新安装包,并上传到服务器文件夹下 上传,解压 5.安装编译需要的依赖 yum install curl-devel expat-devel gettext-devel openssl-…...
检查网站是HTTP那种协议与获取域名的ipv6地址
前言 最近在做HTTPS的应用,可能需要使用ipv6的地址做SLB,但是怎么检查配置正确,总不能每次都看日志吧,实际上客户端也很容易查看,总结工作经验。 检查HTTP协议版本 笔者想到了使用浏览器方式,或者抓包&a…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
C++11 constexpr和字面类型:从入门到精通
文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...
云原生时代的系统设计:架构转型的战略支点
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、云原生的崛起:技术趋势与现实需求的交汇 随着企业业务的互联网化、全球化、智能化持续加深,传统的 I…...
