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