C语言编程实战:每日一题:用队列实现栈

王者杯·14天创作挑战营·第8期 10w+人浏览 259人参与

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

在这里插入图片描述


🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
🔥专栏: 《C语言从零开始到精通》 《C语言编程实战》 《数据结构与算法》 《小游戏与项目》
💪格言:做好你自己,你才能吸引更多人,并与他们共赢,这才是你最好的成长方式。



题目:用队列实现栈

一、题目核心要求

实现一个栈的类 MyStack,要求仅使用两个队列来模拟栈的所有操作,且满足栈“后进先出(LIFO)”的核心特性。栈需要支持以下操作:

  • myStackCreate():创建并初始化栈;
  • myStackPush(int x):将元素 x 压入栈顶;
  • myStackPop():移除并返回栈顶元素;
  • myStackTop():返回栈顶元素(不移除);
  • myStackEmpty():判断栈是否为空;
  • myStackFree():销毁栈,释放所有内存。

二、思路解析

2.1 核心原理:队列与栈的特性差异

队列的核心是“先进先出(FIFO)”,即先插入的元素先出队;而栈的核心是“后进先出(LIFO)”,后插入的元素先出栈。要想用队列模拟栈,关键是通过两个队列的“数据转移” 实现栈的出栈逻辑。

2.2 具体实现思路

我们定义两个队列 q1q2,其中一个作为“主队列”存储数据,另一个作为“辅助队列”临时存放数据,核心操作逻辑如下:

  1. 入栈(Push):始终将新元素插入到非空队列的队尾(若两个队列都为空,默认插入 q1)。这样能保证新元素始终在某一个队列的队尾,而队尾恰好对应栈的栈顶。
  2. 出栈(Pop):假设数据存储在 q1 中,我们将 q1 中前 n-1 个元素依次出队并插入到空队列 q2 中,此时 q1 中仅剩最后一个元素(即栈顶元素),将其出队并返回;之后 q2 变为新的主队列,q1 变为辅助队列。
  3. 查栈顶(Top):直接返回非空队列的队尾元素(因为入栈时新元素始终插入队尾,队尾即为栈顶)。
  4. 判空(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 变为辅助队列:
在这里插入图片描述

注意:栈的总元素个数 sizeq1.size + q2.size,因为任意时刻两个队列中只有一个非空(或都为空),因此 size 也等价于非空队列的元素个数。

2.3 关键细节说明

  • 入栈时无需区分“空/非空”队列的严格判断,只需保证新元素插入到有数据的队列即可,这样能避免数据分散在两个队列中;
  • 出栈时的数据转移是核心,通过转移前 n-1 个元素,将队列的“队尾”暴露出来,实现栈顶的弹出;
  • 所有操作需严格维护队列的 size,栈的总 size 需实时更新为两个队列的 size 之和,保证判空、计数逻辑准确。

三、完整代码实现

#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;
}
*/

四、常见问题与避坑点

  1. 野指针问题myStackCreate 中若未为 q1/q2 分配内存,直接调用 QueueInit(obj->q1) 会触发“非法内存访问”错误;
  2. size 维护错误:栈的 size 需实时更新为 q1.size + q2.size,若仅初始化时赋值一次,会导致判空、计数逻辑错误;
  3. 内存泄漏问题myStackFree 中需依次释放队列节点、队列结构体、栈结构体,遗漏任意一步都会导致内存泄漏;
  4. 出栈循环条件错误myStackPop 中循环条件需为 nonempty->size - 1(剩余元素>1),若写成 nonempty->size 会导致所有元素被转移,无法弹出栈顶。

  • 本节完…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值