引入:
在算法和数据结构领域,栈和队列是两种基础且重要的数据结构。栈遵循后进先出(LIFO)原则,而队列遵循先进先出(FIFO)原则。有时候,我们会遇到一个有趣的问题:如何使用两个队列来实现栈的操作?本文将详细解释这个问题的解决方案,并对每一个栈操作函数进行深入讲解。
练习题:
1. 力扣 225. 用二个队列实现栈

问题背景
栈是一种只能在一端进行插入和删除操作的数据结构,这一端被称为栈顶。而队列则是在一端插入元素(队尾),在另一端删除元素(队头)。现在,我们要使用两个队列来模拟栈的行为,实现栈的基本操作:入栈(push
)、出栈(pop
)、获取栈顶元素(top
)以及判断栈是否为空(empty
)。
数据结构定义
在开始实现栈操作之前,我们需要定义队列和栈的数据结构。以下是具体的代码:
typedef int QDataType;
typedef struct QListNode {
struct QListNode* _next;
QDataType _data;
} QListNode;
typedef struct Queue {
struct QListNode* _front;
struct QListNode* _rear;
} Queue;
typedef struct {
Queue q1;
Queue q2;
} MyStack;
QListNode
是队列的节点结构,包含一个指向下一个节点的指针_next
和存储的数据_data
。Queue
结构体包含两个指针,_front
指向队列的头节点,_rear
指向队列的尾节点。MyStack
结构体包含两个队列q1
和q2
,我们将通过这两个队列来模拟栈的操作。
队列基础操作(详细队列讲解)
在实现栈操作之前,我们需要先实现队列的一些基础操作,如初始化、入队、出队、判断队列是否为空等。以下是这些基础操作的代码:
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->_front = q->_rear = NULL;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data) {
assert(q);
QListNode* newnode = (QListNode*)malloc(sizeof(QListNode));
if (newnode == NULL) {
perror("Fail malloc");
exit(1);
}
newnode->_data = data;
newnode->_next = NULL;
if (q->_front == NULL) {
q->_front = q->_rear = newnode;
}
else {
q->_rear->_next = newnode;
q->_rear = newnode;
}
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
return q->_front == NULL;
}
// 队头出队列
void QueuePop(Queue* q) {
assert(!QueueEmpty(q));
if (q->_front == q->_rear) {
free(q->_front);
q->_front = q->_rear = NULL;
}
else {
QListNode* next = q->_front->_next;
free(q->_front);
q->_front = next;
}
}
// 获取队列头部元素
QDataType QueueFront(Queue* q) {
assert(!QueueEmpty(q));
return q->_front->_data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q) {
assert(!QueueEmpty(q));
return q->_rear->_data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(!QueueEmpty(q));
QListNode* pcur = q->_front;
int size = 0;
while (pcur) {
pcur = pcur->_next;
size++;
}
return size;
}
// 销毁队列
void QueueDestroy(Queue* q) {
assert(q);
QListNode* pcur = q->_front;
while (pcur) {
QListNode* next = pcur->_next;
free(pcur);
pcur = next;
}
q->_front = q->_rear = NULL;
}
这些基础操作是实现栈操作的基础,我们将在后续的栈操作中使用它们。
栈操作实现
1. myStackCreate
:创建栈
MyStack* myStackCreate() {
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
- 功能:创建一个新的栈实例。
- 步骤 :
- 使用
malloc
函数为MyStack
结构体分配内存。 - 调用
QueueInit
函数分别初始化q1
和q2
两个队列。 - 返回指向新创建的栈实例的指针。
- 使用
2. myStackPush
:入栈操作
void myStackPush(MyStack* obj, int x) {
if (!QueueEmpty(&obj->q1)) {
QueuePush(&obj->q1, x);
} else {
QueuePush(&obj->q2, x);
}
}
- 功能 :将元素
x
压入栈中。 - 步骤 :
- 检查
q1
是否为空。 - 如果
q1
不为空,则将元素x
插入到q1
队列的队尾。 - 如果
q1
为空,则将元素x
插入到q2
队列的队尾。
- 检查
3. myStackPop
:出栈操作
int myStackPop(MyStack* obj) {
Queue* emp = &obj->q1;
Queue* noneEmp = &obj->q2;
if (QueueEmpty(&obj->q2)) {
emp = &obj->q2;
noneEmp = &obj->q1;
}
while (QueueSize(noneEmp) > 1) {
QueuePush(emp, QueueFront(noneEmp));
QueuePop(noneEmp);
}
int top = QueueFront(noneEmp);
QueuePop(noneEmp);
return top;
}


- 功能:弹出栈顶元素并返回其值。
- 步骤 :
- 确定哪个队列为空(
emp
),哪个队列不为空(noneEmp
)。 - 将
noneEmp
队列中除最后一个元素外的所有元素依次转移到emp
队列中。 - 保存
noneEmp
队列中最后一个元素的值。 - 弹出
noneEmp
队列中的最后一个元素。 - 返回保存的元素值。
- 确定哪个队列为空(
4. myStackTop
:获取栈顶元素
int myStackTop(MyStack* obj) {
if (!QueueEmpty(&obj->q1)) {
return QueueBack(&obj->q1);
} else {
return QueueBack(&obj->q2);
}
}
- 功能:返回栈顶元素的值,但不弹出该元素。
- 步骤 :
- 检查
q1
是否为空。 - 如果
q1
不为空,则返回q1
队列的队尾元素的值。 - 如果
q1
为空,则返回q2
队列的队尾元素的值。
- 检查
5. myStackEmpty
:判断栈是否为空
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
- 功能:判断栈是否为空。
- 步骤 :
- 检查
q1
和q2
两个队列是否都为空。 - 如果两个队列都为空,则返回
true
,表示栈为空;否则返回false
。
- 检查
6. myStackFree
:释放栈内存
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
obj = NULL;
}
- 功能:释放栈实例占用的内存。
- 步骤 :
- 调用
QueueDestroy
函数分别销毁q1
和q2
两个队列。 - 使用
free
函数释放MyStack
结构体占用的内存。
- 调用
总结
通过使用两个队列,我们成功地模拟了栈的基本操作。这种实现方式虽然在时间复杂度上可能不如直接使用栈高效,但它展示了如何通过组合不同的数据结构来实现特定的功能。在实际应用中,我们可以根据具体的需求选择合适的数据结构和算法。
希望本文能够帮助你理解如何使用两个队列来实现栈操作,以及每个函数的具体实现原理。如果你有任何疑问或建议,欢迎在评论区留言。