了解STL库中的squeue

来源:互联网 发布:java一对一聊天程序 编辑:程序博客网 时间:2024/06/06 03:52

【数据结构第二周作业文档】了解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这个底层容器来实现的,只是进一步封装后提供了自己的接口,使其符合相应的要求。

0 0
原创粉丝点击