deque的介绍

前言 

        为什么会存在deque呢?在c++标准库中deque是作为 stack和queue的底层容器就是deque,我们要是了解过list和vector就会知道这两种容器各有优劣,vector的优点是支持随机访问,进而可以支持排序和二分查找等算法,它的缺点是如果空间不够增容的话要付出一定的代价(性能的损失),头插头删和中间位置的插入删除需要挪动数据,因此比较慢,存在空间的浪费。

        vector的优点是不需要扩容,没有空间的浪费,任意位置的删除和插入都很快(时间复杂度是O(1)) ,但是它也有很大的缺点,就是不支持随机访问(这个缺陷导致很多的算法都不能采用它)。

        deque就是vector和list的代替方案,deque支持随机访问,而且任意位置的插入和删除都很快(时间复杂度为O(1)),空间的浪费也很少,所以可以用一个容器deque来替代vector和list,那么大家肯定很好奇它的底层是怎么实现的,那么让我们一起来看看吧!

目录

1.deque的接口介绍

2.deque实现随机访问和任意位置插入删除的原理

3.deque的缺陷


1.deque的接口介绍

        从上面的接口中我们发现deque不仅支持头尾的删除和插入(时间复杂度都是O(1))还支持operator[],这就可以很好的支持了随机访问。 

我们可以来使用一下:

#include<deque>
void test()
{
	deque<int> dq;
	dq.push_back(1);//尾插
	dq.push_back(2);
	dq.push_back(3);
	dq.push_back(4); 
	dq.push_back(5);
	dq.push_back(6);
	dq.push_back(7);
	for (int i = 0; i < dq.size(); ++i)
	{
		cout << dq[i]<<" ";
	}
	cout << endl;
	//尾删
	dq.pop_back();
	dq.pop_back();
	dq.pop_back();
	dq.pop_back();
	//头插
	dq.push_front(10);
	dq.push_front(20);
	dq.push_front(30);
	dq.push_front(40);
	for (auto& e : dq)
		cout << e << " ";//支持范围for就支持迭代器
	cout << endl;
	//头删
	dq.pop_front();
	dq.pop_front();
}
int main()
{
	test();
	return 0;
}

2.deque实现随机访问和任意位置插入删除的原理

         为什么deque可以成为vector和list的替代方案呢?因为deque在一定程度上弥补了list不支持随机访问的缺点,也弥补了vector头插头删效率低和增容代价大的缺点,那么deque是通过怎么样来实现的那呢?

        其实deque是一种折中的实现方式,它是通过一段一段的连续空间buf来存放数据的,这些buf则通map的中控映射来联系到一起,它是一种折中的实现方式。如图:

        map实际上一个指针数组用来存放这个一段段空间的地址,如果map中存满了,开一段更大的map然后将map中存放的数组拷到新的空间中就好了。

         那么deque是如何实现头插头删的呢,这一段一段的buf是从map的中间开始的,如果头插就在重新开一段buf空间,将数据插入buf中,再将新的buf的地址存到map的前面就可以了,如图:

        deque随机访问与它的迭代器实现有关,如图:         

         如果想要实现遍历的话,首先它的迭代器cur从first开始,判断cur是否等于last,如果不等于last,cur就一直++向后走,当cur等于last,node++,找到下一个buf,让first指向buf的开始,last指向buf的结束,cur从buf的开始向后走,直到走完所有的buf,如果是随机访问的话,就要进行计算来找到数据在哪个buf中。

        deque地层上是假想的连续空间,实际上是分段连续的,为了维护他整体连续和随机访问,才有了这么复杂的结构。

3.deque的缺陷

        deque看似很完美,但是实际上是有很大的缺陷的,它的迭代器实现的很复杂,而且在遍历的时候要频繁检查是否移动到某段小空间的边界,导致效率下降,随机访问的效率也不够高。而在序列式场景下,可能需要经常遍历,因此在实际中,需要线性结构时会优先考虑vector和list。

排序的效率测试,数据越多效率会越低,例如:

#include<vector>
#include<deque>
#include<iostream>
#include<time.h>
#include<algorithm>
using namespace std;
int main()
{
	int i = 0;
	srand((unsigned int)time(NULL));//给随机数种子
	vector<int> v1;
	deque<int> d1;
	while (i < 1000000)//分别给vector和deque相同的十万个随机数
	{
		int k = rand();
		v1.push_back(k);
		d1.push_back(k);
		i++;
	}
	//对vector和deque采用相同的算法进行排序,并记录时间
	int v1begin = clock();
	sort(v1.begin(),v1.end());
	int v1end = clock();

	int d1begin = clock();
	sort(d1.begin(), d1.end());
	int d1end = clock();

	cout << "vector的排序时间:" << v1end - v1begin << endl;
	cout << "deque的排序时间:" << d1end - d1begin << endl;

	return 0;
}

        那么为什么会选择deque作为stack和queue的底层默认容器呢?

        因为stack和 queue不需要遍历(它们是没有迭代器的),只需要在固定的一端或者两端操作即可,stack中元素增长时,deque比vector效率高(扩容时不需要搬运大量数据),queue中的元素增长时,deque不仅效率高,而且内存使用率高(与vector相比)。它们结合了deque的优点避开了deque的缺点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值