1. 程式人生 > >基於環形緩衝區的deque實現方法

基於環形緩衝區的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
歡迎有興趣的筒子學習研究,如有不當的地方,敬請批評指正。