C++ :STL中deque的原理

deque的结构类似于哈希表,使用一个指针数组存储固定大小的数组首地址,当数据分布不均匀时将指针数组内的数据进行偏移,桶不够用的时候会像vector一样扩容然后将之前数组中存储的指针拷贝过来,从原理可以看出deque的性能是非常高的,它不存咋像vector那样大规模的数据拷贝和大批量连续空间的需求同时也弥补了像list不支持随机访问的缺点,可以说deque集中了list和vector的大部分优点,当然缺点很致命在中间节点插入数据的时候会异常复杂,高效的前后端插入机制使得stack和queue都适配于deque。

vector动态数组

  • 支持随机访问
  • 可以自动扩容
  • 只支持末端插入

list双向链表

  • 不支持随机访问
  • 支持任意位置插入
  • 无需扩容

deque双端队列(vector< Array >)

  • 支持随机访问(伪随机)
  • 微量扩容
  • 支持前后端插入

看下面一段代码

#include<iostream>
#include<deque>
using namespace std;
struct T{~T(){cout<<"T析构";}}
void Add_head(deque<int>&q){q.push_front(1);cout<<&q.front()<<" "<<endl;
}void Add_tail(deque<int>&q){q.push_back(1);cout<<&q.back()<<" "<<endl;
}int main(){deque<int>q;for(int i=0;i<10;i++) Add_head(q);for(int i=0;i<10;i++) Add_tail(q);
}

在这里插入图片描述
从地址大概可以看出这个东西是一块一块的,块的大小是固定的,而且块内地址是连续的,想要获取更多的信息的话还要结合源码,网上有说deque是数组链表的,但如果简单的理解为链表的话就大错特错了,这么理解的话随机访问是实现不了的,我觉得理解为一种特殊的二维数组比较好。

下面看具体一些功能的实现

看下_Deque_val模板,_Block_size 表示单个数组元素个数,这里是根据元素的大小决定单个数组的大小,_Mapptr 是桶里面装一维数组的指针,_Mapsize桶的大小根据注释可以看到桶的扩容是指数级的,_Myoff存储偏移量,_Mysize存储元素个数
注意:这个偏移量是针对_Mapptr 的头部开始到当前元素的偏移量,此外单个数组的大小受限于数据大小,所以很多时候双端队列会表现的像维护在指针数组上的链表

// CLASS TEMPLATE _Deque_val
template <class _Val_types>
class _Deque_val : public _Container_base12 {
public:using value_type      = typename _Val_types::value_type;using size_type       = typename _Val_types::size_type;using difference_type = typename _Val_types::difference_type;using pointer         = typename _Val_types::pointer;using const_pointer   = typename _Val_types::const_pointer;using reference       = value_type&;using const_reference = const value_type&;using _Mapptr         = typename _Val_types::_Mapptr;
private:static constexpr size_t _Bytes = sizeof(value_type);
public:static constexpr int _Block_size =_Bytes <= 1 ? 16 : _Bytes <= 2 ? 8 : _Bytes <= 4 ? 4 : _Bytes <= 8 ? 2 : 1; // elements per block (a power of 2)_Deque_val() noexcept : _Map(), _Mapsize(0), _Myoff(0), _Mysize(0) {}size_type _Getblock(size_type _Off) const noexcept {// NB: _Mapsize and _Block_size are guaranteed to be powers of 2return (_Off / _Block_size) & (_Mapsize - 1);}_Mapptr _Map; // pointer to array of pointers to blockssize_type _Mapsize; // size of map array, zero or 2^Nsize_type _Myoff; // offset of initial elementsize_type _Mysize; // current length of sequence
};

这个是_Mapptr 这个桶的基类,可以看到使用的是二级指针,它的作用就是维护一种二维的结构,这时候可能好奇了它作为一个单独的模板是如何适配作为_Deque_val 模板类型的一部分的。

template <class _Ty>
struct _Deque_simple_types : _Simple_types<_Ty> {using _Mapptr = _Ty**;
};

这个适配器完成了适配工作,将多个类适配为一个类很大程度上提高了代码的可读性,使得代码可以更高的遵循开闭原则。

