目录

deque

核心本质与定位

与stack和queue的关系:

deque的使用

deque的底层实现

deque的原理介绍

deque的缺陷

总结:


deque

deque文档 : deque

翻译:

双端队列

  • deque(通常发音类似“deck”)是“double-ended queue”(双端队列)的非常规缩写。双端队列是动态大小的序列容器,可在两端(前端或后端)进行扩展或收缩操作。
  • 具体的库可能会以不同方式实现 deque,一般是某种形式的动态数组。但无论如何,它们允许通过随机访问迭代器直接访问单个元素,并且容器会根据需要自动扩展或收缩来管理存储。
  • 因此,它们提供的功能与 vector 类似,但在序列的开头也能高效地插入和删除元素,而不仅仅是在末尾。但与 vector 不同的是,deque 不保证所有元素都存储在连续的内存位置:通过偏移一个元素的指针来访问 deque 中的其他元素会导致未定义行为。
  • vector 和 deque 提供的接口非常相似,可用于类似的场景,但在内部工作方式上有很大不同:
  • vector 使用需要偶尔重新分配的单个数组来实现增长,而 deque 的元素可以分散在不同的存储块中,容器在内部保存必要的信息,以常数时间直接访问任何元素,并通过迭代器提供统一的顺序接口。因此,deque 在内部比 vector 更复杂,但这使得它在某些情况下(尤其是超长序列,重新分配开销更大时)能更高效地增长。
  • 对于需要频繁在开头或末尾之外的位置插入或删除元素的操作,deque 的表现比 list 和 forward_list 更差,且迭代器和引用的一致性也更弱。

在 C++ STL 中,deque(双端队列,Double-Ended Queue) 是核心的序列式容器之一,设计目标是平衡“两端高效操作”与“随机访问”能力,填补了 vector(尾部高效、头部低效)和 list(两端高效、无随机访问)的功能空白。虽然它名字里有队列,但是并不是队列,并不要求先进先出。

核心本质与定位

  1. 基础容器而非适配器:deque 是直接管理内存和元素的底层容器,与 vector、list 同级,不属于 stack/queue 这类“封装底层容器的适配器”,能独立提供完整的元素操作接口。
  2. 功能核心:以 O(1) 时间复杂度实现头部和尾部的插入/删除操作,同时支持 O(1) 随机访问(像 vector 一样用  []  下标访问元素),是兼顾“两端灵活性”和“访问效率”的中间选择。

与stack和queue的关系:

在上篇文章中,在介绍stack和queue时就提到了deque,deque 是 stack 和 queue 的“默认底层实现”,在 C++ 中,stack、queue 与 deque 的核心关系是 “容器适配器”与“默认底层基础容器”的依赖关系,具体可拆解为两点:

1. deque 是 stack 和 queue 的“默认底层实现”
stack(栈)和 queue(队列)本身不存储元素,必须依赖一个基础容器来管理数据,而 deque 是 STL 为它们预设的默认底层容器。

  • 当你直接定义  stack<int> s;  或  queue<int> q;  时,底层实际是用 deque 来存储元素的,相当于  stack<int, deque<int>> s;  和  queue<int, deque<int>> q; 。
  • 选择 deque 作为默认的原因:deque 支持 O(1) 时间复杂度的两端操作(头部/尾部插入/删除),刚好匹配 stack(仅需栈顶,即 deque 尾部操作)和 queue(需队尾插入、队首删除)的功能需求,且比 vector(头部操作低效)、list(无随机访问)更适配。

2. 三者本质不同:适配器 vs 基础容器

  • stack/queue:是“容器适配器”,作用是“封装底层容器的接口”,强制特定的数据访问规则(stack 先进后出、queue 先进先出),自身不具备元素存储和内存管理能力。
  • deque:是“基础序列容器”,能独立管理元素存储和内存,提供完整的元素操作接口(如随机访问、两端/中间操作),是 stack/queue 实现功能的“底层支撑”。

下面我们就来详细学习deque的主要内容:

deque的使用

deque 、 vector  和  list  均作为 STL 基础序列容器,在成员函数上的共同点主要有以下几类:

  • 元素增删(基础):都支持  push_back (尾部添加元素)和  pop_back (尾部删除元素)操作;也都提供  clear  函数用于清空容器内所有元素。
  • 状态查询:均包含  empty  函数判断容器是否为空, size  函数获取容器内元素的个数。
  • 迭代器遍历:都提供  begin 、 end  用于正向遍历元素, rbegin 、 rend  用于反向遍历元素。
  • 元素访问(部分):都支持  front  获取容器头部元素的引用, back  获取容器尾部元素的引用。
  • 赋值与交换:都实现了  operator=  用于容器间的赋值操作,也都有  swap  函数用于交换两个同类型容器的元素。
