详解STL中stack_queue为什么选择deque作为默认容器

目录

C语言中的栈和队列

vector和list的区别

deque的底层实现逻辑

1.集vector和list之长

2.双端操作优于vector和list

deque简直是stack_queue的天选容器

为何deque无法完美取代vector和list

相比于vector

1.内存连续性:

2.扩容代价:

3.内存碎片:

相比于list

1.中间操作效率:

2.迭代器稳定性:

不存在"完美容器",只有"场景适配"


C语言中的栈和队列

当我们对初阶数据结构有所了解之后,就知道栈的底层通常是用顺序表实现,也就是C++的vector,而队列则通常用链表实现,也就是C++的list,详细可以看看博主栈和队列的篇章链接

https://blog.csdn.net/cyzzzuzm/article/details/147747706?fromshare=blogdetail&sharetype=blogdetail&sharerId=147747706&sharerefer=PC&sharesource=cyzzzuzm&sharefrom=from_link

那为什么C++的STL中的栈和队列会选择deque作为默认容器,它相对于顺序表链表的优点又在哪里,它可以完全取代顺序表链表吗?接下来重点讲解这些。

vector和list的区别

vector优点:

1.下标随机访问快,尾插尾删效率高

2.CPU高速缓存命中率高*

vector缺点:

1.头部或中间插入删除效率低下

2.插入时空间要不断扩容,扩容有一定性能损耗*,倍数级扩容可能存在一定的空间浪费

list优点:

1.任意位置的插入删除效率都是O(1)

2.按需申请释放内存,不会存在空间大量浪费的问题

list缺点:

1.不支持下标随机访问

2.CPU高速缓存命中率低*

由此可见,vector和list其实是两个极端,也是功能的极致,一方的有点是另一方致命的缺点,那有没有什么容器能够集两者之长,优缺点折中呢?那就是我们接下来要说的deque容器。

deque的底层实现逻辑

1.集vector和list之长

deque本质上来讲是一个指针数组,数组里面的每一个指针都指向一个buffer数组的位置,也就是说,数组里面连续存放着多个分散在各个空间的buffer数组的地址。它拥有vector的优点吗?当然有,首先是下标访问效率 ,虽然它的底层多个buffer数组的分布是散乱的,但是遍历完第一个buffer数组之后,指针数组的访问指针就会移动到下一个位置,精准找到第二个buffer数组,然后继续遍历,实现无缝衔接遍历,相当于指针数组将多个buffer数组串联在一起成为一个超级长的数组,大多数情况下,deque的下标访问效率与vector基本一致 。前面提到的,它其实就是相当于被拼接起来的一个巨长无比的连续的数组空间,因此CPU高速缓存命中率也很高 。但是它的中间插入删除的效率并不高 ,通常是O(n)级的,这一点和vector类似。list的优点它也并非没有,按需申请释放内存,不会存在空间大量浪费的问题,这一优点类似list,当buffer数组满了之后,会在申请一个内存块,新的指针会挂在指针数组里面,每次申请的内存块大小都是固定的,因此不会有太大的浪费。

2.双端操作优于vector和list

双端操作,也就是头尾的插入和删除,deque的指针数组会在两端预留位置,要双端删除直接删掉双端的buffer数组中的数据即可,效率为O(1) ,要插入数据,也是插入在双端指向的buffer数组中,如果一个buffer数组满了,比如头部插入,会在开辟一个内存块,它的指针继续存放在指针数组的前端,然后把要插入数据放在新开辟的内存块里,完全不需要移动数据**,效率同样O(1)** ,零数据移动,完美降维打击vector ,这是优于vector的点,而list还得申请结点(带两个指针的额外内存),内存碎片多,开销更大,而deque一次可以分配多个元素,做到"批量分配",内存利用率更高 ,这是优于list的点。最坏的情况,哪怕指针数组也满了,此时可能就要异地扩容,但最多也就是把指针再拷贝到新数组里而已,而且指针占用空间极小,拷贝成本几乎可以忽略,效率极高。综上,双端操作上deque是更优于vector和list的。

