deque(双端队列):是一种双开口的“连续”空间的数据结构。
双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不必搬动数据;与list相比,空间利用比较高。
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图:
deque采用一块所谓的map(注意:不是STL的map容器)作为主控,这里的map是一小块连续空间,其中每个元素都是一个指针,指向另一段(较大的)连续线性空间,称为缓冲区,缓冲区才是deque的存储空间主体。SGI STL允许我们指定缓冲区大小,默认值0表示将使用用512bytes缓冲区。
template<class T,class Alloc=alloc,size_t BufSize = 0>
class deque{
public:
iterator begin();
iterator end();
typedef T value_type;
typedef value_type* pointer;
...
protected:
typedef pointer* map_pointer;// T**
protected:
map_pointer map; //指向map, map是块连续空间,其内的每个元素都是一个指针,指向一个缓冲区
size_type map_size; //map内可容纳多少指针
...
}
我们可以发现,map其实是一个T **,也就是说它是一个指针,指向一个型别为T的一块空间。
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此迭代器的设计就比较复杂,如下图所示:
template<class T,class Ref,class Ptr,size_t BufSiz>
struct __deque_iterator{
typedef T** map_pointer;
T* cur; //此迭代器所指之缓冲区中的现行元素
T* first; //此迭代器所指之缓冲区的头
T* last; //此迭代器所指之缓冲区的尾
map_pointer node; //指向管控中心,即中控器
}
deque迭代器的"++"、"- -"操作是远比vector迭代器繁琐,其主要工作在于缓冲区边界,如何从当前缓冲区跳到另一个缓冲区。deque内部在插入元素的时候,如果map中的node数量全部使用完时,且node指向的缓冲区也没有空间,这时会配置新的map(当前的2倍+2的数量)来容纳更多的node,也就是可以指向更多的缓冲区,在deque删除元素时,也提供了元素的析构和空闲缓冲区空间的释放等机制。
与vector比较,deque的优势是:头部插入和删除时,不需要搬动数据,效率特别高,而且在扩容时,也不需要搬移大量的数据,因此效率比vector高。
与list相比,其底层是"连续"的,空间利用率较高,不需要存储额外的字段。
deque有一个致命的缺陷就是不适合遍历,因为在遍历的时候需要频繁的去检查是否需要移动到某段小空间的边界,进而需要跳转缓冲区,因此导致效率低下。而序列式场景中经常用到,所以在实际情况中,需要线性结构时我们会优先考虑vector和list。在STL中主要应用于stack和queue的默认适配器。
适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列中,而是将其称为
容器适配器
,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。
template <class T, class Container = deque<T> >
class stack;
template <class T, class Container = deque<T> >
class queue;
#include
namespace MySTL{
template<class T,class Sequence=deque<T> >
class stack{
public:
bool empty()const {
return c.empty();}
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();}
private:
Sequence c;
};
template<class T,class Sequence=deque<T> >
class queue{
public:
bool empty()const {
return c.empty();}
T& front(){
return c.front();}
const T& front()const {
return c.front();}
T& back(){
return c.back();}
const T& back()const{
return c.back();}
void push(const T& val){
c.push_back(val);}
void pop(){
c.pop_front();}
size_t size()const {
return c.size();}
private:
Sequence c;
};
}
1、stack所有元素的进出都必须符合"先进后出"的条件,
只有stack顶端的元素,才有机会被外界取用。stack不提供走访功能,也不提供迭代器。
2、queue所有元素的进出都必须符合"先进先出"的条件,
只有queue顶端的元素,才有机会被外界取用。queue不提供走访功能,也不提供迭代器。
除了deque之外,list也是双向开口的数据结构。上述stack源代码中使用的底层容器的函数empty,size,back,push_back,list都具备,所以可以用list为底部结构并封闭其头端开口,一样能够轻易形成stack。
#include
#include
using namespace std;
int main() {
stack<int, list<int>> istack;
istack.push(1);
istack.push(3);
istack.push(5);
istack.push(7);
cout << istack.size() << endl; // 4
cout << istack.top() << endl; // 7
istack.pop();
cout << istack.top() << endl; // 5
istack.pop();
cout << istack.top() << endl; // 3
istack.pop();
cout << istack.top() << endl; // 1
cout << istack.size() << endl; // 1
return 0;
}
除了deque之外,list也是双向开口的数据结构。上述queue源代码中使用的底层容器的函数empty,size,back,push_back,list都具备,所以可以用list为底部结构并封闭其头端开口,一样能够轻易形成queue。
#include
#include
using namespace std;
int main() {
queue<int, list<int>> iqueue;
iqueue.push(1);
iqueue.push(3);
iqueue.push(5);
iqueue.push(7);
cout << iqueue.size() << endl; // 4
cout << iqueue.front() << endl; // 1
iqueue.pop();
cout << iqueue.front() << endl; // 3
iqueue.pop();
cout << iqueue.front() << endl; // 5
iqueue.pop();
cout << iqueue.front() << endl; // 7
cout << iqueue.size() << endl; // 1
return 0;
}