漫画算法-小灰的算法之旅(23)
1. 如何用栈实现队列
Q: 用两个栈来模拟一个队列,要求实现队列的两个基本操作,入队和出队。
思路
栈的特点是先入后出(FILO),出入元素都是在同一端(栈顶)。
队列的特点是先入先出(FIFO),出入元素是在不同的两端(队头和队尾)。
由于我们可以使用两个栈,那么可以让其中一个栈作为队列的入口,负责插入新元素;另一个栈作为队列的出口,负责移除老元素。
队列的主要操作无非有两个:入队列、出队列。
在模拟入队列操作时:每一个元素都被压入到栈A中。
让元素1入队列。
让元素2入队列。
让元素3入队列。
这时,我们希望最先入队的元素1出队列,需要怎么做呢?
让栈A中的所有元素按顺序出栈,再按照出栈顺序压入栈B。这样以来,元素从栈A弹出并压入栈B的顺序是3,2,1;和当初进入栈A的顺序1,2,3,是相反的。
此时让元素1出队列,也就是让元素1从栈B中弹出。
让元素2出队列。
如果此时,又想做入队列操作呢?当有新元素入队时,重新把新元素压入栈A中。
让元素4入队。
此时出队操作仍然从栈B中弹出元素。
让元素3出队。
此时栈B已经空了,如果再想出队列该怎么办呢?也不难,只要栈A中还有元素,就像刚才一样,把栈A中的元素弹出并压入栈B即可。
让元素4出队列。
时间复杂度
入队操作的时间复杂度显然是O(1)。至于出队操作,如果涉及栈A和栈B的元素迁移,那么一次出队的时间复杂度是O(n);如果不用迁移,时间复杂度是O(1)。
代码实现
private Stack<Integer> stackA=new Stack<Integer>();
private Stack<Integer> stackB=new Stack<Integer>();
/**
* 入队操作
* param element 入队的元素
*/
public void enQueue(int element){
stackA.push(element);
}
/**
* 出队操作
*/
public Integer deQueue(){
if(stackB.isEmpty()){
if(stackA.isEmpty()){
return null;
}
transfer();
}
return stackB.pop();
}
/**
* 栈A元素转移到栈B
*/
private void transfer(){
while(!stackA.isEmpty()){
stackB.push(stackA.pop());
}
}
public static void main(String[] args) throws Exception{
StackQueue stackQueue=new StackQueue();
stackQueue.enQueue(1);
stackQueue.enQueue(2);
stackQueue.enQueue(3);
System.out.println(stackQueue.deQueue());
System.out.println(stackQueue.deQueue());
stackQueue.enQueue(4);
System.out.println(stackQueue.deQueue());
System.out.println(stackQueue.deQueue());
}
golang实现
type MyQueue struct {
inStack []int
outStack []int
}
func NewQueue() *MyQueue {
return &MyQueue{}
}
// 将元素插入到队尾
func (q *MyQueue) enQueue(x int) {
q.inStack = append(q.inStack, x)
}
// 将元素出队列
func (q *MyQueue) deQueue() int {
// 如果outStack 为空,则将inStack栈中的数据出栈,并入栈到outStack
if q.isOutStackEmpty() {
q.inStackToOutStack()
}
val := q.outStack[len(q.outStack)-1]
// outStack 出栈
q.outStack = q.outStack[:len(q.outStack)-1]
return val
}
func (q *MyQueue) inStackToOutStack() {
for len(q.inStack) > 0 {
// outStack 入栈
q.outStack = append(q.outStack, q.inStack[len(q.inStack)-1])
// inStack 出栈
q.inStack = q.inStack[:len(q.inStack)-1]
}
}
// 返回当前队头头部元素
func (q *MyQueue) peek() int {
if q.isOutStackEmpty() {
q.inStackToOutStack()
}
return q.outStack[len(q.outStack)-1]
}
func (q *MyQueue) isEmpty() bool {
return q.isInStackEmpty() && q.isOutStackEmpty()
}
func (q *MyQueue) isOutStackEmpty() bool {
return len(q.outStack) == 0
}
func (q *MyQueue) isInStackEmpty() bool {
return len(q.inStack) == 0
}
func main() {
stackQueue := NewQueue()
stackQueue.enQueue(1)
stackQueue.enQueue(2)
stackQueue.enQueue(3)
log.Println("deQueue value is",stackQueue.deQueue())
log.Println("deQueue value is",stackQueue.deQueue())
stackQueue.enQueue(4)
log.Println("myQueue head value is",stackQueue.peek())
log.Println("deQueue value is",stackQueue.deQueue())
log.Println("myQueue is empty:",stackQueue.isEmpty())
log.Println("deQueue value is",stackQueue.deQueue())
log.Println("myQueue is empty:",stackQueue.isEmpty())
}
总结
本题思路其实还是比较清晰的。
将一个栈当作输入栈inStack
,用于压入enQueue
传入的数据;另一个栈outStack
当作输出栈,用于deQueue
和peek
操作。每次deQueue
或peek
时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。