deque简直是stack_queue的天选容器

对初阶数据结构有了一定了解之后,我们都知道栈的出入数据是后进先出的(LIFO),从栈顶入数据,同样也从栈顶出数据;而队列,出入数据的原则是先进先出(FIFO),从一头进再从另一头出。我们可以发现,栈和队列无论是出数据还是入数据都是从两端操作的,那么双端操作,我们是不是拥有了可以完美替代vector和list的容器?那就是deque双端队列,因此用它作为栈和队列的默认容器简直再合适不过了

为何deque无法完美取代vector和list

首先我们知道deque兼顾两者的优点,虽全能,却也全不精,没有二者的优点那么极致

相比于vector

1.内存连续性:

vector的元素在内存中是完全连续的,而deque是由多个分散的"内存块"组成,这会造成哪些劣势呢,首先vector的随机访问是真正的O(1),直接通过首地址+偏移量计算,deque虽然也支持随机访问,但是底层实现上需要先计算元素在哪个内存块,再找块内的偏移量,实际效率略低(尤其是数据量巨大时),且vector的CPU缓存命中率更高(连续内存适合CPU预读),遍历速度通常比deque快。

2.扩容代价:

vector虽然扩容时需要整体迁移数据,代价略高,但是一旦扩容完成,后续尾插的效率极高,deque虽然不需要整体迁移数据,但是每次新增内存块时,需要维护指针数组(比如需要扩容指针数组),且小块内存的管理成本更高。

3.内存碎片:

deque有多个内存块,会导致产生更多的内存碎片,而vector是一大块连续的内存,利用率更高。

相比于list

1.中间操作效率:

list的中间插入删除操作只需要改变前后结点的指针,效率很高,纯O(1)操作,而deque的插入删除操作需要移动插入点之后的所有数据,类似于vector,时间复杂度是O(n),而且由于deque内存块分散,移动数据时需要跨越多个内存块,效率甚至比vector更低。

2.迭代器稳定性:

list的迭代器在插入删除操作后,除了被删除的迭代器,始终有效;而deque在头尾插入删除时,迭代器可能会失效(如触发指针数组扩容),中间操作则会导致大量迭代器失效,使用更麻烦。

不存在"完美容器",只有"场景适配"

vector:适合频繁随机访问、尾插尾删为主的场景,如储存大量数据并遍历。

list:适合频繁中间插入删除的场景。

deque:适合双端频繁操作,但很少中间修改的场景,如实现栈和队列的底层容器。

相关推荐
charlie1145141913 小时前
精读C++20设计模式——结构型设计模式:代理模式
c++·学习·设计模式·代理模式·c++20·概论
序属秋秋秋5 小时前
《C++进阶之C++11》【可变参数模板 + emplace接口 + 新的类功能】
c++·笔记·学习·c++11·可变参数模板·emplace系列接口
Pocker_Spades_A5 小时前
C++程序设计上机作业(1)
开发语言·c++
Chen--Xing5 小时前
OpenMP并行化编程指南
c++·密码学·openmp
乱飞的秋天6 小时前
C++中的特殊成员函数
开发语言·c++
攻城狮7号6 小时前
【AI时代速通QT】第八节:Visual Studio与Qt-从项目迁移到多版本管理
c++·qt·跨平台·visual studio·qt vs tools
郝学胜-神的一滴6 小时前
QAxios研发笔记(一):在Qt环境下,构建Promise风格的Get请求接口
开发语言·c++·spring boot·qt·ajax·前端框架·软件工程
AA陈超6 小时前
虚幻引擎UE5专用服务器游戏开发-21 连招技能动画蒙太奇播放
c++·游戏·ue5·游戏引擎·虚幻
无敌最俊朗@12 小时前
C++ 序列容器深度解析:vector、deque 与 list
开发语言·数据结构·数据库·c++·qt·list