『C++之STL』双端队列 - deque

简介: 『C++之STL』双端队列 - deque

前言

双端队列,Double-ended queue,简称为deque是一种线性结构的一种容器;

在数据结构中出现的顺序表与链表,或者栈与队列都算是线性结构;

在结构中,它与vector相比较会相似一些;

但是在实际当中,双端队列 - deque 包含了vector与list的优点;

  • vector(顺序表)
    支持随机访问,空间连续;尾插尾删效率高,但是头部插入删除以及中间插入删除的开销过大,扩容代价高;
  • list(链表)
    任何位置的插入删除效率高,无扩容代价,但是内存碎片较多,且不支持随机访问;

而deque综合了上面两者部分的优点;

  • deque(双端队列)
    支持随机访问,头尾插入删除效率高,扩容代价低;

deque的结构

为什么双端队列 - deque既能支持头尾插入删除,又能支持下标随机访问?

这应该和它的结构有关;

deque的结构类似于vector,因为和vector一样,总体的框架为一个连续的物理空间;

但是与vector不同的是vector作为类模板容器,数据类型为 T;

而在这里deque所存储的其实为一个指针;

这个指针的类型为T*;

可以理解为一个数组内存储多个小数组从而达到对每个数据进行存储;

但是在结构中的起始位置,为了能便于支持头尾插入删除,初始的buff数组所在的位置并不是在deque总体框架的开头,而是在中间;


deque的接口设置

deque的接口设置与大部分的容器都相同;

在接口设置中较为相似list;

为什么说是list与vector的结合呢,还有一点;

deque重载了operator[];

这也是它可以对数据进行随机访问的一个原因;

那么有个问题,在如此复杂的条件下是怎么进行数据的随机访问?

可以进行假设;

假设存在一个大小为100的deque对象,其中每个buff小数组的大小为10,且100个数据中有3个头插的数据,现在需要去访问它的第25个数据应该怎么进行访问;

只需要2步即可:

用n减去头插的数据数个数(单独未满的buff数组数据个数)再除以数组总大小得是第几个buff数组;

即(25-3)/10;

再用n减去头插的数据数个数(单独未满的buff数组数据个数)再除0数组总大小得是buff数组中的第几个数据;

即(25-3)%10;


数据的插入删除

从上图中可以看出,若是需要进行头删头插或者尾删尾插时,只需要控制每个buff小数组即可;

由于初始buff数组所在位置处中间位置,所以可以更好的进行插入删除;

  • 头部插入删除
    第一次头插时只需要在指向首段buff的位置前再申请一块同样大小的空间即可;
    再进行头插的时候,由于是头插,需要数据从后往前插入;
    删除也为同样的操作;

[如图所示,头插依次插入0,-1];

  • 尾部插入删除
    尾部插入删除与头部插入删除相同,若是该段buff数组已满,则需要新开一个buff小数组用于存储数据;
    删除也是如此;

  • 中间插入删除
    deque中较难的是这个在中间位置的插入删除;
    就如中间插入而言,deque的处理办法有两个办法,但是无论是哪个办法都会有缺点;
    但两点办法的总结也就是:
固定buff数组大小 不固定buff数组大小
若是中间插入时固定buff数组大小,则在中间插入删除时需要大量的挪动数据,造成大量的开销 若是中间插入时不固定buff数组大小,即在每次插入删除的时候,尤其是在插入时,扩容所对应的buff数组,该方法可以优化中间插入,使得在中间插入时不需要大量的挪动数据,但是对应的缺点是无法使用/配合%的方式进行下标的随机访问;

然而在STL的源码中所使用的方法为固定buff数组的大小,也就是抛弃了deque的中间插入删除;


双端队列的应用场景

在实际的应用场景中,使用到双端队列deque的场景并不多;

虽然它结合了vector的优点和list的优点,但是并没有十分的优异,换句话说就是无论是在效率上还是在有点伤都不能完全的取代vectorlist;

在STL中,栈与队列所采用的方式为适配器模式,它们的模板参数为:

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

在适配器模式中的模板参数Container默认为deque<T>,这也是双端队列中最经典的使用场景;

相关文章
|
22天前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
134 77
|
8天前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
42 21
|
22天前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
40 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
22天前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
41 9
|
22天前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
33 7
|
1月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
36 1
|
1月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
58 7
|
1月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
53 0
|
22天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
61 19
|
22天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
41 13