双端队列简介
顾名思义,deque支持在头尾进行插入删除操作。类似于vector和array,deque也是顺序存储结构,没有了解过deque的人在主观上可能会以为deque与vector底层结构类似,实际上为了支持头部插入与删除操作,deque采用分段连续存储的数据结构,因此实现上比vector复杂得多。因为采用分段设计,deque的迭代器效率比vector低。
为了管理片段,STL的deque在内部定义了一个_Map成员,_Map底层实际是一个T**,也可以将_Map看作一个数组,每一个数组元素指向内存中的一段连续的元素存储空间
1 2 3 4 5 6 7 8 9
| template<class _Ty> struct _Deque_simple_types : public _Simple_types<_Ty> { typedef _Ty **_Mapptr; };
_Mapptr _Map;
|
双端队列的迭代器
deque分类上属于顺序存储容器,因此迭代器理所应当提供随机访问迭代器的所有功能。但在内部deque是分段的,因此迭代器需要知道片段的大小以及片段的地址,以在到达片段边缘时跳转到另一个片段。SGI的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| template<typename T,typename Ref,typename Ptr,size_t BufSize> struct _deque_iterator { typedef T** map_pointer; typedef _deque_iterator self; static size_t buffer_size(){}; T* cur; T* first; T* last; map_pointer node;
set_node(map_pointer new_node) { node=new_node; first=*new_node; last=first+difference_type(buffer_size()); } difference_type operator-(self& x) const { return difference_type(buffer_size())*(node-x.node-1)+(cur-first)+(x.last-x.cur); }
self& operator++() { ++cur; if(cur==last) { set_node(node+1); cur=first; } return *this; }
self& operator+=(difference_type n) { difference_type offset=n+cur-first; if(offset>=0 && offset<difference_type(buffer_size())) cur+=n; else { difference_type node_offset=offset>0 ? offset/difference(buffer_size()):-difference_type((-offset-1)/buffer_size())-1; set_node(node+node_offset); cur=first+offset-node_offset*difference_type(buffer_size()); } return *this; }
}
|
双端队列的结构
SGI的deque结构如上图所示,其具体实现见代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template<typename T,typename Alloc=alloc,size_t BuffSize=0> class deque { public: typedef _deque_iterator<T,T&,T*,BuffSize> iterator;
protected: typedef pointer* map_pointer;
iterator start; iterator finish; map_pointer map; size_type map_size;
public: iterator begin() {return start;} iterator end() {return finish;} reference front() {return *start;} reference back() { iterator tmp=finish; --tmp; return *tmp; }
}
|
注意deque迭代器取值操作符返回*cur,start迭代器的cur总是指向首元素,finish迭代器的cur指向最后一个元素的下一位,因此需要前移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| deque(int n,const value_type& value):start(),finish(),map(0),map_size(0) { fill_initialize(n,value); }
template<typename T,typename Alloc,size_t BuffSize> void fill_initialize(size_type n,const value_type& value) { create_map_and_nodes(n); map_pointer cur; for(cur=start.node;cur<finish.node;++cur) uninitialized_fill(*cur,*cur+buffer_size(),value); uninitialized_fill(finish.first,finish.cur,value); }
template<typename T,typename Alloc,size_t BuffSize> void create_map_and_nodes(size_type num_element) { size_type num_nodes=num_element/buffer_size()+1; map_size=max(8,num_nodes+2); map=map_allocator::allocate(map_size); map_pointer nstart=map+(map_size-num_nodes)/2; map_pointer nfinish=nstart+num_nodes-1;
map_pointer cur; for(cur=nstart;cur<=nfinish;++cur) *cur=allocate_node();
start.set_node(nstart); finish.set_node(nfinish); start.cur=start.first; finish.cur=finish.first+num_element%buffer_size(); }
|
再来看看deque在内部如何处理元素插入,以push_back为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| void push_back(const value_type& t) { if(finish.cur != finish.last -1) { construct(finish.cur,t); ++finish.cur; } else push_back_aux(t); }
void push_back_aux(const value_type& t) { value_type t_copy=t; reserve_map_at_back(); *(finish.node+1)=alocate_node(); construct(finish.cur,t_copy); finish.set_node(finish.node+1); finish.cur=finish.first; }
void reserve_map_at_back(size_type node_to_add=1) { if(node_to_add>map_size-(finish.node-map)-1) reallocate_map(node_to_add,false); }
void reallocate_map(size_type node_to_add,bool add_at_front) { size_type old_num_nodes=finish.node-start.node+1; size_type new_num_nodes=old_num_nodes+node_to_add; map_pointer new_nstart; if(map_size>2*new_num_nodes) { new_nstart=map+(map_size-new_num_nodes)/2+(add_at_front?node_to_add:0); if(new_nstart<start.node) copy(start.node,finish.node+1,new_nstart); else copy_backward(start.node,finish.node+1,new_nstart+old_num_nodes); } else { size_type new_map_size=map_size+max(map_size,node_to_add)+2; map_pointer new_map=map_allocator::allocate(new_map_size); new_nstart=new_map+(new_map_size-new_num_nodes)/2+(add_at_front?node_to_add:0); copy(start.node,finish.node+1,new_start); map_allocator::deallocate(map,map_size); map=new_map; map_size=new_map_size; } start.set_node(new_nstart); finish.set_node(new_nstart+old_num_nodes+1); }
|
接下来是删除操作,SGI的deque在片段中没有元素时会销毁片段以节省内存,具体实现如下,以pop_back为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void pop_back() { if(finish.cur!=finish.first) { --finish.cur; destroy(finish.cur); } else pop_back_aux(); }
void pop_back_aux() { deallocate_node(finish.first); finish.set_node(finish.node-1); finish.cur=finish.last-1; destroy(finish.cur); }
|
接下来是clear方法,该方法清除deque所有元素并将deque初始化,SGI的deque在初始状态仍会保留一个片段,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void clear() { for(map_pointer node=start.node+1;node<finish.node;++node) { destroy(*node,*node+buffer_size()); deallocate(*node,buffer_size()); } if(start.node!=finish.node) { destroy(start.cur,start.last); destroy(finish.first,finish.cur); deallocate(finish.first,buffer_size()); } else destroy(start.cur,finish.cur); finish=start; }
|
基本上常见的deque操作都与上面的实现类似,注意map的操作即可。
MS的deque实现
MS与SGI的实现有较大区别,首先在deque内部成员上VS2013没有定义start和finish两个迭代器,而是定义了一个_Myoff记录头节点偏移量以及_Mysize记录元素数量:
1 2
| size_type _Myoff; size_type _Mysize;
|
在内部,deque利用_Myoff计算节点下标来访问节点:
1 2 3 4 5
| size_type _Getblock(size_type off) const { return ((off/_DEQUESIZE) & (this->_Mapsize-1)); }
|
其次,MS的deque采用倍增的扩展策略,即每次重新分配map时大小变为原来的2倍(初始为8,不可自定义):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| void _Growmap(size_type _Count) { typedef typename _Alpty::pointer _Blockptr; _Alpty _Almap(this->_Getal()); size_type _Newsize = 0 < this->_Mapsize ? this->_Mapsize : 1; while (_Newsize - this->_Mapsize < _Count || _Newsize < _DEQUEMAPSIZ) { _Newsize *= 2; } _Count = _Newsize - this->_Mapsize; size_type _Myboff = this->_Myoff / _DEQUESIZ; _Mapptr _Newmap = _Almap.allocate(this->_Mapsize + _Count); _Mapptr _Myptr = _Newmap + _Myboff; _Myptr = _Uninitialized_copy(this->_Map + _Myboff,this->_Map + this->_Mapsize,_Myptr, _Almap); if (_Myboff <= _Count) { _Myptr = _Uninitialized_copy(this->_Map,this->_Map + _Myboff,_Myptr, _Almap); _Uninitialized_default_fill_n(_Myptr, _Count - _Myboff,_Almap); _Uninitialized_default_fill_n(_Newmap, _Myboff, _Almap); } else { _Uninitialized_copy(this->_Map,this->_Map + _Count,_Myptr, _Almap); _Myptr = _Uninitialized_copy(this->_Map + _Count, this->_Map + _Myboff,_Newmap, _Almap); _Uninitialized_default_fill_n(_Myptr, _Count, _Almap); } _Destroy_range(this->_Map + _Myboff, this->_Map + this->_Mapsize, _Almap); if (this->_Map != _Mapptr()) _Almap.deallocate(this->_Map,this->_Mapsize); this->_Map = _Newmap; this->_Mapsize += _Count; }
|
相比起来MS的实现更加复杂化,结构层次也不明显,代码质量明显比SGI差很多。最后再吐槽一下MS神奇的变量命名方式以及各种奇葩#define。