deque介绍
双端队列 (deque)是一个容量可以动态变化,并且可以在两端插入或删除的序列式容器。
deque逻辑模型:
deque迭代器:对结构体进行封装(主要含有四个指针成员属性cur,first,last,node)
想要使用deque需要引入头文件
#include<deque>
想要使用deque的一些泛型算法需要引入头文件
#include<algorithm>
deque的底层实现
deque底层是一个动态开辟的二维数组
deque没有容量大小这一说,deque表面上的连续是假象,底层是靠deque的迭代器将分段连续的空间串联起来的。其中deque的底层实现是靠下面的三部分实现的:
中控器(map)与缓冲区
(注意:这里的map不是STL中的关联式容器map)
因为deque的本质是由多个分段的连续空间构成,因此为了维持表面上连续的假象,需要一个中控器将所有的连续空间的地址组合保存起来,这样才能造成连续的假象,而中控器map其实是一个T**的指针,也就是一个二级指针
中控器map只是一小段的连续空间,而空间中存储节点指向的更大的连续空间称为缓冲区,缓冲区才是deque的主存储体
deque的底层模型
deque迭代器
deque为了避免像vector一样 申请空间,拷贝数据,释放原有空间的繁琐操作,和为了将分段的连续空间组合起来,能够进行++找到下一个元素的位置等,因此底层由复杂的迭代器来实现
那么这样就将中控器,迭代器,缓冲区联系起来,即实现了空间连续的假象,也为deque的相关操作提供了可实行的方法。
例如:当我们设置一个缓冲区的大小时32个byte,需要存储20个int型的元素时,一个缓冲区可以存储8个元素,因此需要3个缓冲区来存储,也需要三个节点来将这三个空间链家起来,其中的迭代器如下图所示:
deque使用
初始化deque
deque<int> deq | 初始化一个装有int类数据的容器,size为0 |
deque<int> deq(10) | 初始化一个装有int类数据的容器,size为10,每个数据的默认值为0 |
deque<int> deq(5,9) | 初始化一个装有int类数据的容器,size为5,每个数据初始化为9 |
deque<int> deq deque<int> deq2(deq) | 先初始化一个装有int类数据容器deq 再使用容器deq给装有int类数据容器deq2初始化 |
deque<int> deq(5,2) deq2(deq.begin(),deque.begin()+3) | 先初始化一个装有int类数据容器deq,size为5,每个数据初始化为2 再用容器deq的0-2下标数据(共3个)给装有int类数据容器deq2初始化,size为3 |
deque的一些内置函数
deque<int> deq;
deq.front(); //返回容器第一个节点的值
deq.back(); //返回容器最后一个节点的值
deq.begin(); //返回指向容器第一个节点的迭代器
deq.end(); //返回指向容器最后一个节点下一个位置(头节点)的迭代器
deq.clear(); //清空容器的值(size=0)
deq.empty(); //判空,如果容器为空则返回true,不为空返回false
deq.size(); //返回当前节点个数
deq.resize(size_t newsize,T c = T()); //重置deque的大小(含元素个数),多出来的空间用c填充
deq[n]; //返回deque上n位置上的元素
//使用less和greater需要引入头文件
#include<functional>
deq.sort(less<int>()) //将容器从小到大排序
deq.sort(greater<int>()) //将容器从大到小排序
deque迭代器iterator的使用
deque<int> deq(5,2) deque<int>::iterator it = deq.begin() | 初始化一个装有整形数据的容器deq,size=5,每个数据初始化为2 初始化指向int类数据的迭代器it,并指向deq的第一个节点 |
it.begin() | 返回指向第一个节点的迭代器 |
it.end() | 返回指向最后一个节点下一个节点的迭代器 |
it.rbegin() | 返回指向最后一个节点前一个节点的迭代器 |
it.rend() | 返回指向第一个节点前一个节点的迭代器 |
deque增删查改
增:
void push_front( const T val ):头插
void push_back( const T val ):尾插
iterator insert( iterator pos,const T val ):在pos位置之前插入数据
pos:指向要插入的位置的迭代器
val:要插入的值
返回值:指向新插入元素的迭代器
删:
void pop_front():头删
void pop_back():尾删
iterator erase( iterator pos ):删除pos位置的元素
pos:指向删除位置的迭代器
返回值:删除元素下一个元素的迭代器
查:
iterator find( iterator begin,iterator end,const T val ):在begin到end范围区间内查找值为val的元素,并返回指向此元素的迭代器
begin:查找范围的起始位置的迭代器
end:查找范围末尾的迭代器
val:查找的值
返回值:如果有此值-》返回指向此值的迭代器
如果没有-》返回容器的end()
改:
直接使用下标访问去修改容器中的值
deque的迭代器失效问题
插入
头插与尾插:
当中控器map还有空间时:头插与尾插不会导致迭代器失效
当中控器map已经满的时候:头插与尾插会导致重新分配空间(申请空间,拷贝数据,释放旧空间),此时会导致容器所有迭代器失效
其他部分插入
使用insert在其他地方插入时,会导致元素的挪移,因此会导致迭代器失效
删除
头删与尾删
头删和尾删只会导致被删除节点的迭代器失效
其他部分删除
使用erase去删除其他元素,会导致元素挪移,因此会导致迭代器失效
如何避免迭代器失效:
在使用insert和erase时,用迭代器去接收保存它们的返回值
deque的优缺点
优点:
可以随机访问元素[]
头部尾部的操作时间复杂度为O(1)
缺点:
遍历容器效率非常低
deque的使用场景
当我们只关心头部和尾部的操作,而不去遍历以及对中间数据的操作时,我们采用deque