deque介绍

目录

简介:

初识deque

deque的底层实现

deque插入

deque的operator[]

deque的迭代器

deque的缺陷 

与vector比的缺陷

与list相比的缺陷

deque的优势


简介:

这一节不会进行模拟实现,只会聊聊deque的底层

原因是我们学习deque是为了后面的适配器打基础,deque本身并不重要 

初识deque

deque是一个双端队列,它是基于vector和list的优点为一身(但又不突出)

deque对头部数据进行处理时,时间复杂度会到达O(1)   弥补了vector的缺点

deque支持随机访问,也就是它支持了[]的重载,弥补了list的缺点

既然弥补了vector的缺点和list的缺点,那么为什么我们平时使用的时候好像没有使用过这个容器

不急,我们先看看deque的底层实现

deque的底层实现

deque插入

首先deque在实现时先有一段缓冲区(buffer)来存储数据

 可以看到,图上的缓冲区满了

deque的尾插规则就是

如果缓冲区没满,那么直接在缓冲区后面插入

如果缓冲区满了,那么就重新申请一个buffer并插入数据

 我们看到,buffer可能是存有多个的,既然有多个我们就需要对buffer进行管理

deque中就有了中控器

中控器是用来存储buffer指针的指针数组,我们直接用二级指针(因为中控也是要增容的)表示即可

deque头插的规则:

deque在进行头插的时候也是要重开一个buffer的

跟尾插不同的是,重新开的buffer进行头插时是从buffer的尾部开始插入的

例如,我们在上面图的基础上,先头插入一个0,再头插入一个-1

 以上就是deque的头插和尾插的原理,接下来我们看看它的随机访问是如何实现的

deque的operator[]

T& operator[](size_t n);

首先先判断n是否大于第一个buffer的数据

如果小于第一个buffer的数据,那么返回的就是第一个buffer对应n的位置的数据

如果大于第一个buffer的数据,计算规则如下

(n-第一个buffer的数据多少)/每个buffer的大小 = 对应数据所在的第几个buffer

(n-第一个buffer的数据多少)%每个buffer的大小 = 数据在buffer中对应的下标

注意:buffer对应的大小应该从0开始

如上图,我们计算一下[8]所对应的数据

(8-2)/8 =0

(计算出来的是第一个buffer之后第0个buffer,这个是从0开始的)

(8-2)%8 = 6

 (计算出来的6是对应buffer[6]所对应的数据)

最终,也就计算出了对应的数据在buffer1下标为6的位置也就是7

deque的迭代器

如图:

(节选自STL源码剖析) 

可以看到,deque的迭代器中有四个成员:cur、first、last、node,那他们对应的功能是什么呢?

cur:指向的数据

first:指向的是这段buffer所在的开始位置

last:指向的是这段buffer所在的结束位置

node:指向的是这段buffer在中控所对应的下标

可以看出,deque的迭代器其实是蛮复杂的

当我们进行++时,迭代器会进行判断,cur是否等于last

如果cur等于last,就让node指向下一个buffer,并把cur指向buffer的first

如果cur不等于last,就让cur指向下一个下标所对应的数据即可

deque的缺陷 

 既然我们在实际当中不经常使用deque,那么他肯定是有所缺陷的

正如它的优点是与vector和list相比,那么它的缺点肯定也是跟这两个容器相比

所谓成也萧何,败也萧何

与vector比的缺陷

首先我们比较一下它的随机访问和vector的随机访问

deque的随机访问是要通过计算的

vector的随机访问是通过下标直接找到,不需要计算

那么deque如果进行大量的随机访问,效率就会比vector慢很多很多 

我们写一个代码测试一下

#include<iostream>
#include<deque>
#include<vector>
#include<ctime>
using namespace std;
#define NUM 10000000 //10000000次随机访问


void TestDequeVector()
{
	deque<int> d;
	vector<int> v;
	srand((long long)time(nullptr));
	for (int i = 0; i < 10000; ++i)
	{
		int x = rand();
		d.push_back(x);
		v.push_back(x);
	}
	int begin1 = clock();
	for (int i = 0; i < NUM; ++i)
	{
		d[i % 10000]++;
	}
	int end1 = clock();
	cout << "deque:" << end1 - begin1 << endl;
	int begin2 = clock();
	for (int i = 0; i < NUM; ++i)
	{
		v[i % 10000]++;
	}
	int end2 = clock();
	cout << "vector:" << end2 - begin2 << endl;
}

int main()
{
	TestDequeVector();
	return 0;
}

与list相比的缺陷

虽然说deque的头部插入和list的头部插入都达到了O(1)

但他们两的区别在于中间插入

deque的中间插入是需要挪动数据的(因为如果扩容了小buffer,那么下标就无法进行访问了)

list的中间插入一如既往的稳定,时间复杂度还是O(1)

#include<iostream>
#include<list>
#include<deque>
#include<ctime>
using namespace std;
#define NUM 10000 //插入的数据
void TestDequeList()
{
	list<int> l;
	deque<int> d;
	//先插入100000个数据,不然不好统计中间插入
	for (size_t i = 0; i < 100000; ++i)
	{
		l.push_back(i);
		d.push_back(i);
	}
	size_t mid = 100000 / 2;
	list<int>::iterator lit = l.begin();
	deque<int>::iterator dit = d.begin();
	while (mid)
	{
		//找到中间数据
		++lit;
		++dit;
		mid--;
	}
	//从这里开始记录clock
	int begin1 = clock();
	for (size_t i = 0; i < NUM; ++i)
	{
		lit = l.insert(lit, 1);
	}
	int end1 = clock();
	cout << "list insert : " << end1 - begin1 << endl;

	int begin2 = clock();
	for (size_t i = 0; i < NUM; ++i)
	{
		dit = d.insert(dit, 1);
	}
	int end2 = clock();
	cout << "deque insert : " << end2 - begin2 << endl;
}

int main()
{
	TestDequeList();
	return 0;
}

 

deque的优势

1、空间利用率比起list较高

2、 与vector相比支持头插和尾插,且都达到了O(1)时间复杂度(作为适配器的主要条件)

3、与list相比无需频繁申请空间

4、与vector相比扩容代价小(空间满了以后只需扩容中控,中控一次扩容可以存放很多数据)

5、与vector相比空间浪费少

那么这期deque介绍就到这里了,感谢大家的支持 

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值