目录
一.简述deque容器
二.deque的创建方式
三.deque容器的插入和删除操作
四.deque的底层形式以及扩容方式。
五.deque容器底层内存连续的实现方法
deque是一个双端队列容器,其在底层为一个双端队列,所需要的头文件为#include
正如上图所示,双端队列的每一个端口都既能出,又能进。但我们一般在使用双端队列时,使用的都是受限的双端队列,即我们选定双端队列的某一端只能进(插)元素,相反的,另一端就只能出元素。
deque的底层是双端队列,所以底层的内存空间是连续的(其实是不连续的,后续我们会讲到这一点),因此我们可以使用指针的直接跳转来访问队列中的任何元素,也就是说,后续我们可以使用 迭代器+偏移 的形式来访问deque容器中的数据。所以我们将deque中的迭代器称为随机访问迭代器。
#include
#include
int main()
{
std::deque dqu1;
std::deque dqu2(10);//count
std::deque dqu3(10, 20);
int arr[] = { 12, 3, 234, 3, 6 };
int len = sizeof(arr) / sizeof(arr[0]);
std::deque dqu4(arr, arr + len);
return 0;
}
在构造deque容器dqu1时,调用的是deque类中默认的构造函数,(所有的容器中都有默认的构造函数),且构造的同时没有做任何事情。
在构造deque容器dqu2时,我们显式的给定了这个容器的初始大小10,并且将这10个大小的数据置为0。
在构造deque容器dqu3时,我们显式的给定了这个容器的初始大小10,且显式的将这10个大小的数据置为20。即我们给dqu3中插入了10个值为20的数据。
在构造deque容器dqu4时,我们传入的参数是一个迭代器区间,即将dqu3的开始和末尾的后一个位置传入,也就是将dqu3中的数据插入到dqu4中。
#include
#include
#include
#include
template
void Show(Iterator first, Iterator last)
{
for (first; first != last; first++)
{
std::cout << *first << " ";
}
std::cout << std::endl;
}
int main()
{
std::deque dqu1;
for (int i = 0; i < 5; i++)
{
dqu1.push_front(i + 1);// 5 4 3 2 1
}
Show(dqu1.begin(), dqu1.end());
for (int i = 0; i < 5; i++)
{
dqu1.push_back(i + 1);
}
Show(dqu1.begin(), dqu1.end());// 5 4 3 2 1 1 2 3 4 5
dqu1.insert(dqu1.begin() + 3, 100);//随机访问迭代器
Show(dqu1.begin(), dqu1.end());
dqu1.pop_front();
dqu1.pop_back();
dqu1.erase(dqu1.begin() + 3);
Show(dqu1.begin(), dqu1.end());
return 0;
}
push_front即头插,在队头一端插入元素,时间复杂度为O(1)。
push_back即尾插,在队尾一端插入元素,时间复杂度为O(1)。
insert 即按位置插入元素,insert函数有以下五个重载,时间复杂度为O(n)
dqu1.insert(dqu1.begin(), { 1,3,4 });
iterator std::deque<_Ty, _Alloc>::insert<_Iter, >(const_iterator _Where, _Iter _First, _Iter _Last),按Where位置插入一个区间(Iter_First~Iter_Last)里面的元素,这个区间代表的是一个迭代器区间。
pop_front 即头删,删除队列头部的元素,时间复杂度为O(1)。
pop_back 即尾删,删除队列尾部的元素,时间复杂度为O(1)。
erase 即按位置删除元素,erase函数有以下两个重载,时间复杂度为O(n)
访问即Show函数的时间复杂度为O(1)。
deque容器的特点:“头部或尾部快速地插入或删除元素以及直接访问任何元素”。
注意:上述的所有Where都是用迭代器表示的。
1.deque的底层形式
deque的底层如下图
因为映射区的本质是一个指针数组,因此映射区内存放的都是地址(也可以理解为指向某一地址的指针),而这个地址指的就是数据区的地址。一开始,我们初始化一个deque容器时,映射区的大小默认为2个空间,也就是说这个指针数组只能存放两个指针。
如果我们要在deque容器中插入元素时,系统就会用malloc 或者 new在堆上申请一块大的内存空间,这块内存空间就是数据区,而这个数据区的大小也是固定的,它的大小是512字节。接着指针数组中0号下标位置会存放这块数据区的地址。
设计人员当初为了标明队列的队头和队尾,引进了两个指针。分别是队头指针 pfront 和队尾指针ptail,并且一开始队头指针和队尾指针都指向队列的中间位置。当我们要在队头插入数据时,队头指针就会向前移动一格,而要插入的数据就会存放在当前队头指针所指的位置。相反的,如果我们要在队尾插入数据时,队尾指针就会向后移动一格,而要插入的数据就会存放在当前队尾指针所指的位置。
2.deque的扩容方式
以push_front的方式插入数据(头部扩容)
假如现在我们头插的方式插入n多个值为1的元素,那么pfront指针就会一直前移,知道移动到数据区的首地址为止,这种情况下pfront就不能够继续前移了,如下图
如果现在我们还要以头插的方式插入数据,那么接下来就要进行扩容操作了,具体的扩容操作如下
以push_back的方式插入数据(尾部扩容)
现在我们就又可以进行尾插操作了。
其实deque容器的底层内存是不连续的,但是deque容器中的迭代器在设计当初对外屏蔽了底层内存的不连续,且对外提供了一个底层内存连续的特征。
例如,此时若有一个迭代器ptr指向如图所示位置,现在我们对迭代器进行加偏移的操作,如 ptr+2,那么迭代器ptr先会找到下一个映射区,接着通过映射区内存放着的数据区的地址,来找到下一个数据区,这样就不会发生越界的问题了,且我们也可以使用迭代器+偏移的方式来访问deque容器中的数据了。