humid1ch blogs

本篇文章

手机用户建议
PC模式 或 横屏
阅读


STL 2022 年 7 月 15 日

[C++-STL] deque的分析

STL源码, 实现 stack 和 queue 都使用了 deque 作为适配器. deque 是什么?它的结构是什么?为什么 Stack和 Queue要用它来作为适配器实现?
STL源码实现 StackQueue 都使用了 deque 作为适配器
deque 是什么?它的结构是什么?为什么 Stack和 Queue要用它来作为适配器实现?

文档中的 deque

官方文档中这样解释 deque, 而通俗来讲 deque 就是一个可以前插后插、前删后删的动态开辟的线性结构的容器
并且, 这个 deque 拥有许多的成员函数:
这些成员函数功能, 好像 list 容器也有, 但是 为什么 StackQueue的要由它作为适配器实现呢?

deque 结构介绍

我们都知道:
  1. vector, 占用的是连续的物理空间, 所以它具有他以下优缺点:

    优点:

    1. 尾插尾删, 时间复杂度为 O(1)
    2. 可以随机访问
    3. cpu高速缓存命中率高

    缺点:

    1. 头插头删、中间插中间删, 需要挪动数据, 效率低
    2. 需要扩容 消耗资源, 并且有可能造成空间的浪费
  2. list, 占用的物理空间是不连续的, 所以它具有以下优缺点:

    优点:

    1. 任意位置插入删除, 时间复杂度都是 O(1)
    2. 按需申请空间不会造成浪费

    缺点:

    1. 不可随机访问
    2. cpu高速缓存命中率低

其实, 使用 vector 一般都是有需要随机访问的需求;使用 list 一般是有频繁不定位置插入数据的的需求

这两种容器, vector的随机访问list 的插入删除数据 O(1), 是他们的绝对优势

而 deque 的结构实现, 其实 在一定程度上综合了 vector 和 list 两种容器结构的优点
list 是单个数据节点由指针相互连接
而 deque 的结构就像是 以固定长度的可以存放多个数据的vector 为一个的节点 相互连接起来
但是 并不是像list那样 个节点存储相邻结点的地址, 而是额外提供了一个vector来按顺序存放 deque中各个vector节点的地址
并且为了支持 双端操作, 还提供了 两个迭代器分别指向 首数据位置和末数据位置
所以 deque 的结构, 大致上是这样的:
每段 vector 之间没有实际联系, 而是由另一个容器存放每个vector的地址, 且存放vector地址的容器也不是从首空间开始存放的, 因为需要考虑到头插新vector 需要添加指针
知道了 首元素位置与末元素位置, 有知道了每个vector之间的顺序, 且vector定长, 就可以通过计算位置来实现随机访问
并且, 在deque中尾插尾删、头插头删是不用挪动数据的, 因为 当vector满了之后, 会添加新的 vector
这样的结构在一定程度上综合了 vector 和 list 的优点, 但是都没有优到vector和list的那种程度
所以, 综合一下, deque的优缺点有:

优点:

  1. 可以先计算位置来实现, 随机访问, 效率比list遍历快得多
  2. 头插尾插、头删尾删 时间复杂度都是 O(1)
  3. 扩容代价较小
  4. CPU高速缓存命中率 较高, 比 list 高

缺点:

  1. 中间插入删除效率不行
  2. 虽然可以随机访问, 但是效率 还是比vector差, 因为需要先计算位置
所以, deque 其实就是一个 功能多 但是功能效率不是非常高的 容器

问题: 为什么 Stack 和 Queue 的实现, 要用 deque 来做适配器

要回答这个问题, 就得先明白 Stack 和 Queue 的结构、功能需要什么, 也就是 这两种容器的需求是什么?
  1. 栈 Stack

    先进后出, 只能尾插、尾删, 一般由 顺序表实现

  2. 队列 Queue

    先进先出, 只能尾插、头删, 一般由 链表实现

deque 功能齐全, 头插尾插、头删尾删 效率不差, 结构 综合了 链表与顺序表
其实 deque 适合用于 大量的头插尾插、头删尾删、偶尔随机访问 的场景, 而这 正是 Stack 和 Queue 需要的
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)

作者: 哈米d1ch 发表日期:2022 年 7 月 15 日