数据结构:c++栈&&队列的相互实现
·
一、用队列实现栈 (Stack via Queues)
栈是一种后进先出(LIFO)的数据结构。要使用队列(先进先出,FIFO)来模拟栈,核心在于如何利用队列的操作让最后入队的元素能够最先出队。主要有两种方法:双队列法和单队列法。
双队列法:
这种方法使用两个队列,通常称为 q1(主队列)和 q2(辅助队列)
。
#include<iostream>
#include<string>
#include<algorithm>
#include<stdexcept>
#include<stack>
using namespace std;
class MyStack
{
private:
Queue<int> q1; // 主队列,通常用于存储栈中的所有元素
Queue<int> q2; // 辅助队列,用于在pop和top操作时临时存放元素
public:
// 将元素 x 压入栈顶
void push(int x)
{
q1.enqueue(x); // 新元素直接进入主队列q1的尾部
// 注意:此时新元素位于q1的尾部,而栈顶元素应该是最后加入的元素,
// 但队列是FIFO,所以需要通过后续的pop和top操作来调整访问顺序
}
// 移除并返回栈顶元素(最后入队的元素)
int pop() {
// 将q1中除最后一个元素外的所有元素转移到q2
while (q1.getsize() > 1) // 循环直到q1只剩一个元素(即栈顶元素)
{
q2.enqueue(q1.dequeue()); // 将q1队首元素出队并加入q2队尾
}
// 此时q1中剩下的最后一个元素就是最后进入队列的元素(即栈顶元素)
int result = q1.dequeue(); // 移除并获取该栈顶元素
// 将q2中的所有元素转移回q1,恢复q1存储所有剩余元素的状态
while (!q2.empty())
{
q1.enqueue(q2.dequeue()); // 将q2队首元素出队并加入q1队尾
}
return result; // 返回被移除的栈顶元素
}
// 返回栈顶元素但不移除
int top() {
// 将q1中除最后一个元素外的所有元素转移到q2
while (q1.getsize() > 1) // 循环直到q1只剩一个元素(即栈顶元素)
{
q2.enqueue(q1.dequeue()); // 将q1队首元素出队并加入q2队尾
}
// 获取q1的最后一个元素(栈顶元素)
int result = q1.dequeue(); // 取出栈顶元素
q1.enqueue(result); // 因为只是获取而非移除,所以需要将其重新放回q1
// 将q2中的所有元素转移回q1
while (!q2.empty())
{
q1.enqueue(q2.dequeue()); // 将q2队首元素出队并加入q1队尾
}
return result; // 返回栈顶元素的值
}
// 检查栈是否为空
bool empty() {
return q1.empty(); // 因为所有元素都存储在q1中,只需检查q1是否为空
}
};
int main()
{
MyStack s;
s.push(1);
s.push(2);
s.push(3);
s.push(4);
cout << s.top() << endl;
s.pop();
cout << s.top() << endl;
cout << s.empty() << endl;
return 0;
}
-
**
push(x)- 入栈操作**:- 先将新元素
x入队到辅助队列q2。 - 然后将主队列
q1中的所有元素依次出队并入队到q2。此举确保了新元素位于q2的队首(即栈顶)。 - 最后,交换
q1和q2的角色,使得q1始终是包含所有元素的主队列,q2再次变为空队列以备下次使用。 - 时间复杂度:O(n),因为需要转移
q1中的所有元素。
- 先将新元素
-
**
pop()- 出栈操作**:- 由于在每次
push操作后,q1的队首元素就是栈顶元素,因此直接让q1出队即可。 - 时间复杂度:O(1)。
- 由于在每次
-
**
top()- 获取栈顶元素**:- 与
pop操作类似,直接返回q1的队首元素但不移除。 - 时间复杂度:O(1)。
- 与
-
**
empty()- 检查栈是否为空**:- 检查主队列
q1是否为空即可。 - 时间复杂度:O(1)。
- 检查主队列
💡 提示:这种方法通过确保每次入栈操作后主队列 q1 的队首都是最新元素,使得 pop 和 top 操作变得非常简单直接,但 push 操作的成本较高
双队列法与单队列法对比
| 操作 | 双队列法 | 单队列法 | 说明 |
|---|---|---|---|
push(x) |
O(n) | O(1) | 单队列法直接入队,效率高 |
pop() |
O(1) | O(n) | 双队列法直接出队,效率高 |
top() |
O(1) | O(1) 或 O(n) | 取决于队列是否支持直接访问队尾 |
empty() |
O(1) | O(1) | 两者效率一致 |
| 空间复杂度 | O(n) | O(n) | 双队列法需要两个队列,但总元素数不变 |
选择建议:如果 push 操作非常频繁,单队列法可能更合适。如果 pop 和 top 操作非常频繁,双队列法可能更合适。
二、用栈实现队列 (Queue via Stacks)
#include<iostream>
#include<string>
#include<algorithm>
#include<stdexcept>
#include<stack>
using namespace std;
class MyQueue
{
private:
Stack<int>s1;
Stack<int>s2;
public:
void push(int x)
{
s1.push(x);
}
int pop()
{
if (s2.empty()) {//如果 outStack 为空,则将 inStack 中的所有元素依次弹出并压入 outStack。
while (!s1.empty())//此操作将元素的顺序反转,使得最先进入 inStack 的元素位于 outStack 的栈顶。
//弹出并返回 outStack 的栈顶元素。
{
s2.push(s1.pop());
}
}
return s2.pop();
}
int peek()//获取栈顶与弹栈的逻辑一样只是不用删除元素直接获取就行了
{
if (s2.empty()) {
while (!s1.empty())
{
s2.push(s1.pop());
}
}
return s2.top();
}
bool empty()
{
return s1.empty() && s2.empty();//同时为空即空
}
};
int main()//运行例子
{
MyQueue q1;
q1.push(5);
q1.push(6);
q1.push(7);
cout << q1.pop() << endl;
cout << q1.peek() << endl;
return 0;
}
队列是一种先进先出(FIFO)的数据结构。使用栈(后进先出,LIFO)来实现队列,最经典和高效的方法是双栈法
这种方法使用两个栈:一个作为输入栈 (inStack),专用于处理入队(push)操作;另一个作为输出栈 (outStack),专用于处理出队(pop)和查看队首(peek)操作。
-
**
push(x)- 入队操作**:- 新元素直接压入
inStack。 - 时间复杂度:O(1)。
- 新元素直接压入
-
**
pop()- 出队操作**:- 如果
outStack为空,则将inStack中的所有元素依次弹出并压入outStack。此操作将元素的顺序反转,使得最先进入inStack的元素位于outStack的栈顶。 - 弹出并返回
outStack的栈顶元素。 - 时间复杂度:均摊 O(1)。最坏情况下(当
outStack为空时)一次操作是 O(n),但每个元素最多只会从inStack转移到outStack一次,因此多次pop操作的平均时间复杂度是很低的。
- 如果
-
**
peek()- 获取队首元素**:- 逻辑与
pop操作类似。如果outStack为空,则需要先将inStack的所有元素转移到outStack。 - 然后返回
outStack的栈顶元素(但不弹出)。 - 时间复杂度:均摊 O(1)。
- 逻辑与
-
**
empty()- 检查队列是否为空**:- 检查
inStack和outStack是否都为空。 - 时间复杂度:O(1)。
- 检查
提示:双栈法是实现队列的高效方法。关键在于惰性转移元素——只有在 outStack 为空且需要执行 pop 或 peek 操作时,才将 inStack 的元素倒入 outStack。这样确保了每个元素只被转移一次,使得 pop 和 peek 操作的均摊时间复杂度为 O(1)
。
三、关键总结与注意事项
- 思维转换:这类问题的核心在于理解如何利用基本数据结构(队列的FIFO或栈的LIFO)并通过组合操作来模拟另一种数据结构的特性。队列实现栈的关键是反转元素的顺序;栈实现队列的关键也是反转元素的顺序(通过第二个栈)。
- 时间复杂度分析:关注均摊时间复杂度(Amortized Time Complexity),特别是在用栈实现队列的
pop操作中。不要因为一次昂贵的操作而认为整个算法效率低下。 - 实际应用:这些问题主要是为了考察对数据结构的理解,其教学意义大于工程实践意义。在实际项目开发中,应直接使用编程语言提供的标准栈或队列实现。
- 代码实现注意:
- 务必注意边界条件的判断,例如在
pop或top/peek操作前检查栈或队列是否为空。 - 在用队列实现栈时,如果使用双队列法,注意在交换队列引用时不要出错。
- 在用栈实现队列时,确保只有在
outStack为空时才进行转移操作,避免不必要的操作。
- 务必注意边界条件的判断,例如在
更多推荐

所有评论(0)