用两个栈实现一个队列

在阅读《算法导论》基本数据结构那一章时,看到了练习10.1-6 要求用两个栈来实现队列,于是好奇为什么会存在这样一种需求,在上网查阅了相关资料后,将收获整理如下。其中参考来源主要是https://stackoverflow.com/questions/7395400/why-do-we-do-implement-a-queue-using-2-stacks, stackoverflow的一个问题

一、如何用两个栈来实现队列

算法基本思路:定义两个栈,不妨称为StackIn和StackOut,当接收到入队的请求时,将元素压入StackIn,接收到出队请求时,先检测StackOut是否为空,不为空则弹出StackOut的栈顶元素;若为空,则逐个弹出StackIn的元素并将其压入StackOut,直到StackIn为空,此时再弹出StackOut的栈顶元素即可。

可以这样理解:队列是FIFO,那么对于双栈实现的队列,第一个进入的元素在StackIn栈底,此时若想输出它,只能先处理它之上的那些元素,处理方法即将这些元素从In转移到Out。最后一个压入Out为原先In的栈底元素,也就是第一个入队的元素。所以此时弹出Out的栈顶元素就是第一个进入的元素了。我们注意到接下来第二,第三,....的元素在Out中也已经排列好了,之后若想出队,就弹出Out中的元素就好了,直到为空,。

在C++标准库提供的stack的基础上实现这样一个队列

template
class Stack {
private:
	std::stack s;
public:
	void push(const T & e) { s.push(e); }
	bool empty() { return s.empty(); }
	//将stl的stack重新封装一下,把pop变为栈弹出的同时也返回值
	T pop() {
		T topElement = s.top();
		s.pop();
		return topElement;
	}
	T top() {
		return s.top();
	}
};
template
class Queue {
private:
	Stack stackIn;
	Stack stackOut;
	void feedStackOut() {
		while (!stackIn.empty())
			stackOut.push(stackIn.pop());
	}
public:
	bool empty() { return stackIn.empty() && stackOut.empty(); }
	void enqueue(T &e) { stackIn.push(e); }
	T dequeue() {
		if (stackOut.empty())
			feedStackOut();//当stackOut为空时,将stackIn中的元素转移到stackOut中去
		return stackOut.pop();
	}
};

二、为什么会有用两个栈来实现队列的需要?

2.1 一些函数式语言,如Haskell, ML, Lisp等内置了list类型, 一般是单向链表。这种链表很适合实现一个栈结构,因为我们只需要在表头添加一个节点作为入栈,或是去掉表头作为出栈,这只需要O(1)的时间。但是若用这种内置链表实现队列的话,入队的操作是O(1)的实现,但是出队的操作需要我们遍历到表尾,这需要O(n)的时间。

那么此时用两个栈来实现队列,我们可以将出队的时间经过分摊以后将时间复杂度降为O(1),所以在这些语言中用双栈构建队列可能是一种好的选择。

2.2一些额外的函数功能对于双栈搭建的队列而言更好实现

例如findMax,findMin等查找栈或队列中最小值的函数。

带有这种功能栈的实现思路

#include
#include
#define ELEMENT 0
#define MIN 1
#define MAX 2
using namespace std;
template
class Stack {
private:
	std::stack> s;
public:
	void push(const T & e) {
		vector v(3, e);//用一个大小为3的向量保存要压入的元素,以及栈中的最大值和最小值
		if (!empty()) {
			//更新该向量的最大值最小值
			v[MIN] = findMin() < v[MIN] ? findMin() : v[MIN];
			v[MAX] = findMax() > v[MAX] ? findMax() : v[MAX];
		}
		s.push(v);
	}
	bool empty() { return s.empty(); }
	//将stl的stack重新封装一下,把pop变为栈弹出的同时也返回值
	T pop() {
		vector v = s.top();
		T topElement = v[ELEMENT];
		s.pop();
		return topElement;
	}
	T top() {
		vector v = s.top();
		T topElement = v[ELEMENT];
		return topElement;
	}
	//由于压入时的比较,我们知道栈顶元素的那个向量保存的是当前栈中所有元素的最大值和最小值
	T findMax() {
		vector v = s.top();
		T max = v[MAX];
		return max;
	}
	T findMin() {
		vector v = s.top();
		T min = v[MIN];
		return min;
	}
};

由于栈FILO的性质,我们维护最大值最小值时比较容易,因为每个元素所在的那个向量的最大值、最小值都是和之前入栈的元素进行比较得到的。所以当某个元素出栈时,不需要再去检查这个元素之前的那些元素是否需要更新。

但是对于队列而言,其FIFO的性质使得我们很难维护最大值最小值。当我们现在要入队一个元素时,这个元素所在那个向量的最大值最小值,需要跟队伍中已存在的元素进行比较。之后我们如果出队一个元素,因为first in firs out,当前队伍中最早入队的元素出队了,但是我们知道这个排在这个元素后面的那些元素,它们所在的向量的最值均与这个元素比较过,所以这个元素出队后,剩下的每个元素均需要重新与在其前面的元素进行比较,确认最大值、最小值是否发生改变。

    所以这个出队时维护最大值、最小值的所耗费的时间是很难接受的。这里需要说明一点的是,对于普通方式实现的队列,不需要像上述stack那样为每个元素维护一个向量。它只需为整个队列,维护两个变量即可。这样出队时,只需进行O(n)的比较即可,否则的话需要进行O(n^2)此比较。

    那有没有更好的方式去实现这样一个能够维护最大值最小值的队列呢?有的,就是利用上面实现的那个已具有这些功能的两个栈即可。

思路是这样:当要求队列的最大值/最小值时,我们可以比较两个栈中的最大值/最小值,从而确定队列中的最大值/最小值,拿到两个栈中最大值/最小值是O(1),比较得到队列的最大值/最小值也是O(1),所以我们可以得到一个在时间上复杂度更优的功能队列。不过需要注意处理两个栈中有一个栈为空的特殊情况。

template
class Queue {
private:
	Stack stackIn;
	Stack stackOut;
	void feedStackOut() {
		while (!stackIn.empty())
			stackOut.push(stackIn.pop());
	}
public:
	bool empty() { return stackIn.empty() && stackOut.empty(); }
	void enqueue(T &e) { stackIn.push(e); }
	T dequeue() {
		if (stackOut.empty())
			feedStackOut();//当stackOut为空时,将stackIn中的元素转移到stackOut中去
		return stackOut.pop();
	}
	T findMax() {
		if (!empty()) {
			if (stackIn.empty())
				return stackOut.findMax();
			else if (stackOut.empty())
				return stackIn.findMax();
			else {
				T max = stackIn.findMax() > stackOut.findMax() ? 
					    stackIn.findMax() : stackOut.findMax();
				return max;
			}
		}

	}
	T findMin() {
		if (!empty()) {
			if (stackIn.empty())
				return stackOut.findMin();
			else if (stackOut.empty())
				return stackIn.findMin();
			else {
				T min = stackIn.findMin() < stackOut.findMin() ?
						stackIn.findMin() : stackOut.findMin();
				return min;
			}
		}
	}
};


你可能感兴趣的:(用两个栈实现一个队列)