#include<deque>
#include<iostream>
using namespace std;void test_deque()
{deque<int> dp;//尾插dp.push_back(1);dp.push_back(2);dp.push_back(3);dp.push_back(4);//头插dp.push_front(10);dp.push_front(20);dp.push_front(30);dp.push_front(40);//尾删dp.pop_back();//头删dp.pop_front();//随机访问for (size_t i = 0; i < dp.size(); i++){cout << dp[i] <<" ";}cout << endl;
}
int main()
{test_deque();return 0;
}

可以看到它既支持头插尾插,又支持头删尾删,还支持随机访问,它融合了vector和list的优点,从使用的角度,避开了它们各自的缺点:

  • list的缺点:不支持随机访问
  • vector的缺点:头部和中间插入删除效率低

deque的底层实现

deque 、 vector  和  list  均作为 STL 基础序列容器,它们之间究竟有什么区别呢 ? 首先我们来看一下vector和list的优缺点:

vector

核心特点:

  • 基于连续内存空间存储,支持随机访问(通过下标  []  或  at()  直接定位元素)。
  • 元素在内存中紧密排列,尾部增删效率高,中间/头部增删效率低。
  • 容量(capacity)会自动扩容(通常扩容为原容量的1.5-2倍),扩容时需拷贝旧元素到新内存,可能产生性能开销。

优点:

  • 1. 随机访问速度快(时间复杂度 O(1)),适合需要频繁通过下标读取元素的场景(如数组遍历、索引访问)。
  • 2. 内存缓存利用率高(连续内存易被CPU缓存命中),遍历元素时速度比  list  快。
  • 3. 尾部  push_back / pop_back  操作高效(O(1),无内存移动)。
  • 4. 接口简洁,支持  reserve()  预分配容量,避免频繁扩容,优化性能。

缺点:

  • 1. 中间或头部插入/删除元素效率低(O(n)):需移动插入点后所有元素,内存移动开销大。
  • 2. 扩容时可能浪费内存(扩容后的容量大于实际元素个数),且拷贝旧元素会产生额外性能消耗。
  • 3. 不适合存储大量大型对象(扩容时拷贝大型对象开销高),也不适合频繁修改中间元素的场景。

list

核心特点:

  • 基于非连续内存空间存储,元素通过前后指针连接,不支持随机访问。
  • 元素在内存中分散存储,任意位置增删效率高,访问元素需从头/尾遍历。
  • 无容量概念,元素增删时直接分配/释放单个节点内存,无需扩容和拷贝。

优点:

  • 1. 任意位置(头部、中间、尾部)插入/删除元素效率高(时间复杂度 O(1)):只需修改对应节点的指针,无需移动其他元素。
  • 2. 无扩容开销,内存分配灵活,不会浪费内存(每个元素按需分配节点)。
  • 3. 适合频繁在中间位置增删元素的场景(如链表结构、频繁插入的队列)。

缺点:

  • 1. 不支持随机访问(访问指定元素需遍历链表,时间复杂度 O(n)),无法通过下标快速定位元素。
  • 2. 内存缓存利用率低(非连续内存难被CPU缓存命中),遍历元素时速度比  vector  慢。
  • 3. 每个元素需额外存储前后指针,内存开销比  vector  大(存储相同数量元素时, list  占用更多内存)。
  • 4. 不支持  reserve()  预分配内存,也无法通过下标访问,功能比  vector  受限。

结合上面deque的使用我们可以看出deque既支持头插尾插,又支持头删尾删,还支持随机访问,它融合了vector和list的优点,从使用的角度,避开了它们各自的缺点,如果deque真的像上面说的那么优秀,那么vector和list可能就被淘汰了。

其实,deque还是有缺陷的,它的底层怎么实现的呢?

deque的原理介绍

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

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

