帶你深入理解STL之Stack和Queue
上一篇部落格,帶你深入理解STL之Deque容器中詳細介紹了deque容器的原始碼實現方式。結合前面介紹的兩個容器vector和list,在使用的過程中,我們確實要知道在什麼情況下需要選擇恰當的容器來滿足需求和提升效率。一般選擇的準則有如下幾條:
- 如果需要隨機訪問一個容器,vector比list要好
- 如果需要經常插入和刪除操作的話,list比vector要好
- 如果既要隨機存取,又要關心兩端資料的插入和刪除,則選擇deque
好了,複習完前面的知識後,開始介紹今天的兩個容器stack和queue。由於stack和queue都是基於deque來實現的,所以相應的程式碼會比較簡單,也是比較輕鬆易實現的,下面一起去看看吧。
stack
如果把deque比作一個管道,兩頭都可進可出的話,stack就是一個桶!只能一頭進一頭出,而且,壓在下面的東西你看不到,你要是想看,只能把上面的東西拿出來再去看。
stack是一種先進後出的資料結構,允許新增元素、移除元素和取得最頂端的元素,除了最頂端,沒有任何其他方法可以存取stack中的元素,也就是說stack沒有遍歷行為,因此,stack是沒有迭代器的!!!!!
以deque為底層容器來實現stack這種資料結構,簡直不能再簡單,基本的操作函式都已經定義好了,deque可以為它完成所有工作。與其說stack是一種容器,倒不如說它是一種配接器,一種容器介面卡。
下面我們就來看看stack的原始碼,真的沒騙你,超級簡單。
template <class T, class Sequence = deque<T> // 以deque作為預設底層容器
class stack
{
// #define __STL_NULL_TMPL_ARGS <>
friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);
friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);
public:
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_type;
typedef typename Sequence::reference reference;
typedef typename Sequence::const_reference const_reference;
protected:
Sequence c; // 底層容器,stack全靠它來實現
public:
// 以下函式直接呼叫底層容器的介面即可實現
// 判斷stack是否為空
bool empty() const { return c.empty(); }
// stack中元素個數
size_type size() const { return c.size(); }
// 返回棧頂元素, 注意這裡返回的是引用!!!
reference top() { return c.back(); }
const_reference top() const { return c.back(); }
// 在棧頂追加新元素
void push(const value_type& x) { c.push_back(x); }
// 移除棧頂元素, 注意不返回元素的引用,
// 很多初學者隨機用此容器時經常誤認為pop()操作同時會返回棧頂元素的引用
void pop() { c.pop_back(); }
};
// 判斷兩個stack是否相等, 就要測試其內部維護容器是否相等
// x.c == y.c會呼叫容器過載的operator ==
template <class T, class Sequence>
bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y)
{
return x.c == y.c;
}
// 比較兩個迭代器的大小,即比較底層容器的大小
template <class T, class Sequence>
bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y)
{
return x.c < y.c;
}
你沒有看錯,stack的原始碼就只有上面幾句話,全是呼叫底層容器的介面。下面再來看看它的同胞queue,同樣很簡單。
queue
queue是一種先進先出的資料結構,上面說道,dequeu是個雙向可進可出的管道,stack是一個桶,queue就是一個單向的水管,只能一端進,一端出。
queue允許新增元素、移除元素、從最底端插入元素,從最頂端取得元素,但是,從了最底端插入,最頂端取出之外,沒有任何其他方法可以存取queue裡面的元素,queue和stack一樣,不允許有遍歷行為,因此,queue也沒有迭代器!!!!
queue和stack一樣,也是一種容器介面卡,只需要呼叫底層容器的介面就能實現。下面來看看它的原始碼吧。
template <class T, class Sequence = deque<T> >
class queue
{
friend bool operator== __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
friend bool operator< __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
public:
// 由於queue僅支援對隊頭和隊尾的操作, 所以不定義STL要求的
// pointer, iterator, difference_type
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_type;
typedef typename Sequence::reference reference;
typedef typename Sequence::const_reference const_reference;
protected:
Sequence c; // 底層容器
public:
// 以下操作和stack一樣
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
const_reference front() const { return c.front(); }
reference back() { return c.back(); }
const_reference back() const { return c.back(); }
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
// 過載==操作符,比較底層容器即可
template <class T, class Sequence>
bool operator==(const queue<T, Sequence>& x, const queue<T, Sequence>& y)
{
return x.c == y.c;
}
// 同上
template <class T, class Sequence>
bool operator<(const queue<T, Sequence>& x, const queue<T, Sequence>& y)
{
return x.c < y.c;
}
後記
這篇部落格的兩個”容器“比較容易理解,因為底層都已經學過了,只需要呼叫介面即可。最後再囉嗦兩句,stack是一個先進後出的容器,queue是一個先進先出的容器,在使用過程中,需要根據你的需求來選擇。我在刷leetcode的時候,碰到遍歷二叉樹的問題,基本上前、中後序遍歷的非遞迴實現中,都會用到stack,而樹的層序遍歷中,會採用queue,具體的做法可以參考我的這片博文,全面剖析樹的各類遍歷方法,相信看完你會對stack和queue的使用有進一步的理解!
參考:
- 侯捷先生的《STL的原始碼剖析》