• c++ deque 的使用


     

    目录

    1. deque 的介绍

    2. deque 底层原理

    3. deque 的迭代器 

    4. deque 的接口使用 

    5. deque 和 vector,list 的比较


    1. deque 的介绍

    下面是 deque 的介绍,来自于:deque - C++ Reference (cplusplus.com)  的翻译,您可以不用看,我稍后会对 deque 的一种实现做讲解,以便您能更好的理解 deque 。deque 在编程中并不如 vector 和 list 使用得频繁。模拟实现 deque,这是意义不大的行为。

    1:deque(通常读作 "deck")是双端队列的不规则缩写。双端队列是具有动态大小的序列容器,可以在两端(前端或后端)扩展或收缩。

    2:特定的库可能会以不同的方式实现 deque,通常是作为某种形式的动态数组。但无论如何,它们都允许通过随机访问迭代器直接访问单个元素,并根据需要通过扩展和收缩容器自动处理存储。

    因此,它们提供了与 vector 类似的功能,但也能在序列的开头(而不仅仅是结尾)有效地插入和删除元素。但与 vector 不同的是,deque 不能保证将所有元素都存储在连续的存储位置:通过偏移指向另一个元素的指针来访问 deque 中的元素会导致未定义的行为。

    3:vector 和 deque 提供了非常相似的接口,可用于类似的目的,但两者内部的工作方式却截然不同: vector 使用的是单个数组,偶尔需要重新分配以适应增长;而 deque 的元素可以分散在不同的存储块中,容器内部保存必要的信息,可以在恒定时间内直接访问其中的任何元素,并提供统一的顺序接口(通过迭代器)。

    因此,deque 的内部结构要比 vector 复杂一些,但在某些情况下,deque 可以更高效地增长,尤其是在处理超长序列时,重新分配的代价会更高。

    4:与 list 和 forward_list 相比,对于频繁插入或移除位于开头或结尾以外位置的元素的操作,deques 的性能更差,迭代器和引用的一致性也更差。

    2. deque 底层原理

    deque 中文名,双端队列。但并不是一个队列,因为队列要求先进先出,而 deque 可以头插,头删,尾插,尾删,不符合队列的特征!

    下面将会讲解 deque 的底层原理:

    1:deque 维护了若干个 buff 数组,用于存储数据。除此之外,deque 还维护了一个中控数组 (指针数组),数组中的元素指向 deque 维护的 buff 数组。

    2:中控数组的使用时从中间开始,当中控数组满了之后会直接扩容,然后释放原来的空间。

    3:头插,头删数据。

    下图中橙色的部分表示已经右数据啦!下面演示依次头插数据:1,2。

     头删数据就是删除 2 啦,如果再次头删,就是删除 1 啦!

    4. 尾插,尾删数据。

    下图中橙色的部分表示已经右数据啦!下面演示依次尾插数据:1,2。

     依次尾删数据:先删除 2 再删除 1。

    5:deque 是支持下标随机访问的,在这看似不连续的空间上,是如何做到的呢?

    关键就是每个 buff 的大小必须一样!下面我们来看看是如何做到下标随机访问的!

    橙色部分代表有数据。

    1):我们要访问下标为 i 的位置,首先要判断他在不在第一个 buff 里面,如果在第一个 buff 里面,那么就可以通过这个 buff 的指针直接访问了。

    2):如果下标为 i 的位置不在第一个 buff 里面,那就让 i -= 第一个 buff 数据的个数。然后通过数学运算:

    i /= buff.size()

    i %= buff.size()

    找到下标为 i 的元素在第几个 buff 数组,在这个 buff 数组的第几个位置。

    然后通过中控数组,找到这个 buff 访问对应位置上的数据即可。

    插入,删除,operator[] 的逻辑倒是讲通了!但是具体怎么实现的呢!deque 的迭代器发挥了至关重要的作用。

    3. deque 的迭代器 

    deque 的迭代器封转了四个指针:

    1:cur:指向一个 buff 中的某个位置,表示该位置的迭代器。

    2:first:指向一个 buff 数组的第一个位置,是第一个位置,不是第一个有效元素。

    3:last:指向一个 buff 数组的最后一个位置的下一个位置,不是最后一个有效元素的下一个位置。

    4:node:指向 buff 在中控数组的位置。

    我们来看 deque 是如何通过迭代器遍历元素的:

    我们观察 deque 的源代码,发现在 deque 的实现是维护了两个迭代器的,start,finish。分别表示deque 中 front (deque 的第一个元素) 位置对应的迭代器,和 back (deque 的最后一个元素) 位置对应的迭代器。

    1:初始化 deque 的迭代器 it = begin(),begin() 就是 start 。

    2:it++,这里的加加实际上是让迭代器封装的四个指针指针中的 cur++。

    如果 it++ 之后不等于 last,那么直接加加即可。

    如果 it++ 之后等于 last,那么我们需要通过 node 找到当前的 buff 数组在中控数组中的位置,让 node++,指向下一个 buff 数组。同时修改 first 和 last 的值,让 cur 指向新的 first。

    3:与 end() 的比较,就是当前迭代器的 cur指针 和 deque 内部维护的迭代器 finish 的 cur指针 比较就行啦!

    按照上述过程就能实现迭代器遍历整个 deque 。

    4. deque 的接口使用 

    deque 的接口也是很简单哇!STL 库中的代码风格都差不多,经常使用的接口我在代码中已经贴出来了!

    1. #include
    2. #include
    3. using namespace std;
    4. int main()
    5. {
    6. deque<int> d;
    7. d.push_back(5);
    8. d.push_back(6);
    9. d.push_back(7);
    10. d.push_back(8);
    11. //输出:5 6 7 8
    12. for (auto e : d)
    13. cout << e << " ";
    14. cout << endl;
    15. d.push_front(4);
    16. d.push_front(3);
    17. d.push_front(2);
    18. d.push_front(1);
    19. //输出:1 2 3 4 5 6 7 8
    20. for (auto e : d)
    21. cout << e << " ";
    22. cout << endl;
    23. d.pop_front();
    24. d.pop_back();
    25. //输出:2 3 4 5 6 7
    26. for (int i = 0; i < d.size(); i++)
    27. cout << d[i] << " ";
    28. cout << endl;
    29. cout << d.size() << endl; //输出:6
    30. cout << d.front() << endl; //输出:2
    31. cout << d.back() << endl; //输出:7
    32. cout << d.empty() << endl; //输出:0
    33. return 0;
    34. }

    5. deque 和 vector,list 的比较

    deque 被发明出来,本来不仅想解决 vector 头插,头删,扩容拷贝数据带来的效率问题的,还想解决 list 频繁申请释放节点,cpu 高速缓存效率低的问题的!但发现 deque 谁都代替不了。

    与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不 需要搬移大量的元素,因此其效率是必vector高的。

    与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段

    但是,deque有致命缺陷不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构 时,大多数情况下优先考虑 vector 和 list。

    还有一个问题就是:deque 想要在中间插入删除,那可老麻烦了,正是因为 每个 buff 数组 的大小相同,才使得 operator[] 比较高效,因此,deque 的插入删除是不能改变 buff 数组的大小的。这不就理了个大谱了嘛。

    最后的问题就是:deque 的 operator[] 需要通过计算,在 频繁使用 operator[] 的时候,效率也会变低

    deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack 和 queue 的底层数据结构。

    好啦,我们模拟实现 stack 和 queue 的时候再见!

  • 相关阅读:
    Linux安装Docker | 使用国内镜像
    缓存行/伪共享问题,验证优化
    ​力扣解法汇总1752. 检查数组是否经排序和轮转得到
    如何创建书画家百度百科词条?书画家百度百科怎么做?
    信息系统项目管理师教程 第四版【1-共24章整体脑图整理】
    [山东科技大学OJ]1107 Problem A: 编写函数:Swap (I) (Append Code)
    hutool工具类常用API整理
    【北京迅为】RK3568开发板android11系统固件讲解
    C语言的三个经典题目:三步翻转法、杨氏矩阵、辗转相除法
    06-散列(Hash)基础分析
  • 原文地址:https://blog.csdn.net/m0_73096566/article/details/134076826