【C++】deque双端队列

deque的原理介绍

1.deque(双端队列):是一种双开口的“连续”空间的数据结构,双开口的含义是:在头尾两端都可以进行插入和删除操作,且时间复杂度为O(1)。
在这里插入图片描述
需要注意的是deque并不是真正连续的空间,不是像vector那样底层是连续空间的数组,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,为了管理分段空间,deque容器引入了map,称之为中控器,map是一块连续的空间,其中每个元素是指向缓冲区buffer的指针,缓冲区才是deque存储数据的主体,其底层结构如下图所示:
在这里插入图片描述
2.双端队列底层是一段假象的连续空间,实际上是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器上,因此deque的迭代器就比较复杂,如下图所示:
在这里插入图片描述
中控器包含了map size,指向buffer的指针,deque的开始迭代器与结尾迭代器。

_Tp **_M_map;
size_t _M_map_size;
iterator _M_start;
iterator _M_finish;

deque的优点和缺陷

在了解deque双端队列的优缺点之前,我们先学习一下vector和list的优缺点,有助于更好的比对。

vector的优点

  • 尾插尾删效率很高
  • 支持随机访问(利用下标访问)
  • 相比链表结构。顺序表CPU高速缓存命中率更高(因为顺序表的底层由数组实现,物理地址是连续的)

vector的缺点

  • 头部和中部插入删除效率低(O(N))
  • 扩容:存在性能消耗和空间上的浪费

list的优点

  • 任意位置插入删除效率很高。(O(1))
  • 按需申请和释放,优化了vector在空间上的浪费

list的缺点

  • CPU高速缓存的命中率不高(物理地址空间不连续)
  • 不支持随机访问

deque的优点

与vector比较,deque的优势是:头部插入删除时,不需要搬移元素,效率高,而且在扩容时,也不需要搬移大量的元素。因为deque在头部插入删除的时候,是直接在前面开一块空间(缓冲区buffer)插入元素的,尾插同理,在后面开空间。
与list比较,deque的底层是连续空间,空间利用率比较高,不需要存储额外的字段。

deque的缺点

  • 不适合遍历及排序
    因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下**,并且deque的迭代器非常复杂,deque维护了两个迭代器,start指向第一个缓冲区的第一个元素,finish指向最后一个缓冲区的最后一个元素的下一个位置,cur指向迭代器所指缓冲区的当前元素,first指向迭代器所指缓冲区第一个元素,last指向迭代器所指缓冲区的最后一个元素,node指向中控,当cur等于last,说明本段空间已被使用完毕,通过node++找到中控数组中下一段内存空间的地址(见下图)。而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用场景并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

  • 中间插入删除数据不如list
    从deque的底层结构图中可以看出,中间插入、删除数据仍会产生数据的挪动。

  • 随机访问速度不如vector
    由于deque的中控数组中指向的一段地址空间之间并不连续,所以随机访问时需要计算目标数据处于哪段buffer中的第几个数据。计算方式与磁盘定位扇区类似(LBA地址转化为CHS地址)。

在这里插入图片描述

为什么选择deque作为stack和queue的底层默认容器?

1.stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作;
2.在stack和queue中,元素增长时,deque和vector相比,deque效率更高(扩容时不需要搬移大量数据),内存使用率更高。

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
以下是用C++实现双队列的代码和详细步骤: ```cpp #include <iostream> using namespace std; #define MAXSIZE 100 // 定义双队列的最大长度 class Deque { private: int data[MAXSIZE]; // 双队列的数据存储数组 int left; // 左指针 int right; // 右指针 public: Deque() { // 构造函数,初始化左右指针 left = 0; right = 0; } bool isEmpty() { // 判断队列是否为空 return left == right; } bool isFull() { // 判断队列是否已满 return (right + 1) % MAXSIZE == left; } void push_front(int x) { // 在队头插入元素 if (isFull()) { cout << "Deque is full!" << endl; return; } left = (left - 1 + MAXSIZE) % MAXSIZE; data[left] = x; } void push_back(int x) { // 在队尾插入元素 if (isFull()) { cout << "Deque is full!" << endl; return; } data[right] = x; right = (right + 1) % MAXSIZE; } void pop_front() { // 在队头删除元素 if (isEmpty()) { cout << "Deque is empty!" << endl; return; } left = (left + 1) % MAXSIZE; } void pop_back() { // 在队尾删除元素 if (isEmpty()) { cout << "Deque is empty!" << endl; return; } right = (right - 1 + MAXSIZE) % MAXSIZE; } int front() { // 返回队头元素 if (isEmpty()) { cout << "Deque is empty!" << endl; return -1; } return data[left]; } int back() { // 返回队尾元素 if (isEmpty()) { cout << "Deque is empty!" << endl; return -1; } return data[(right - 1 + MAXSIZE) % MAXSIZE]; } }; int main() { Deque dq; dq.push_front(1); dq.push_back(2); dq.push_front(3); dq.push_back(4); cout << dq.front() << endl; // 输出:3 cout << dq.back() << endl; // 输出:4 dq.pop_front(); dq.pop_back(); cout << dq.front() << endl; // 输出:1 cout << dq.back() << endl; // 输出:2 return 0; } ``` 步骤: 1. 定义一个常量MAXSIZE,表示双队列的最大长度。 2. 定义一个类Deque,包含数据存储数组data和左右指针left、right。 3. 构造函数初始化左右指针。 4. 定义isEmpty()和isFull()函数,分别判断队列是否为空和已满。 5. 定义push_front()和push_back()函数,在队头和队尾插入元素。 6. 定义pop_front()和pop_back()函数,在队头和队尾删除元素。 7. 定义front()和back()函数,分别返回队头和队尾元素。 8. 在main函数中创建Deque对象dq,测试双队列的各种操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_麦子熟了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值