基于环形缓冲区的deque实现方法

众所周知,C++ STL中有一个叫做deque的容器,实现的是双端队列数据结构,这种队列允许同时从队列的首部和尾部插入和删除数据。
然而在STL中这种数据结构是用”分段连续”的物理结构实现的(可以参考侯捷老师的《STL源码剖析》)。网上分析STL中这种容器的文章很多,如:http://blog.csdn.net/baidu_28312631/article/details/48000123 (STL源码剖析——deque的实现原理和使用方法详解)就分析得很清楚。
个人认为STL的这种实现方法略有画蛇添足的嫌疑,不光增加了空间管理复杂度,而且使得iterator的随机访问变得低效(相对vector的iterator)。
侯捷老师在《STL源码剖析》第143页也提到:“对deque进行的排序操作,为了最高效率,可将deque先完整复制到一个vector身上,将vector排序后(利用STL sort算法),再复制回deque。”只因deque的Randon Access Iterator远不如vector的原生指针来得高效。
虽然作者暂时没弄明白为什么需要对“队列”做排序操作,即便如此,侯捷老师指出的copy-sort-copy思路,作者也是难苟同。

下面开始引出本文的主角——基于环形缓冲区的deque实现方法。
由于deque需要允许从队列头部插入和删除数据,如果像vector那样,为了在头部增删数据,每次都需要移动整个列表,显然是无法忍受的。
然而,利用环形链表思想,将vector的缓冲区看做是一个环形,则可以完美的在像vector一样连续的可增长的连续存储空间内实现deque。
数据结构定义如下:

class deque{
    // pointer to range of storage array.
    //full storage is [_Myfirst, _Mylast)
    //buffer is cycle, that is to say, next(_Mylast-1)=_Myfirst, prev(_Myfirst)=_Mylast-1
    pointer _Myfirst, _Mylast;

    //head and tail of deque
    //real data is [_Myhead,_Mytail)
    //so if tail<head, data is [_Myhead, _Mylast-1, _Myfirst, _Mytail)
    //head points to the first elem  available for read
    //tail points to the first space available for write
    pointer _Myhead, _Mytail;
}

其中[_Myfirst, _Mylast)记录了当前缓冲区的地址范围(即当前允许的最大队列长度)。
_Myhead和_Mytail记录了当前队列的头部和尾部的位置。
由于缓冲区是被看做是环形的,所以数据[_Myhead,_Mytail)可能有两种情况:
1. _Mytail >= _Myhead, 队列数据在[_Myhead,_Mytail)
2. _Mytail < _Myhead, 队列数据在[_Myhead, _Mylast-1, _Myfirst,_Mytail)

下面来详细讲述deque的4个操作:

void push_front(const value_type &_Val){
    _Myhead = _Prev(_Myhead);
    _STDEXT unchecked_uninitialized_fill_n(_Myhead, 1, _Val, this->_Alval);
    if (_Myhead == _Mytail){//buffer full
        _Buy(2*capacity());
    }
}

void push_back(const value_type &_Val){
    _STDEXT unchecked_uninitialized_fill_n(_Mytail, 1, _Val, this->_Alval);
    _Mytail = _Next(_Mytail);
    if (_Myhead == _Mytail){//buffer full
        _Buy(2*capacity());
    }
}

bool pop_front(){
    if (empty()){
        return false;
    }
    _Destroy(_Myhead);
    _Myhead=_Next(_Myhead);

    return true;
}

bool pop_back(){
    if (empty()){
        return false;
    }
    _Mytail = _Prev(_Mytail);
    _Destroy(_Mytail);
    return true;
}

bool empty() const{
    return _Myhead == _Mytail;
}

特别说明,当_Myhead == _Mytail的时候,表示队列为空。
可以看到,从头部push和pop的时候,实际只需要将_Myhead- -和_Myhead++
同理,从尾部push和pop的时候,只需要_Mytail++和_Mytail- -
当插入数据后,如果_Myhead==_Mytail,表示缓冲区已满,需要重新申请更大(2倍)的缓冲区,并把队列数据拷贝到新空间。

可以看到,以上代码在跟vector类似的连续空间上简单的实现了deque的所有关键操作,更让人欣慰的是,iterator(如果需要)是跟vector一样的原生指针,要在上面实现sort算法将是相当高效的,绝对不需要的copy-sort-copy。

我将代码放在了这里:
https://github.com/vipally/clab/blob/master/stl/deque/deque.hpp
欢迎有兴趣的筒子学习研究,如有不当的地方,敬请批评指正。

猜你喜欢

转载自blog.csdn.net/vipally/article/details/52865445