这张图准确展示了 deque(双端队列)的底层结构,核心由三部分组成:

  1. 中控器(map):是指针数组,每个指针指向一个缓冲区(buffer)(连续内存块),用于管理所有分段的内存。
  2. 缓冲区(buffer):多个分段的连续内存块,实际存储元素。图中不同缓冲区的填充状态体现了元素的分布(如中间缓冲区有部分元素已存储)。
  3. 迭代器(iterator):
  • start(iterator) :标记 deque 的起始范围,由  cur (当前元素指针)、 first (起始缓冲区的起始指针)、 last (起始缓冲区的末尾指针)、 node (指向中控器对应节点的指针)组成。
  • finish(iterator) :标记 deque 的末尾范围,结构与  start  一致,用于界定元素的结束位置。

这种结构的核心优势是:

  • 双端操作高效:在起始缓冲区执行  push_front / pop_front ,在末尾缓冲区执行  push_back / pop_back ,均只需操作分段内存的两端,无需整体移动元素。
  • 随机访问可行:通过中控器的指针数组快速定位目标元素所在的缓冲区,再结合迭代器的  cur  指针偏移,实现 O(1) 时间的随机访问。
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问 的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

  1. 中控器(map):一个指针数组,每个指针指向一块连续的内存区域(称为“缓冲区 buffer”)。
  2. 缓冲区(buffer):分段的连续内存块,用于实际存储元素,解决了 vector 单一连续内存扩容时的拷贝开销问题。
  3. 迭代器(iterator):通过  cur (当前元素指针)、 first (缓冲区起始指针)、 last (缓冲区末尾指针)、 node (指向中控器对应节点的指针),实现跨分段的元素访问和迭代。

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

这张图通过可视化的方式,解释了 deque 执行  push_back (尾插)操作时的内部逻辑:
 
1. 缓冲区与中控器的配合:
deque 底层由多个缓冲区(buffer)(连续内存块)组成,这些缓冲区的地址由中控器(map,指针数组)管理。图中可以看到多个缓冲区(如存储 0-7、8-15、16-19 及后续元素的块),中控器的指针数组指向这些缓冲区的起始地址。
2. 尾插的执行流程:
当调用  push_back(0) 、 push_back(1) 、 push_back(2)  时:
- deque 会找到最末尾的缓冲区(图中存储 16-19 及后续元素的块)。
- 从该缓冲区的空闲位置开始依次插入元素 0、1、2。
- 若当前缓冲区已满,deque 会自动分配新的缓冲区,并通过中控器的指针数组维护新缓冲区的地址,保证后续尾插操作的连续性。
3. 迭代器的角色:
 finish(iterator)  标记了 deque 的末尾范围,其内部的  cur  指针会随着尾插操作不断后移,始终指向“下一个可插入的位置”,从而维护了 deque“假想连续”的逻辑结构——尽管物理内存是分段的,但通过迭代器和中控器的配合,用户可以感知到元素是连续存储的。
 
简言之,这张图直观呈现了 deque 如何通过分段缓冲区 + 中控器管理 + 迭代器标记,实现高效的尾插操作,同时维持“逻辑连续”的抽象结构。

deque的缺陷

deque 虽然在双端操作和随机访问之间做了平衡,但也存在以下缺陷:
1. 中间操作效率低
在中间位置(非头部、非尾部)插入或删除元素时,需要移动该位置前后的元素,时间复杂度为 O(n),远不如 list(中间操作 O(1))高效。
2. 随机访问略逊于 vector
尽管支持随机访问,但由于内存是分段存储的,访问元素时需要先通过中控器定位到对应的缓冲区,再在缓冲区内偏移访问,实际速度比 vector(单一连续内存直接访问)稍慢。
3. 内存结构复杂
底层需要维护“中控器(map)+ 分段缓冲区”的结构,实现逻辑比 vector 和 list 更复杂,这也导致其迭代器的实现和维护成本较高。
4. 迭代器稳定性不足
当在 deque 的头部或尾部之外的位置进行插入/删除操作,或者因中控器扩容导致缓冲区地址变化时,迭代器可能会失效,稳定性不如 list(list 仅被删除节点的迭代器失效)。
5. 不适合大量中间操作场景
如果业务场景中需要频繁对中间元素进行增删,deque 的性能表现会明显不如 list;而如果需要频繁随机访问,又不如 vector 高效,存在一定的使用场景局限性。

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其 是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实 际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看 到的一个应用就是,STL用其作为stack和queue的底层数据结构
为什么选择deque作为stackqueue的底层默认容器?
stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性 结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据 结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如 list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:
  • 1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
  • 2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的 元素增长时,deque不仅效率高,而且内存使用率高。 结合了deque的优点,而完美的避开了其缺陷。

