目录
- 一、容器适配器
- 二、deque的介绍
- 三、deque的使用及缺陷
- 1、deque的构造函数
- 2、deque的元素访问接口
- 3、deque的 iterator的使用
- 4、deque的增删查改
- 4、deque的缺陷
- 5、为什么选择deque作为stack和queue的底层默认容器
一、容器适配器
在了解deque前,我们先讲一讲什么是适配器。
适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。 就像我们生活中常见的充电器一样。
在STL中,虽然stack、queue和priority_dueue中也可以存放元素,但是并没有将其划分在容器的行列中去,而是将其称为容器适配器,这就是因为stack、queue和priority_dueue中只是对其他容器的接口进行了包装。并且,在STL中stack和queue默认使用的是deque,priority_dueue默认使用的是vector。
二、deque的介绍
deque:即双端队列,是一种双开口的连续空间的数据结构。可以在头尾两端进行插入和删除操作。并且,时间复杂度为O(1),与vector比较,头插效率高,不需要移动元素;与list比较,空间利用率比较高。
但是,deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下:
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
那deque又是如何借助其迭代器维护其假想连续的结构呢?
三、deque的使用及缺陷
1、deque的构造函数
函数名称 | 功能说明 |
---|---|
deque() | 无参构造 |
deque(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
deque (InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
deque (const deque& x) | 拷贝构造 |
代码演示:
#include <iostream>
#include <deque>
using namespace std;
int main()
{
int i;
deque<int> d1;
deque<int> d2(4, 50);
deque<int> d3(d2.begin(), d2.end());
deque<int> d4(d3);
for (auto e : d4)
{
cout << e << " ";//结果:50 50 50 50
}
cout << endl;
int arr[] = { 16,2,77,29 };
deque<int> d5(arr, arr + sizeof(arr) / sizeof(int));
deque<int>::iterator it = d5.begin();
while (it != d5.end())
{
cout << *it << ' ';//结果:16 2 77 29
++it;
}
cout << endl;
return 0;
}
2、deque的元素访问接口
函数声明 | 说明 |
---|---|
size() | 获取数据个数 |
empty() | 判断是否为空 |
resize() | 更该容器大小 |
front() | 访问第一个元素 |
back() | 访问最后一个元素 |
3、deque的 iterator的使用
函数声明 | 说明 |
---|---|
begin() + end() | 获取第一个数据位置的iterator/const_iterator + 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin() + rend() | 获取最后一个数据位置的reverse_iterator + 获取第一个数据前一个位置的reverse_iterator |
与vector一样是一个随机访问迭代器,而list是一个双向迭代器
4、deque的增删查改
函数名称 | 功能说明 |
---|---|
push_back() | 在末尾添加元素 |
push_front() | 在开头插入元素 |
pop_back() | 删除最后一个元素 |
pop_front() | 删除第一个元素 |
insert() | 插入元素 |
erase() | 删除元素 |
swap() | 交换元素 |
clear() | 清空有效元素 |
operator[] | 访问元素 |
insert / erase 代码演示:
#include <iostream>
#include <deque>
#include <vector>
using namespace std;
void PrintList(const deque<int>& d)
{
for (deque<int>::const_iterator it = d.begin(); it != d.end(); ++it)
{
cout << *it << " "; }
cout << endl;
}
int main()
{
deque<int> d1;
for (int i = 1; i < 6; i++)
{
d1.push_back(i); // 1 2 3 4 5
}
deque<int>::iterator it = d1.begin();
++it;
it = d1.insert(it, 10);
PrintList(d1); // 结果:1 10 2 3 4 5
d1.insert(it, 2, 20);
PrintList(d1);// 结果:1 20 20 10 2 3 4 5
it = d1.begin() + 2;
vector<int> v(2, 30);
d1.insert(it, v.begin(), v.end());
PrintList(d1);// 结果:1 20 30 30 20 10 2 3 4 5
deque<int> d2;
for (int i = 1; i <= 10; i++)
{
d2.push_back(i); // 1 2 3 4 5 6 7 8 9 10
}
d2.erase(d2.begin() + 5);
PrintList(d2); //结果:1 2 3 4 5 7 8 9 10
d2.erase(d2.begin(), d2.begin() + 3);
PrintList(d2); //结果:4 5 7 8 9 10
return 0;
}
运行结果:
由上述接口中,可以看出deque是vector和list的结合体,但是实际使用中并不常见,而目前能看到的一个应用就是在STL中用其作为stack和queue的底层数据结构,为什么呢?
4、deque的缺陷
优势
- 与vector比较,deque头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比vector高的。
- 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
缺陷:
- deque并不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多。所以目前能看到的一个应用就是在STL中用其作为stack和queue的底层数据结构。
5、为什么选择deque作为stack和queue的底层默认容器
- stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以
- queue是先进先出的特殊线性数据结构,只要具有push_back()和pop_front()操作的线性结构,都可以作为queue的底层容器,比如list。
在STL中stack和queue默认选择deque作为其底层容器,主要原因是:
- stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
- 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
综上所述,stack和queue结合了deque的优点,而完美避开了其缺陷。