template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwiseusing type = _Ty1;
};
template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {using type = _Ty2;
};
template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;

deque中迭代器访问元素,使用偏移量获取存储的行和列。

_NODISCARD reference operator*() const noexcept {const auto _Mycont = static_cast<const _Mydeque*>(this->_Getcont());
#if _ITERATOR_DEBUG_LEVEL != 0_STL_VERIFY(_Mycont, "cannot dereference value-initialized deque iterator");_STL_VERIFY(_Mycont->_Myoff <= this->_Myoff && this->_Myoff < _Mycont->_Myoff + _Mycont->_Mysize,"cannot deference out of range deque iterator");
#endif // _ITERATOR_DEBUG_LEVEL != 0_Size_type _Block = _Mycont->_Getblock(_Myoff);_Size_type _Off   = _Myoff % _Block_size;return _Mycont->_Map[_Block][_Off];}

那么现在就有一个问题,如果插入元素时集中在头部或者尾部时它会直接扩容吗?带着问题接着分析。

首先看一下emplace_back函数

template <class... _Tys>void _Emplace_back_internal(_Tys&&... _Vals) {if ((_Myoff() + _Mysize()) % _Block_size == 0 && _Mapsize() <= (_Mysize() + _Block_size) / _Block_size) {_Growmap(1);}_Myoff() &= _Mapsize() * _Block_size - 1;size_type _Newoff = _Myoff() + _Mysize();size_type _Block  = _Getblock(_Newoff);if (_Map()[_Block] == nullptr) {_Map()[_Block] = _Getal().allocate(_Block_size);}_Alty_traits::construct(_Getal(), _Unfancy(_Map()[_Block] + _Newoff % _Block_size), _STD forward<_Tys>(_Vals)...);++_Mysize();}

可以看到整体逻辑和vector差不多,emplace_front也差不多就不看了,因为站在函数角度它的不需要关注是头插还是尾插。
这里的策略依旧是可以插入时插入不能插入时进行处理,直接看扩容处理函数,和直接注释的一样,如果没有可插入的空间时桶的大小变为原来的二倍,然后将指针挪到新的数组的中间即可,偏移量这些重新计算,这个环节虽然看起来很麻烦但是注意这里是针对指针的而不是针对元素的,所以说效率还是蛮高的。

void _Growmap(size_type _Count) { // grow map by at least _Count pointers, _Mapsize() a power of 2static_assert(1 < _Minimum_map_size, "The _Xlen() test should always be performed.");_Alpty _Almap(_Getal());size_type _Newsize = 0 < _Mapsize() ? _Mapsize() : 1;while (_Newsize - _Mapsize() < _Count || _Newsize < _Minimum_map_size) {// scale _Newsize to 2^N >= _Mapsize() + _Countif (max_size() / _Block_size - _Newsize < _Newsize) {_Xlen(); // result too long}_Newsize *= 2;}_Count = _Newsize - _Mapsize();size_type _Myboff = _Myoff() / _Block_size;_Mapptr _Newmap   = _Almap.allocate(_Mapsize() + _Count);_Mapptr _Myptr    = _Newmap + _Myboff;_Myptr = _STD uninitialized_copy(_Map() + _Myboff, _Map() + _Mapsize(), _Myptr); // copy initial to endif (_Myboff <= _Count) { // increment greater than offset of initial block_Myptr = _STD uninitialized_copy(_Map(), _Map() + _Myboff, _Myptr); // copy rest of old_Uninitialized_value_construct_n_unchecked1(_Myptr, _Count - _Myboff); // clear suffix of new_Uninitialized_value_construct_n_unchecked1(_Newmap, _Myboff); // clear prefix of new} else { // increment not greater than offset of initial block_STD uninitialized_copy(_Map(), _Map() + _Count, _Myptr); // copy more old_Myptr = _STD uninitialized_copy(_Map() + _Count, _Map() + _Myboff, _Newmap); // copy rest of old_Uninitialized_value_construct_n_unchecked1(_Myptr, _Count); // clear rest to initial block}_Destroy_range(_Map() + _Myboff, _Map() + _Mapsize());if (_Map() != _Mapptr()) {_Almap.deallocate(_Map(), _Mapsize()); // free storage for old}_Map() = _Newmap; // point at new_Mapsize() += _Count;}

stack和queue其实都是deque的适配器类,下面举个例子

template<class T,class dq=deque<T>>
class stk{
protected:dq d;
public:void pop(){d.pop_back();}void push(const T& val){d.push_back(val);}const T& top()const{return d.back();}
};

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

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

相关文章

LTE信令和协议

****部分笔记***** 控制面连接&#xff08;Control Plane Connection&#xff09;是在LTE和5G网络中的一个重要概念&#xff0c;它用于在设备和网络之间传输控制信息。控制面连接主要用于以下几种情况&#xff1a; 设备接入和鉴权&#xff1a;当设备需要接入网络时&#xff0c…

ISP-VPN实验

文章目录 ISP-VPN实验一&#xff0c;实验拓扑二、实验要求三、IP规划四、实验配置1、IP配置R1的配置R2的配置R3的配置R4的配置R5的配置 2、配置缺省路由3、认证与被认证配置4、HDLC封装5、构建MGRE和GRE6、整个私有网络基于RIP全网可达7、查看路由配置和PC端配置8、PC端pingR5的…

环境影响与碳排放生命周期评估应用及案例分析

生命周期分析 (Life Cycle Analysis, LCA) 是评价一个产品系统生命周期整个阶段——从原材料的提取和加工&#xff0c;到产品生产、包装、市场营销、使用、再使用和产品维护&#xff0c;直至再循环和最终废物处置——的环境影响的工具。这种方法被认为是一种“从摇篮到坟墓”的…

用JSch实现远程传输文件并打包成jar

本文将简单介绍一下 JSch 这个Java的第三方库的一个简单用法&#xff0c;并以此为实例&#xff0c;讲解 IntelliJ 中打包成 jar 包的2种方式。 实现目标 我们的目标是&#xff0c;做出一个jar包&#xff0c;它能够实现类似于 scp 命令的远程传输文件的功能。用法如下&#xf…

day 2| 上下文Context

再次来解释一下上下文&#xff0c;我也忘了&#xff0c;问下chatgpt 上下文在 Go Web 开发中是指与当前 HTTP 请求相关的环境信息&#xff0c;它提供了更多的控制和功能来处理 HTTP 请求和响应。也就是对get和POST方法的强化 对于处理 HTML 或 JSON 等响应内容&#xff0c;上…

应急响应靶机训练-Linux2题解

前言 接上文&#xff0c;应急响应靶机训练Linux2 靶机地址&#xff1a;应急响应靶机-Linux(2) 题解 登录虚拟机&#xff1a; 修改面板密码 提交攻击者IP 答案&#xff1a;192.168.20.1 查看宝塔日志即可 用的net直接是网关 提交攻击者修改的管理员密码(明文) 答案&…

LeetCode---390周赛

题目列表 3090. 每个字符最多出现两次的最长子字符串 3091. 执行操作使数据元素之和大于等于 K 3092. 最高频率的 ID 3093. 最长公共后缀查询 一、每个字符最多出现两次的最长子字符串 非常经典的滑动窗口问题&#xff0c;即动态维护一段区间&#xff0c;使得这段区间满足…

JUC:park/unpark的用法与原理

park / unpark 用法 // 暂停当前线程 LockSupport.park(); // 恢复某个线程的运行 LockSupport.unpark(暂停线程对象)**先说结论&#xff1a;**无论unpark在park前还是后&#xff0c;都可以解除暂停状态。 先park在unpark可以成功运行&#xff1a; Thread t1 new Thread((…

全局UI方法-弹窗二-列表选择弹窗(ActionSheet)

1、描述 定义列表弹窗 2、接口 ActionSheet.show(value:{ title: string | Resource, message: string | Resource, autoCancel?: boolean, confrim?: {value: string | Resource, action: () > void }, cancel?: () > void, alignment?: DialogAlignment, …

顺序表专题

文章目录 目录1. 数据结构相关概念1.1 什么是数据结构1.2 为什么需要数据结构 2. 顺序表的概念及结构3. 顺序表分类4. 实现动态顺序表4.1 初始化4.2 顺序表的尾部插入4.3 打印顺序表4.4 顺序表的头部插入4.5 顺序表的尾部删除4.6 顺序表的头部删除4.7 指定位置之前插入数据4.8 …

词-词共现概率与Transformer

1.词词共现概率 词-词共现概率是自然语言处理中的一个关键概念&#xff0c;它描述的是在一段文本或一个大型语料库中&#xff0c;任意两个词在同一上下文中共同出现的概率。具体来说&#xff0c;如果我们在分析语料库时发现词A和词B经常相邻出现或者在一定距离范围内出现&#…

ajax, axios, 同步和异步

Ajax和同步与异步 今日目标&#xff1a; 1.利用原生ajax发送get和post请求 2.利用axios发送get和post请求 3.同步和异步 01-Ajax概述【了解】 概念&#xff1a; async javascript and xml: 异步的js和xml优点&#xff1a; 无刷新提交数据&#xff0c;用户体验好缺点&…

kafka学习笔记02(小滴课堂)

Kafka命令行生产者发送消息和消费者消费消息实战 已存在的kafka不能重复创建。 broker设置的是1&#xff0c;factor大于broker了&#xff0c;所以报错。 生产者发送消息&#xff1a; kafka列表出现了新的kafka。 我们使用这个kafka。 我们启动消费者&#xff1a; 我们现在不从…

PYTHON调离线语音合成并实时播放

1、资源下载&#xff08;可以去讯飞官网控制台下载&#xff09; 2、实现代码 # Python调普通离线合成并实时播放 import wave from ctypes import * import pyaudio""" 1、 在Python中某些时候需要C做效率上的补充&#xff0c;Python中的ctypes模块可以很方便…

csv文件转换成list 代码

hb [] h open("your_file.csv","r",encoding "utf-8") data h.read() #print(type(data)) rows data.split("\n") # 数据切分&#xff0c;以换行&#xff08;“\n”&#xff09;为基准&#xff0c;形成一个list&#xff0c;每一行…

【Qt 学习笔记】Day1 | Qt 背景介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Day1 | Qt 背景介绍 文章编号&#xff1a;Qt 学习笔记 / 01 文章目录…

C/C++语言实现简易通讯录 [含文件操作,循环双链表]

文章目录 C/C语言实现简易通讯录概要基本功能运行截图展示主要代码展示 &#x1f396; 博主的CSDN主页&#xff1a;Ryan.Alaskan Malamute &#x1f4dc; 博主的代码仓库主页 [ Gitee ]&#xff1a;ryanala [GitHub]&#xff1a; Ryan-Ala C/C语言实现简易通讯录 ⚠⚠⚠ …

话题通信的python实现

一、发布者Publisher的python实现 step1&#xff1a;在scripts文件夹中创建py节点 step2&#xff1a;第一行是为了指定解释器&#xff0c;Ubuntu20.04是python3&#xff0c;比他低的版本是python。第二行是为了指定编码方式。第五行中&#xff0c;引用index.ros.org中数据类型…

RVM安装ruby笔记

环境 硬件&#xff1a;Macbook Pro 系统&#xff1a;macOS 14.1 安装公钥 通过gpg安装公钥失败&#xff0c;报错如下&#xff1a; 换了几个公钥地址&#xff08;hkp://subkeys.pgp.net&#xff0c;hkp://keys.gnupg.net&#xff0c;hkp://pgp.mit.edu&#xff09;&#xff0c;…

论文笔记:Retrieval-Augmented Generation forAI-Generated Content: A Survey

北大202402的RAG综述 1 intro 1.1 AICG 近年来&#xff0c;人们对人工智能生成内容&#xff08;AIGC&#xff09;的兴趣激增。各种内容生成工具已经精心设计&#xff0c;用于生产各种模态下的多样化对象 文本&代码&#xff1a;大型语言模型&#xff08;LLM&#xff09;…