我想要vector的随机访问能力,又想要list的插入性能那该怎么办?俗语有云,鱼和熊掌不可兼得。可是我偏不,我命由我不由天...
既然这样那可能deque就是你想要的菜???
老套路,先来两个关于deque的权威参考:cppreference
又或者这个也行cplusplus
deque实现原理
deque的全称是Double ended queue
又叫双端队列。和vector一样,是带索引的序列容器,它允许在它的首尾两端快速插入及删除,而插入删除慢,这正是vector的诟病所在...
deque(双端队列):是一种双开口的" 连续 "空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高,不太容易造成内存碎片。
虽然deque像vector支持随机访问,但是deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的。这些一段一段的小空间,都用一个指针数组保存起来,这个数组又叫中控数组。 实际上当对deque进行插入删除时,其内部操作的都是中控数组的内部指针,deque的内部结构大概如下图所示:
例如当对deque进行头插入数据时,当头部空间不足时就在中控数组中增加一个指针指向一块新的空间,这样在新的空间中插入输入,这样就不需要挪动数据了。
同理当需要尾插数据时,如果尾部空间不足就在中控数组中增加一个指针指向一块新的空间,在新的空间中插入数据这样就不需要因为空间不够扩容时拷贝数据了。
deque基本操作
先看看怎么构造一个deque,其实和前面介绍的vector或者list差不多:
c
int main() {
// 空
std::deque<int> que_01;
// 构造list中包含10个值为3的元素
std::deque<int> que_02(10,3);
std::deque<int> que_03(que_02.begin() + 1,que_02.end());
for (auto value: que_03) {
std::cout << "que_03--value:" << value << std::endl;
}
return 0;
}
对于deque的访问,和vector一样,通过下标访问符号[]
进行访问了。还可以通过函数at
进行访问,但是使用at时需要特别注意检查是否越界。
c
int main() {
// 构造list中包含10个值为3的元素
std::deque<int> que_02(10,3);
std::deque<int> que_03(que_02.begin() + 1,que_02.end());
// at越界会抛出异常
std::cout << "at(5):" << que_03.at(5) << std::endl;
que_03.at(5) = 10;
std::cout << "[5] :" << que_03[5] << std::endl;
// 访问首元素
que_03.front();
// 访问尾元素
que_03.back();
return 0;
}
对于deque元素的插入与删除,deque也大部分兼容了vector和list的插入与删除相关的函数:
c
int main() {
// 构造list中包含10个值为3的元素
std::deque<int> que;
// 头部插入
que.push_front(1);
// 尾部插入
que.emplace_back(2);
que.push_back(3);
que.push_back(4);
// 弹出头部
que.pop_front();
// 弹出尾部
que.pop_back();
// 删除第二个元素
que.erase(que.begin() + 1);
for (const auto value:que) {
std::cout << "value:" << value << std::endl;
}
return 0;
}
在正确的情况下,上述的实例代码应该输出:value:2
总的来说,因为deque设计的目的就是为了集成vector和list的优点,因此大部分vector和list有的函数,deque也会有。当然,也有不包含的,例如deque就不包含vector的capacity
函数, 也不包含list的remove
等。
这么牛为何没有没有普及???
既然deque这么优秀那我是不是无脑用deque就行了???
其实性能是否优越都是相对的。
虽然deque确实结合了vector和list的优点并且弱化减小了它们的缺点,但是它的结合也让它自己的优点没有原始的vector和list那么极致,导致deque变得很中庸, 所以deque的应用场景也并没有那么多,它经常被用来作为stack和queue的底层容器。
deque双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其"整体连续"以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂。 因此在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下。这可能也是影响deque没有取代vector的一个重要原因。
deque的优缺点总结
按照惯例,要批评一个人之前,总是要先表扬一番的,所以我们先来看看它的优点:
与vector比较,deque的优势是:
- 头部插入和删除时,不需要搬移元素,效率特别高。
- 在扩容时,也不需要搬移大量的元素,只需要搬移中控数组中的指针就行了。
与list比较,deque的优势是:
- 其底层是连续空间,空间利用率比较高,不需要存储额外字段。
- 支持随机访问
下面再来看看deque相对与vector和list不足的地方。
-
中间的插入删除元素并不是O(1),而是O(n)。
-
单项功能对比的话优点没有vector和list极致,例如随机访问的速度没有vector快,(vector是真正的连续空间,在计算机硬件中缓存利用率极高,在排序方面这点很重要!!!), 中间位置的插入与删除没有list快(list的中间位置的插入与删除是O(1)),deque需要进行扩容,而list根本不需要扩容。
-
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下。 而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。
关注我,后期不定期更新...