总结:

摘要: 双端队列deque是STL中的动态序列容器,支持两端高效插入/删除和随机访问,填补了vector和list的功能空白。其底层通过"中控器+分段缓冲区"实现,兼具vector的随机访问和list的双端操作优势,但中间操作效率较低。deque是stack和queue的默认底层容器,因其完美适配二者仅需单端或双端操作的特点。虽然deque平衡了功能与性能,但在实际应用中仍存在遍历效率低、中间操作慢等缺陷,通常在需要频繁随机访问时选择vector,频繁中间操作时选择list更为合适。

感谢大家的观看!

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

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

相关文章

布草洗涤厂设备租赁押金原路退回系统—东方仙盟

设备租赁状态设备管理添加设备设备收押金设备退押金在布草洗涤行业的运营版图中&#xff0c;设备租赁是连接厂商与客户的重要纽带&#xff0c;而押金的收取与退还则是这一环节中关乎信任与效率的关键节点。未来之窗布草洗涤厂深谙此道&#xff0c;专为设备租赁业务打造的 “押金…

换源rocklinux和centos

一、Rockylinux换源&#xff0c;国外的源换成国内的源#nmcli connection modify ens33 ipv4.addresses 192.168.121.11 ipv4.gateway 192.168.121.2 ipv4.method manual ipv4.dns 114.114.114.114 connection.autoconnect yes修改地址#systemctl stop firewalld#systemctl diab…

第一部分:服务器硬件配置

目录1.1 服务器上架与连线1.2 启用CPU虚拟化功能&#xff08;BIOS设置&#xff09;1.3 配置RAID存储步骤1&#xff1a;进入RAID配置界面步骤2&#xff1a;确认RAID控制器信息步骤3&#xff1a;创建系统RAID&#xff08;用于安装ESXi&#xff09;步骤4&#xff1a;创建数据RAID&…

手搓一个 DELL EMC Unity存储系统健康检查清单

写在前面对于DELL EMC存储系统Unity的一些深度的健康检查通过Web的Unisphere图形化界面是做不到的&#xff0c;图形化界面只能看到是否有告警&#xff0c;物理的东西是否有问题的&#xff0c;逻辑的Pool和LUN等是否ready&#xff0c;再深入的潜在的问题是查不到的。另外&#x…

【数据结构】二叉树的概念

01 概念定义&#xff1a;二叉树既然叫二叉树&#xff0c;顾名思义即度最大为2的树称为二叉树。 它的度可以为 1 也可以为 0&#xff0c;但是度最大为 2 。 一颗二叉树是节点的一个有限集合&#xff0c;该集合&#xff1a;① 由一个根节点加上两棵被称为左子树和右子树的二叉树组…

【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?

单独编译kernel依赖如下几个源码&#xff1a;【交叉编译工具链】prebuilts/clang/host/linux-x86/clang-r487747c【内核源码】kernel-6.1为什么Android下编译内核使用clang作为交叉编译工具链而不是GCC&#xff1f;Android 14 选择使用预置的 Clang 工具链&#xff08;如 clang…

什么是Redis的Pipeline

介绍Redis的Pipeline是一种网络优化技术&#xff0c;在没有Pipeline的时候&#xff0c;客户端往redis发送请求&#xff0c;客户端需要等到redis响应之后才能发送下一个请求。而Pipeline&#xff0c;使redis可以一次性接收多个请求。减少了通信次数&#xff0c;显著的提高了性能…

【ElementUI el-table跨页勾选】

一、el-table需加上refs和 row-key属性 二、type"selection"勾选框 需加上 reserve-selection储备选择属性 三、在分页请求数据时&#xff0c;触发 setSelected()方法 四、在 selection-change变化时保存 selectedRows <el-table ref"tables" :data&quo…

论文阅读/博弈论/拍卖:《Truthful Auction for Cooperative Communications》

摘要&#xff1a;一方面&#xff0c;协作通信由于其在提升无线网络容量方面的巨大潜力而日益受到关注。另一方面&#xff0c;协作通信技术的实际应用却很少见&#xff0c;即使在一些对带宽需求极高的应用场景中&#xff0c;系统设计者也并未采用协作通信技术来开发创新的网络解…

系统软中间件:连接软件与硬件的桥梁

理解“系统软中间件”这个术语很重要&#xff0c;它实际上是两个紧密相关但又不同的概念的组合&#xff1a; 系统软件中间件 严格来说&#xff0c;“系统软中间件”不是一个标准的独立术语。它通常指的是属于系统软件范畴的中间件&#xff0c;或者理解为作为系统软件重要组成部…

