【数据结构第二周作业文档】了解STL库中的squeue
queue单向队列与栈有点类似,一个是在同一端存取数据,另一个是在一端存入数据,另一端取出数据。单向队列中的数据是先进先出(First In First Out,FIFO)。
在STL中,单向队列也是以别的容器作为底部结构,再将接口改变,使之符合单向队列的特性就可以了。
单向队列一共6个常用函数:front()取队列头部数据、back()取队列尾部数据、push(elem)在队尾加入elem数据、pop()队头数据出队、empty()判断是否为空、size()判断数据个数。
下面就给出单向队列在VS2008中单向队列的源代码:
<span style="font-size:18px;">//VS2008中 queue的定义 MoreWindows整理(http://blog.csdn.net/MoreWindows)
template<class _Ty, class _Container = deque<_Ty> >
class queue
{ // FIFO queue implemented with a container
public:
typedef _Container container_type;
typedef typename _Container::value_type value_type;
typedef typename _Container::size_type size_type;
typedef typename _Container::reference reference;
typedef typename _Container::const_reference const_reference;
queue() : c()
{ // construct with empty container
}
explicit queue(const _Container& _Cont) : c(_Cont)
{ // construct by copying specified container
}
bool empty() const
{ // test if queue is empty
return (c.empty());
}
size_type size() const
{ // return length of queue
return (c.size());
}
reference front()
{ // return first element of mutable queue
return (c.front());
}
const_reference front() const
{ // return first element of nonmutable queue
return (c.front());
}
reference back()
{ // return last element of mutable queue
return (c.back());
}
const_reference back() const
{ // return last element of nonmutable queue
return (c.back());
}
void push(const value_type& _Val)
{ // insert element at beginning
c.push_back(_Val);
}
void pop()
{ // erase element at end
c.pop_front();
}
const _Container& _Get_container() const
{ // get reference to container
return (c);
}
protected:
_Container c; // the underlying container
};</span>
可以看出,由于queue只是进一步封装别的数据结构,并提供自己的接口,所以代码非常简洁,如果不指定容器,默认是用deque来作为其底层数据结构的。
那么问题来了,deque又是什么样子的呢?
deque是双端队列,在队列头部和尾部可以快速的进行元素的插入和删除操作,相比vector而言有一定的优势,同时由于内部构造的设计,不存在vector那样扩充时带来的“配置新空间 / 移动旧数据 / 释放旧空间”问题。deque同时是STL中queue和stack的底层依赖组件。
在查找的资料中可以看到,vector底层采用的是一个数组来实现,list底层采用的是一个环形的双向链表实现,而deque这种容器则采用的是两者相结合。所谓结合,并不是两种数据结构的结合,而是某些性能上的结合。通常我们知道vector支持随机访问,而list支持常量时间的删除,deque支持的是随机访问以及首尾元素的删除。
看到一个图用来说明deque底层所采用的数据结构非常直观:
这个结构就与我们所学的知识有点似曾相识的感觉了!貌似可以将这个数据结构变相地看成是一个二维数组,可将map看成是该二维数组的数组名。
当然deque的设计不会这么简单,在我搜的资料里看起来很高大上的就是它的一个迭代器设计:
deque的迭代器的设计,主要是让其看起来像一个random access iterator,因此源码主要各种operator的重载。内部结构分为四个指针,分别为cur, first, last, node。
在某一个时刻,迭代器肯定指向某个具体元素,而这个元素位于N段连续内存块中的某一块,其中node指向map结构中的一个节点(这个节点指向当前的内存块),first指向当前内存块的起始位置,cur指向当前内存块中的特定元素节点,last指向当前内存块的末尾位置。
好吧一图抵千言,这回是另一个地方扒下来的图。。感觉颜色很好看:
最后,对于deque的分析。
deque的构造函数中会进行map结构的初始化操作,通过调用_M_initialize_map来实现,默认map的大小为8. 或者,当用户指定了每个内存块容纳的元素个数时,根据每个内存块默认的大小,算出需要多少个map指针,再加上2. 随后创建map需要的空间,并让start和finish尽可能落在map的中间位置,这样方便在头部和尾部都可以进行高效的扩充。
deque支持push_back / pop_back / push_front / pop_font,实现机制都是类似的,这里以push_back为例进贴一段源码。(我找的这一段出处、准确性不保证哈哈,但是貌似也就这个好看懂点嘿嘿。)虽然很长,但是不难看出我们学的数据结构的影子呢~
// ============================================================================
// deuqe class
// ============================================================================
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class deque : protected _Deque_base<_Tp, _Alloc> {
public:
// ==========================================================================
// push_back() 操作
// ==========================================================================
void push_back(const value_type& __t) {
// 如果还有多余1个节点的空间,则直接构造,否则借助_M_push_back_aux实现
if (_M_finish._M_cur != _M_finish._M_last - 1) {
construct(_M_finish._M_cur, __t);
++_M_finish._M_cur;
}
else
_M_push_back_aux(__t);
}
protected:
// ==========================================================================
// push_back() 依赖的内部操作
// ==========================================================================
void _M_push_back_aux(const value_type& __t)
{
value_type __t_copy = __t;
// 确保map还有剩余空间
_M_reserve_map_at_back();
// 构造一块新的内存块,并填充map对应的节点
*(_M_finish._M_node + 1) = _M_allocate_node();
__STL_TRY {
// 构造节点后更新指针
construct(_M_finish._M_cur, __t_copy);
_M_finish._M_set_node(_M_finish._M_node + 1);
_M_finish._M_cur = _M_finish._M_first;
}
__STL_UNWIND(_M_deallocate_node(*(_M_finish._M_node + 1)));
}
void _M_reserve_map_at_back (size_type __nodes_to_add = 1)
{
// 如果剩余空间不够,则需要扩充map了
if (__nodes_to_add + 1 > _M_map_size - (_M_finish._M_node - _M_map))
_M_reallocate_map(__nodes_to_add, false);
}
// 默认在尾部扩充
void _M_reallocate_map(size_type __nodes_to_add, bool __add_at_front)
{
// map中已经被填充的节点的个数
size_type __old_num_nodes = _M_finish._M_node - _M_start._M_node + 1;
// 需要被填充的新的节点个数
size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;
_Map_pointer __new_nstart;
// 如果map的大小比需要被填充的节点个数的两倍还大
// 则将map中start, finish区间往前移动即可
if (_M_map_size > 2 * __new_num_nodes) {
__new_nstart = _M_map + (_M_map_size - __new_num_nodes) / 2
+ (__add_at_front ? __nodes_to_add : 0);
if (__new_nstart < _M_start._M_node)
copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
else
copy_backward(_M_start._M_node, _M_finish._M_node + 1,
__new_nstart + __old_num_nodes);
}
// 否则就需要重新配置一个新的map了
else {
size_type __new_map_size =
_M_map_size + max(_M_map_size, __nodes_to_add) + 2;
_Map_pointer __new_map = _M_allocate_map(__new_map_size);
__new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2
+ (__add_at_front ? __nodes_to_add : 0);
copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
_M_deallocate_map(_M_map, _M_map_size);
_M_map = __new_map;
_M_map_size = __new_map_size;
}
_M_start._M_set_node(__new_nstart);
_M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
}
// ...
}
deque还有很多内部/外部函数,也都是围绕deque自身独特的设计进行操作。
总之,在STL库中,squeue和stack都是通过deque这个底层容器来实现的,只是进一步封装后提供了自己的接口,使其符合相应的要求。