『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>,这也是双端队列中最经典的使用场景;

相关文章
|
5天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
22 7
|
23天前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
48 4
|
24天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
50 5
|
24天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
38 2
|
8天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
18 0
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
80 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
86 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
31 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4