音视频学习(六十四):avc1 hvc1和hev1

基础概念缩写编码标准FourCC说明AVC/H.264Advanced Video Codingavc1最常用的 H.264 编码标识符&#xff0c;兼容 MP4/MOV/FMP4 等容器。HEVC/H.265High Efficiency Video Codinghvc1HEVC 视频流在 MP4/FMP4 中常用标识符&#xff0c;要求存储 NALU 的 VPS/SPS/PPS&#xff08;…

【WIT】编程百问一

01 什么时postman&#xff1f; Postman 是一款专门用于帮助开发人员处理 API 的工具&#xff0c;它的作用主要有以下几个方面&#xff1a; 方便调试 API&#xff1a;就像你打电话给别人要先拨对号码一样&#xff0c;开发人员要让不同的软件系统之间通过 API 进行通信&#xff…

RAG 从入门到放弃?丐版 demo 实战笔记(go+python)

背景 我当前有一个业务系统&#xff0c;希望能添加一个机器人助手。直接使用大模型&#xff0c;由于缺少相关的业务数据&#xff0c;效果并不理想&#xff0c;了解一下 RAG。 什么是 RAG RAG(Retrieval Augmented Generation)&#xff0c;搜索引擎 大模型。 简单来说就是从…

《IDEA 突然“三无”?三秒找回消失的绿色启动键、主菜单和项目树!》

目录 一、左上角绿色启动键凭空消失 1.1 解决办法 二、顶部 File / Edit / View... 整条主菜单栏 罢工 2.1 解决办法 三、左侧 Project 工具窗口 集体失联&#xff0c;只剩 External Libraries 孤零零 3.1 解决办法 昨天下午撸代码&#xff0c;不知道按到了哪儿&#xff…

软件工程实践二:Spring Boot 知识回顾

文章目录一、创建项目&#xff08;Spring Boot 向导&#xff09;二、项目最小代码示例三、运行与验证四、标准目录结构与说明五、Maven 依赖最小示例&#xff08;仅供参考&#xff09;六、常用配置&#xff08;application.yml 示例&#xff09;七、返回 JSON 与统一异常八、Va…

【系列文章】Linux中的并发与竞争[04]-信号量

【系列文章】Linux中的并发与竞争[04]-信号量 该文章为系列文章&#xff1a;Linux中的并发与竞争中的第4篇 该系列的导航页连接&#xff1a; 【系列文章】Linux中的并发与竞争-导航页 文章目录【系列文章】Linux中的并发与竞争[04]-信号量一、信号量二、实验程序的编写2.1驱动…

Elasticsearch启动失败?5步修复权限问题

文章目录&#x1f6a8; 为什么会出现这个问题&#xff1f;✅ 解决方案&#xff1a;修复数据目录权限并确保配置生效步骤 1&#xff1a;确认数据目录存在且权限正确步骤 2&#xff1a;确认 elasticsearch.yml 中的配置步骤 3&#xff1a;**删除或清空 /usr/share/elasticsearch/…

Docker push 命令:镜像发布与管理的艺术

Docker push 命令&#xff1a;镜像发布与管理的艺术1. 命令概述2. 命令语法3. 核心参数解析4. 推送架构图解5. 完整工作流程6. 实战场景示例6.1 基础推送操作6.2 企业级推送流程6.3 多架构镜像推送7. 镜像命名规范详解8. 安全最佳实践8.1 内容信任机制8.2 最小权限原则9. 性能优…

智能合约测试框架全解析

概述 智能合约测试库是区块链开发中至关重要的工具&#xff0c;用于确保智能合约的安全性、正确性和可靠性。以下是主流的智能合约测试库及其详细解析。 一、主流测试框架对比 测试框架开发语言主要特点适用场景Hardhat WaffleJavaScript/TypeScript强大的调试功能&#xf…

【大模型算法工程师面试题】大模型领域新兴的主流库有哪些?

文章目录 大模型领域新兴主流库全解析:国产化适配+优劣对比+选型指南(附推荐指数) 引言 一、总览:大模型工具链选型框架(含推荐指数) 二、分模块详解:优劣对比+推荐指数+选型建议 2.1:训练框架(解决“千亿模型怎么训”) 2.2:推理优化(解决“模型跑起来慢”) 2.3:…