目录
一、Deque的引入
二、Deque是什么?
三、deque的遍历方式?deque的缺陷?
四、它为什么能更贴合与stack与queue?
五、STL中vector与list的底层实现
一、Deque的引入
Stack、Queue在之前的博客中我也是分别使用了更容易处理的vector和list来实现。
栈是一种先进后出,只能基于一端(栈顶元素)来进行元素访问、修改即各项操作,栈的各项操作使用vector中的一部分接口可以完整的封装起来,将vector中更倾向于stack的一些接口以栈的接口形式给用户呈现出来。例如push、pop均可以使用vector中的push_back、pop_back来进行封装。但是vector的缺陷也比较严重,就是如果插入、删除元素的时候就要进行大量的搬移元素操作。
队列是一种先进先出的数据结构,只能从尾部(队尾)进行插入元素,访问、检索、删除只能从头部(队头)来进行操作,而list链表结构头删尾插功能更加贴合了这一特性。然而list的离散式存储很容易造成内存碎片,空间利用率比较低。
上方的栈和队列都是基于一种容器来进行封装实现,将更贴合于用户需求的接口呈现出来,我们把这种容器成为容器适配器。
而在STL中的栈和队列却是基于一种叫做Deque的容器来进行封装。Deque究竟具有什么功能结构?Deque的实现原理是什么?它为什么能更贴合与stack与queue?
二、Deque是什么?
deque(双端队列)是一种双开口的“连续”的数据结构,可以在其头尾两端进行插入与删除操作,并且时间复杂度都为O(1),它相比于vector不用进行大量的元素搬移,相比于list空间利用率更高。
deque并不是真正连续的一段空间,而是由一段段连续的小空间拼接而成的。
基本原理:新建一块数组,将数组的地址(0x234)存入map中控器中,在数组中尾插的时候就在该块数组中进行尾插,当将这块数组空间存储满了之后,就开辟一块新的数组空间,并将该数组的地址(0x456)放入之前数组的下一个位置,然后在进行尾插。
当头插时,创建一块新数组,并把新数组的地址(0x123)放入map中0x234的上一块位置。然后在新数组的尾部进行插入。
当map满载之后,将map进行扩容,开辟新空间,拷贝元素,使用新空间,这里的map中存放都是一个个的地址,数组拷贝起来也比较容易。
我的一个小疑问,为什么不使用链表来存储这些数组的地址而是用数组来进行存储呢?
首先,存放的数据是一片片的地址,扩容拷贝时也比较容易,然而使用链表来进行存放地址,链表是使用一块块小的结点来进行存储,这无疑降低了内存的使用率
三、deque的遍历方式?deque的缺陷?
void test()
{
deque<int> q = {1,2,3,4,5,6,7};
auto it = q.begin();
while(it != q.end())
cout<<*it<<" ";
cout<<endl;
}
诸如vector、list都能实现下方类似的代码进行遍历访问,那么deque如何进行迭代器访问,上面的deque原理中一块块数组的地址通过中控器map来进行存储,然而在deque的内部迭代器上有着这样的结构:
iterator中有这cur、first、last、node这几个指针,前三个指针就类似于组成一个vector结构,而node结点就描述了该块数组在map中的存储位置情况。
对于一整块deque的遍历来说,就是从第一个存储空间的第一个frist开始,直到最后一个存储空间的last位置,中途要进行不断的判断,deque的迭代器要不断的进行检测其是否移动到了某段空间的末尾位置,导致效率低下,这也正是deque的致命缺陷
四、它为什么能更贴合与stack与queue?
stack为先进后出结构,所有具有push_back和pop_back的容器都可以作为底层默认容器,比如list,vector。
queue为先进先出结构,所有具有push_back和pop_front的容器都可以作为底层默认容器,比如list。
然而STL库中使用deque作为底层模板容器的原因:
1、deque所有的头删头插尾删尾插的时间复杂度都为O(1)非常的高效,但是致命缺陷就是进行遍历,然而stack与queue根本不需要对元素进行遍历,使用deque作为底层模板容器简直就是取其精华去其糟粕的典型案例。
2、使用vector作为stack的底层模板虽然可以,但是效率低下(插入删除需要对元素进行大量搬移操作),使用deque相比于list作为queue的底层模板更能提高空间利用率。
五、STL中vector与list的底层实现
#include<deque>
namespace bite {
template<class T, class Con = deque<T>>
//template<class T, class Con = vector<T>>
//template<class T, class Con = list<T>>
class stack {
public:
stack() {}
void push(const T& x) {
_c.push_back(x);
}
void pop() {
_c.pop_back();
}
T& top() {
return _c.back();
}
const T& top()const {
return _c.back();
}
size_t size()const {
return _c.size();
}
bool empty()const {
return _c.empty();
}
private:
Con _c;
};
}
#include<deque>
#include <list>
namespace bite {
template<class T, class Con = deque<T>>
//template<class T, class Con = list<T>>
class queue {
public:
queue() {}
void push(const T& x) {
_c.push_back(x);
}
void pop() {
_c.pop_front();
}
T& back() {
return _c.back();
}
const T& back()const {
return _c.back();
}
T& front() {
return _c.front();
}
const T& front()const {
return _c.front();
}
size_t size()const {
return _c.size();
}
bool empty()const {
return _c.empty();
}
private:
Con _c;
};
}