下面這段代碼是我定義的Stack類模板,接下來介紹幾種用2個該Stack類實現隊列Queue的幾種方法。算法
template<class T, int DEFAULT_CAPACITY = 0> class Stack { public: Stack(); Stack(const Stack<T> &st); Stack &operator=(const Stack<T> &st); ~Stack(); public: void Push(const T &data); void Pop(); T &Top(); T &End(); bool Empty(); size_t Size(); void Print(); protected: void CheckCapacity(); protected: T *_arr; size_t _top; size_t _capacity; };
聲明:爲了實現除「入隊」「出隊」以外更多的功能,好比「打印」等,我將上面那個已造好的「輪子」Stack作了擴展,增長了一些成員方法。而若是你關注的重點是push和pop的算法,那麼其實並不須要在乎我造的下面這個「輪子」。能夠直接跳過下面的代碼,並把全部我使用的Stack類型看成庫裏的stack便可.ide
擴展後的Stack:優化
template<class T, int DEFAULT_CAPACITY = 0> class Stack { public: Stack() :_arr(NULL) , _top(0) , _capacity(0) {} Stack(const Stack<T> &st) :_arr(new T[st._capacity]) , _top(st._top) , _capacity(st._capacity) { for (size_t i = 0; i < _capacity; i++) { _arr[i] = st._arr[i]; } } Stack &operator=(const Stack<T> &st) { if (st._arr != _arr) { delete[] _arr; _arr = new T[st._capacity]; for (size_t i = 0; i < st._capacity; i++) { _arr[i] = st._arr[i]; } _top = st._top; _capacity = st._capacity; } return *this; } ~Stack() { if (_arr != NULL) { delete[] _arr; } } public: void Push(const T &data) { CheckCapacity(); _arr[_top] = data; ++_top; } void Pop() { --_top; } T &Top() { return _arr[_top - 1]; } T &End() { return _arr[0]; } bool Empty() { if (0 == _top) { return true; } else { return false; } } size_t Size() { return _top; } void Print() { for (size_t i = 0; i < _top; i++) { cout << _arr[i] << " "; } cout << endl; } void RePrint() { if (0 == _top) { return; } for (int i = _top - 1; i >= 0; i--) { cout << _arr[i] << " "; } cout << endl; } protected: void CheckCapacity() { if (_top == _capacity) { _capacity = _capacity + 3; T *tmp = new T[_capacity]; for (size_t i = 0; i < _top; i++) { tmp[i] = _arr[i]; } delete[] _arr; _arr = tmp; } } protected: T *_arr; size_t _top; size_t _capacity; };
--------------------------------------------------------------------------------this
1、普通版本spa
棧的特色是「先入後出」,而隊列的特色是「先入先出」。
3d
因此能夠定義一個類Queue,包含2個成員對象:對象
一個棧_stack1存放數據,另外一個棧_stack2用來臨時存放數據,經過一些壓棧出棧的成員方法就能夠實現對隊列的入隊、出隊操做。blog
實現的2個棧組成的隊列以下圖所示,如今要將一組數據【1 2 3 4 5】放入隊列中:隊列
先將這組數依次壓入_stack1中,而後再將_stack1中的元素依次出棧壓入_stack2中:ci
這時候,_stack2中的元素依次出棧,就至關於隊列的出隊操做了。
用代碼實現:
定義一個類模板Queue:
template<class T> class Queue { Queue() :_size(0) {} void Push(const T &data) //入隊 { _stack1.Push(data); ++_size; } void Pop() //出隊 { Converse(_stack2, _stack1); _stack2.Pop(); Converse(_stack1, _stack2); --_size; } protected: void Converse(Stack<T> &dst, Stack<T> &src) //src->dst { while (size--) { dst.Push(src.Top()); src.Pop(); } } protected: Stack<T> _stack1; Stack<T> _stack2; size_t _size; };
其中,
成員方法Converse():做用是將棧src中的內容依次出棧,壓入棧dst中。
成員方法Push() :入隊操做,每次將元素data存入成員對象_stack1中。
成員方法Pop() :出隊操做,彈出第一個送入的元素。其中,第二個Converse的做用是還原。
能夠看出,這種入隊、出隊的算法,須要保證元素始終在_stack1中維護,而只有在出棧的時候用到_stack2臨時存放數據。
採用這種方式實現的隊列,能夠實現正常的入隊、出隊操做,但應該注意到,其中出隊操做須要進行兩次壓棧,咱們能夠對一個細節稍做優化,進一步提升出隊操做的執行效率。
下圖爲優化後的出隊操做:
區別在於,在出隊操做時,將_stack1中的(_size - 1)個元素彈出並壓入_stack2中。
彈出後,也不須要將_stack2的元素「倒回」_stack1中。
2、代碼優化
具體的實現步驟爲:
出隊操做時:
而是在每次執行出隊的時候進行一次判斷:
若_stack2爲空,則將_stack1中的(_size - 1)個元素彈出並壓入_stack2中,並彈出_stack中剩下的那個元素(就是我上面說的那個步驟);
若_stack2不爲空,則彈出_stack2中最頂層的元素。
在入隊操做時,判斷_stack1是否爲空:
若爲空,則先將_stack2中的元素依次彈出並壓入_stack1中,而後再將入棧元素壓入_stack1中(左圖)
不然,直接將入棧元素壓入_stack1中
優化後的方案用代碼實現以下:
template<class t> class queue { public: queue() :_size(0) {} queue(const queue &que) { _stack1 = que._stack1; _size = que._size; } public: void Push(const t &data) { if (_stack1.Empty() && !_stack2.Empty()) { Converse(_stack1, _stack2); } _stack1.Push(data); ++_size; } void Pop() { if (_stack2.Empty()) { if (_stack1.Empty()) { return; } RemainConverse(_stack2, _stack1); _stack1.Pop(); } else { _stack2.Pop(); } --_size; } void Print() { _stack1.Print(); _stack2.RePrint(); } bool Empty() { return (0 == _size); } t& Front() { if (_stack1.empty()) { return _stack2.top(); } else { return _stack1.end(); } } t& Back() { if (_stack1.Empty()) { return _stack2.End(); } else { return _stack1.Top(); } } size_t Size() { return _size; } protected: void RemainConverse(Stack<t> &dst, Stack<t> &src) { size_t count = src.Size() - 1; while (count--) { dst.Push(src.Top()); src.Pop(); } } void Converse(Stack<t> &dst, Stack<t> &src) //src->dst { while (!src.Empty()) { dst.Push(src.Top()); src.Pop(); } } protected: Stack<t> _stack1; Stack<t> _stack2; size_t _size; }; int main() { queue<int> que1; que1.Push(1); que1.Push(2); que1.Push(3); que1.Push(4); que1.Print(); que1.Pop(); que1.Print(); que1.Push(5); que1.Print(); return 0; }
到目前咱們已經實現了2種不一樣的方式實現這個隊列。
這兩種方法相比,第一種方法每次進行出隊操做都要移動2次棧中的所有數據
而對於第二種方法實現的隊列,若是連續進行入隊或者出隊操做,則不須要移動2個棧中的數據,能必定程度上提升效率。
3、進一步優化
能夠看出,_stack1和_stack2中所有元素(或者說,所有元素-1)轉移的次數越少,程序的執行效率就越高。
還有一種方法能夠進一步減小_stack1和_stack2中所有元素交換的次數:
檢測_stack2是否爲空:
若爲空,則將_stack1中的元素依次彈出並壓入_stack2中,
若不爲空,則彈出_stack2中棧頂的元素
入隊:
將元素壓入_stack。
能夠看出,這種實現方式入隊永遠是從_stack2中彈出元素,出隊永遠是向_stack1中壓入元素
而只有當入棧時檢測到_stack2爲空時,才執行2個棧之間所有元素的轉移。
用以下的圖能更形象地表示:
實現代碼以下:
template<class T> class Queue { public: Queue() :_size(0) {} Queue(const Queue &que) { _stack1 = que._stack1; _size = que._size; } public: void Push(const T &data) { _stack1.Push(data); ++_size; } void Pop() { if (_size == 0) //異常 { return; } if (_stack2.Empty()) { Converse(_stack2, _stack1); } _stack2.Pop(); --_size; } void Print() { _stack2.RePrint(); _stack1.Print(); } bool Empty() { return (0 == _size); } T& Front() { if (_stack2.Empty()) { return _stack2.End(); } else { return _stack2.Top(); } } T& Back() { return _stack1.Top(); } size_t Size() { return _size; } protected: void Converse(Stack<T> &dst, Stack<T> &src) { while (!src.Empty()) { dst.Push(src.Top()); src.Pop(); } } protected: Stack<T> _stack1; Stack<T> _stack2; size_t _size; };
4、總結
這裏一共提供了3種方法: