下面这段代码是我定义的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种方法: