欢迎来到 s a y − f a l l 的文章 欢迎来到say-fall的文章 欢迎来到say−fall的文章

🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言 🔥专栏: 《C语言从零开始到精通》 《C语言编程实战》 《数据结构与算法》 《小游戏与项目》 💪格言:做好你自己,你才能吸引更多人,并与他们共赢,这才是你最好的成长方式。
文章目录
- 题目:用队列实现栈
-
- 一、题目核心要求
- 二、思路解析
-
- [2.1 核心原理:队列与栈的特性差异](#2.1 核心原理:队列与栈的特性差异)
- [2.2 具体实现思路](#2.2 具体实现思路)
- [2.3 关键细节说明](#2.3 关键细节说明)
- 三、完整代码实现
- 四、常见问题与避坑点
题目:用队列实现栈
一、题目核心要求
实现一个栈的类 MyStack,要求仅使用两个队列来模拟栈的所有操作,且满足栈"后进先出(LIFO)"的核心特性。栈需要支持以下操作:
myStackCreate():创建并初始化栈;myStackPush(int x):将元素 x 压入栈顶;myStackPop():移除并返回栈顶元素;myStackTop():返回栈顶元素(不移除);myStackEmpty():判断栈是否为空;myStackFree():销毁栈,释放所有内存。
二、思路解析
2.1 核心原理:队列与栈的特性差异
队列的核心是"先进先出(FIFO)",即先插入的元素先出队;而栈的核心是"后进先出(LIFO)",后插入的元素先出栈。要想用队列模拟栈,关键是通过两个队列的"数据转移" 实现栈的出栈逻辑。
2.2 具体实现思路
我们定义两个队列 q1 和 q2,其中一个作为"主队列"存储数据,另一个作为"辅助队列"临时存放数据,核心操作逻辑如下:
- 入栈(Push) :始终将新元素插入到非空队列 的队尾(若两个队列都为空,默认插入
q1)。这样能保证新元素始终在某一个队列的队尾,而队尾恰好对应栈的栈顶。 - 出栈(Pop) :假设数据存储在
q1中,我们将q1中前n-1个元素依次出队并插入到空队列q2中,此时q1中仅剩最后一个元素(即栈顶元素),将其出队并返回;之后q2变为新的主队列,q1变为辅助队列。 - 查栈顶(Top):直接返回非空队列的队尾元素(因为入栈时新元素始终插入队尾,队尾即为栈顶)。
- 判空(Empty):当且仅当两个队列都为空时,栈为空。
可视化示例:
我们在
q1队列中按顺序插入 1、2、3、4,此时q1的元素顺序为 [1,2,3,4],单纯出队只能按 1→2→3→4 的顺序:
若想让 4(栈顶)先出栈,我们将
q1中前 3 个元素(1、2、3)依次出队并插入到q2中,此时q1仅剩 4,直接出队即可实现"栈顶出栈";后续q2变为主队列,q1变为辅助队列:
注意:栈的总元素个数size是q1.size + q2.size,因为任意时刻两个队列中只有一个非空(或都为空),因此size也等价于非空队列的元素个数。
2.3 关键细节说明
- 入栈时无需区分"空/非空"队列的严格判断,只需保证新元素插入到有数据的队列即可,这样能避免数据分散在两个队列中;
- 出栈时的数据转移是核心,通过转移前
n-1个元素,将队列的"队尾"暴露出来,实现栈顶的弹出; - 所有操作需严格维护队列的
size,栈的总size需实时更新为两个队列的size之和,保证判空、计数逻辑准确。
三、完整代码实现
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
// 队列节点数据类型
typedef int QDatatype;
// 队列节点结构体
typedef struct QNode
{
struct QNode* next; // 指向下一个节点的指针
QDatatype val; // 节点存储的数据
}QNode;
// 队列管理结构体(封装头/尾指针+元素个数)
typedef struct Queue
{
QNode* phead; // 队头指针
QNode* ptail; // 队尾指针
int size; // 队列元素个数
}Queue;
// ===================== 队列核心操作声明 =====================
// 初始化与销毁
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
// 尾插(入队)与头删(出队)
void QueuePush(Queue* pq,QDatatype x);
void QueuePop(Queue* pq);
// 队列元素个数与判空
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
// 查看队头、队尾元素
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);
// ===================== 队列核心操作实现 =====================
// 初始化队列:置空头尾指针,元素个数为0
void QueueInit(Queue* pq)
{
assert(pq); // 确保传入的队列指针有效
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
// 销毁队列:释放所有节点内存,重置队列状态
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* pcur = pq->phead;
while (pcur)
{
QNode* pnext = pcur->next; // 先保存下一个节点,避免free后丢失
free(pcur); // 释放当前节点
pcur = pnext; // 移动到下一个节点
}
// 重置队列,避免野指针
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
// 队尾插入元素(入队)
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
// 创建新节点并初始化
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL) // 校验内存分配是否成功
{
perror("malloc fail");
return;
}
newnode->next = NULL;
newnode->val = x;
// 空队列:头尾指针都指向新节点
if (pq->ptail == pq->phead && pq->ptail == NULL)
{
pq->ptail = pq->phead = newnode;
}
else // 非空队列:尾节点next指向新节点,更新尾指针
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++; // 队列元素个数+1
}
// 队头删除元素(出队)
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->phead); // 确保队列非空
// 只有一个节点:释放后重置头尾指针
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else // 多个节点:保存下一个节点,释放头节点并更新头指针
{
QNode* pnext = pq->phead->next;
free(pq->phead);
pq->phead = pnext;
}
pq->size--; // 队列元素个数-1
}
// 获取队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
// 判断队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
// 获取队头元素(不删除)
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead); // 确保队列非空
return pq->phead->val;
}
// 获取队尾元素(不删除)
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail); // 确保队列非空
return pq->ptail->val;
}
// ===================== 栈的实现(基于两个队列) =====================
typedef struct
{
Queue* q1; // 队列1(主队列/辅助队列)
Queue* q2; // 队列2(辅助队列/主队列)
int size; // 栈的总元素个数(q1.size + q2.size)
} MyStack;
// 创建并初始化栈
MyStack* myStackCreate()
{
// 为栈结构体分配内存
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
if (obj == NULL) // 校验内存分配
{
perror("malloc MyStack fail");
return NULL;
}
// 为两个队列分配内存并初始化
obj->q1 = (Queue*)malloc(sizeof(Queue));
obj->q2 = (Queue*)malloc(sizeof(Queue));
QueueInit(obj->q1);
QueueInit(obj->q2);
// 初始化栈的总元素个数
obj->size = obj->q1->size + obj->q2->size;
return obj;
}
// 入栈:将元素插入非空队列的队尾
void myStackPush(MyStack* obj, int x)
{
assert(obj); // 确保栈指针有效
// 假设法:先假设q1为空,q2为非空
Queue* empty = obj->q1;
Queue* nonempty = obj->q2;
// 若q1非空,则交换空/非空队列的指向
if(!QueueEmpty(obj->q1))
{
empty = obj->q2;
nonempty = obj->q1;
}
// 将元素插入非空队列的队尾
QueuePush(nonempty, x);
// 更新栈的总元素个数
obj->size = obj->q1->size + obj->q2->size;
}
// 出栈:转移非空队列前n-1个元素,弹出最后一个元素(栈顶)
int myStackPop(MyStack* obj)
{
assert(obj); // 确保栈指针有效
// 假设法:确定空队列和非空队列
Queue* empty = obj->q1;
Queue* nonempty = obj->q2;
if(!QueueEmpty(obj->q1))
{
empty = obj->q2;
nonempty = obj->q1;
}
// 将非空队列前n-1个元素转移到空队列
while(nonempty->size - 1) // 循环条件:剩余元素个数>1
{
QueuePush(empty, QueueFront(nonempty)); // 非空队列队头插入空队列
QueuePop(nonempty); // 弹出非空队列的队头元素
}
// 取出并弹出栈顶元素(非空队列仅剩的最后一个元素)
int top = QueueBack(nonempty);
QueuePop(nonempty);
// 更新栈的总元素个数
obj->size = obj->q1->size + obj->q2->size;
return top;
}
// 获取栈顶元素:返回非空队列的队尾元素
int myStackTop(MyStack* obj)
{
assert(obj); // 确保栈指针有效
if(!QueueEmpty(obj->q1))
{
return QueueBack(obj->q1);
}
else
{
return QueueBack(obj->q2);
}
}
// 判断栈是否为空:两个队列都为空则栈空
bool myStackEmpty(MyStack* obj)
{
assert(obj); // 确保栈指针有效
return (QueueEmpty(obj->q1) && QueueEmpty(obj->q2));
}
// 销毁栈:释放队列和栈的所有内存
void myStackFree(MyStack* obj)
{
assert(obj); // 确保栈指针有效
// 销毁两个队列的节点内存
QueueDestroy(obj->q1);
QueueDestroy(obj->q2);
// 释放队列和栈结构体的内存
free(obj->q1);
free(obj->q2);
free(obj);
}
// ===================== 测试用例(可选) =====================
/*
int main()
{
// 创建栈
MyStack* stack = myStackCreate();
// 入栈操作
myStackPush(stack, 1);
myStackPush(stack, 2);
myStackPush(stack, 3);
// 查看栈顶元素(预期:3)
printf("栈顶元素:%d\n", myStackTop(stack));
// 出栈操作(预期:3)
printf("出栈元素:%d\n", myStackPop(stack));
// 再次查看栈顶(预期:2)
printf("栈顶元素:%d\n", myStackTop(stack));
// 判断栈是否为空(预期:false)
printf("栈是否为空:%s\n", myStackEmpty(stack) ? "是" : "否");
// 清空栈
myStackPop(stack);
myStackPop(stack);
printf("清空后栈是否为空:%s\n", myStackEmpty(stack) ? "是" : "否");
// 销毁栈
myStackFree(stack);
return 0;
}
*/
四、常见问题与避坑点
- 野指针问题 :
myStackCreate中若未为q1/q2分配内存,直接调用QueueInit(obj->q1)会触发"非法内存访问"错误; - size 维护错误 :栈的
size需实时更新为q1.size + q2.size,若仅初始化时赋值一次,会导致判空、计数逻辑错误; - 内存泄漏问题 :
myStackFree中需依次释放队列节点、队列结构体、栈结构体,遗漏任意一步都会导致内存泄漏; - 出栈循环条件错误 :
myStackPop中循环条件需为nonempty->size - 1(剩余元素>1),若写成nonempty->size会导致所有元素被转移,无法弹出栈顶。
- 本节完...

