本文主要内容有两个,是如何用双栈 实现队列 和如何用单队列实现 栈。(采用Java实现)
回忆一下:
栈:
队列:
相关教程:
一、双栈实现队列
设计目标
设计的目标是使用栈(后进先出 ,LIFO)来实现队列(先进先出,FIFO)的功能。队列的特点是先进入的元素最先被取出,而栈则是最后进入的元素最先被取出。因此,我们需要一种方法使得栈能够表现出队列的行为。
思路分析
1. 使用双栈实现
为了模拟队列的先进先出特性,我们可以使用两个栈:一个作为输入栈 (s1
),另一个作为输出栈 (s2
)。基本思路如下:
s1
作为输入栈,用于接收新元素的入队操作。s2
作为输出栈,用于模拟队列的出队操作。
2. push 操作
- 思想 :将新元素推入输入栈
s1
中。 - 实现 :直接将新元素
x
压入s1
栈中。
3. pop 操作
- 思想 :从输出栈
s2
中弹出栈顶元素,如果没有元素,则需要将输入栈s1
中的元素全部转移到输出栈s2
中。 - 实现 :
- 如果
s2
不为空,则直接从s2
弹出栈顶元素。 - 如果
s2
为空,则将s1
中的所有元素依次弹出并压入s2
,然后从s2
弹出栈顶元素。
- 如果
4. peek 操作
- 思想:查看队列的前端元素但不移除。
- 实现 :
- 如果
s2
不为空,则直接返回s2
的栈顶元素。 - 如果
s2
为空,则将s1
中的所有元素依次弹出并压入s2
,然后返回s2
的栈顶元素。
- 如果
5. empty 操作
- 思想:判断队列是否为空。
- 实现 :检查
s1
和s2
是否都为空。
源码:
java
class MyQueue {
private Stack<Integer> s1;// 输入栈
private Stack<Integer> s2;// 输出栈
public MyQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int x) {
s2.push(x);
}
public int pop() {
if(s1.isEmpty()){
while(!s2.isEmpty()){
s1.push(s2.pop());
}
}
return s1.pop();
}
public int peek() {
if (s1.isEmpty()) {
while (!s2.isEmpty()) {
s1.push(s2.pop());
}
}
return s1.peek();
}
public boolean empty() {
return s1.isEmpty() && s2.isEmpty();
}
}
解释:
- push 方法 :将新元素直接压入
s2
。 - pop 方法 :在
s1
为空时,将s2
中的元素转移到s1
中,然后从s1
中弹出栈顶元素。 - peek 方法 :与
pop
类似,但在s1
中查看栈顶元素而不移除。 - empty 方法:检查两个栈是否都为空。
- 新元素总是被压入
s2
。 - 当需要弹出或查看队列前端元素时,如果
s1
为空,则将s2
中的元素转移到s1
中。 s1
用于模拟队列的前端,而s2
用于接收新元素并临时存储。push
操作的时间复杂度为 O(1),因为直接将元素压入s2
。pop
和peek
操作的平均时间复杂度为 O(1),因为每个元素只会被转移一次(从s2
到s1
),并且在后续的操作中可以直接从s1
中获取。
二、单队列实现栈
设计目标
设计的目标是使用队列(先进先出 ,FIFO)来实现栈(后进先出,LIFO)的功能。栈的特点是最后进入的元素最先被取出,而队列则是先进入的元素先被取出。因此,我们需要一种方法使得队列能够表现出栈的行为。
思路分析
1. 使用单个队列实现
既然栈要求后进先出,那么我们可以把新加入的元素放在队列的末尾,然后通过一系列操作将它移到队列的前端。这样,当我们需要访问栈顶元素时,实际上就是访问队列的前端元素。
2. push 操作
- 思想 :当新的元素
x
被推入栈时,我们希望x
成为新的栈顶元素。由于队列是先进先出的,我们需要一种方式使x
成为队列的前端元素。 - 实现 :将
x
加入队列的尾部,然后将队列中除了最后一个元素外的所有元素都移到队列的尾部。这样做的结果是,最新的元素x
将会成为队列的前端元素,符合栈的要求。
3. pop 操作
- 思想:从栈中弹出栈顶元素,即删除队列的前端元素。
- 实现:直接从队列的前端移除元素即可。
4. top 操作
- 思想:获取栈顶元素但不移除。
- 实现:查看队列前端的元素即可。
5. empty 操作
- 思想:判断栈是否为空。
- 实现:检查队列是否为空。
6. 大小管理
- 思想 :由于
push
操作涉及到元素的重新排列,因此需要一个计数器来记录栈中的元素数量。 - 实现 :使用一个
size
变量来追踪栈中元素的数量。每次push
时增加size
,每次pop
时减少size
。
源码:
java
class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
int size = 0;
public void push(int x) {
queue.offer(x);
for (int i = 0; i < size; i++) {
queue.offer(queue.poll());
}
size++;
}
public int pop() {
size--;
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
解释:
-
push
方法:当一个元素被压入栈时,它首先被添加到队列的尾部,然后将队列中除了最后一个元素之外的所有元素都移回队列的末尾。这样做是为了确保新加入的元素实际上处于"顶部",即如果从队列中取出元素,最先取出的是最近压入的元素。 -
pop
方法:弹出栈顶元素,即队列的第一个元素,并返回它。注意在弹出前会先减小size
变量以保持计数准确。 -
top
方法:返回栈顶元素而不移除它,也就是队列的第一个元素。 -
empty
方法:检查队列是否为空,从而判断栈是否为空。
需要注意的是,在 push
操作中,每次插入新元素都需要进行 size
次队列元素的重新排列。这意味着 push
操作的时间复杂度为 O(n),其中 n 是当前栈中的元素数量。而 pop
, top
, 和 empty
操作的时间复杂度则为 O(1)。
三、总结
最后,希望通过以上练习可以加深各位对栈和队列这两种数据结构的基本操作有更进一步的掌握和理解,下期见,谢谢~