225. 用队列实现栈 - 力扣(LeetCode)https://leetcode.cn/problems/implement-stack-using-queues/description/
实现逻辑
一个是先进先出(队列),一个是后进先出(栈)
这里用两个队列导入·一下数据
出来数据之后入数据
代码的实现
#include<stdio.h> #include<assert.h> #include<stdlib.h> typedef int QDataType; // 链式结构:表示队列 typedef struct QListNode { QDataType _data; struct QListNode* _next; }QNode; // 队列的结构 (因为队列是先进先出,后进后出,也就是和栈是相反的,此时会尾进头出,所以我们需要更新尾部和头部节点) // (把头结点,尾结点,链表的实际的大小放里面,这样不需要每次使用的时候进行循环找尾,我们只需要每次更新尾结点就可以) typedef struct Queue { QNode* _phead;//头节点 QNode* _ptail;//尾结点 int size; }Queue; // 这里采取一级指针进行实现代码逻辑,如果不创建队列的结构,我们就需要采取二级指针 // 初始化队列 void QueueInit(Queue* ps); // 销毁队列 void QueueDestroy(Queue* ps); // 队尾入队列 void QueuePush(Queue* ps, QDataType data); // 队头出队列 void QueuePop(Queue* ps); // 获取队列头部元素 QDataType QueueFront(Queue* ps); // 获取队列队尾元素 QDataType QueueBack(Queue* ps); // 获取队列中有效元素个数 int QueueSize(Queue* ps); // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* ps); // 初始化队列 void QueueInit(Queue* ps) { ps->_phead = NULL; ps->_ptail = NULL; ps->size = 0; } // 销毁队列 void QueueDestroy(Queue* ps) { assert(ps); QNode* cur = ps->_phead; while (cur) { //存下下一个节点的地址,不会出现找空的情况 QNode* next = cur->_next; free(cur); cur =next; } } // 队尾入队列 void QueuePush(Queue* ps, QDataType x) { QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("QueuePush:newnode:error:"); exit(1); } //把需要插入的数值插入到节点里面 newnode->_next = NULL; newnode->_data = x; // ps->_phead == ps->_ptail 的结果确实是 true,这表明队列中只有一个元素(头尾相接)。 // 但是,通常情况下,我们不会使用 ps->_phead == ps->_ptail // 来检查队列中是否只有一个元素。相反,我们通常会使用 ps->size == 1 或者 ps->_phead != NULL && ps->_ptail != NULL // 来检查队列中是否只有一个元素。 //插入节点 第一个节点,这里的判断是不能写成ps->_phead ==ps->_ptail;因为都是空指针不能进行比较 //ps->_phead == NULL if (ps->size == 0) { ps->_phead = ps->_ptail = newnode; } else { //尾插节点 ps->_ptail->_next = newnode; ps->_ptail= newnode; } ps->size++; } // 队头出队列 void QueuePop(Queue* ps) { assert(ps && ps->size); // 改变头结点 ps->_phead = ps->_phead->_next; ps->size--; } // 获取队列头部元素 QDataType QueueFront(Queue* ps) { assert(ps && ps->size); return ps->_phead->_data; } // 获取队列队尾元素 QDataType QueueBack(Queue* ps) { assert(ps && ps->size); return ps->_ptail->_data; } // 获取队列中有效元素个数 int QueueSize(Queue* ps) { return ps->size; } // 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* ps) { assert(ps); return ps->size == 0; } //匿名结构体(我们需要两个队列,所以采取结构体嵌套的形式解决问题) typedef struct { Queue Q1; Queue Q2; } MyStack; //初始化(关键在于,初始化的数值出去之后就销毁,所以我们需要创建节点进行初始化) MyStack* myStackCreate() { MyStack* obj = (MyStack*)malloc(sizeof(MyStack)); if(obj == NULL) { perror("obj:error:"); exit(1); } QueueInit(&(obj->Q1)); QueueInit(&(obj->Q2)); return obj; } //入栈 void myStackPush(MyStack* obj, int x) { if(QueueEmpty(&(obj->Q1)))//q1为空 { QueuePush(&(obj->Q2), x); } else { QueuePush(&(obj->Q1), x); } } //删除栈顶元素(核心逻辑) int myStackPop(MyStack* obj) { //判空(假设法假设q1为空) MyStack* Empty = &(obj->Q1); MyStack* noEmpty = &(obj->Q2); if(QueueEmpty(&(obj->Q2)))//如果q1不为空,q2为空,此时颠倒一下 { Empty = &(obj->Q2); noEmpty = &(obj->Q1); } //循环导入 while(QueueSize(noEmpty) > 1)//这里的关键点是你不知道谁是空,不是空 { QueuePush(Empty, QueueFront(noEmpty));//这里 是把不空的数值头部元素,循环导入到空的数值里面去 QueuePop(noEmpty);//并且进行出栈操作 } //结束之后留下了一个,我们保存下来返回这个节点,并且最后进行删除 int ret = QueueFront(noEmpty); QueuePop(noEmpty); return ret; } //取出栈顶元素 int myStackTop(MyStack* obj) { if(QueueEmpty(&(obj->Q1)))//q1为空 { return QueueBack(&(obj->Q2)); } else { return QueueBack(&(obj->Q1)); } } //判空 bool myStackEmpty(MyStack* obj) { //q1 q2都为空的时候才为空 return (QueueEmpty(&(obj->Q1)) && QueueEmpty(&(obj->Q2))); } //释放空间 void myStackFree(MyStack* obj) { QueueDestroy(&(obj->Q1)); QueueDestroy(&(obj->Q2)); free(obj); } /** * Your MyStack struct will be instantiated and called as such: * MyStack* obj = myStackCreate(); * myStackPush(obj, x); * int param_2 = myStackPop(obj); * int param_3 = myStackTop(obj); * bool param_4 = myStackEmpty(obj); * myStackFree(obj); */
这段代码使用两个队列
Q1
和Q2
来实现一个栈MyStack
。下面是对每个部分的详细解释:
数据结构定义:
QDataType
:定义了队列中存储的数据类型,这里使用int
。QNode
:定义了队列中的节点,包含数据_data
和指向下一个节点的指针_next
。Queue
:定义了队列结构,包含指向队列头部和尾部的指针_phead
和_ptail
,以及记录队列大小的size
。队列操作函数:
QueueInit
:初始化队列,将头尾指针设置为NULL
,大小设置为0。QueueDestroy
:释放队列中所有节点的内存。QueuePush
:在队列尾部添加一个新节点。QueuePop
:移除队列头部的节点,并减少队列大小。QueueFront
:返回队列头部的元素。QueueBack
:返回队列尾部的元素。QueueSize
:返回队列中元素的数量。QueueEmpty
:检查队列是否为空。栈操作函数:
myStackCreate
:创建一个新的MyStack
实例,初始化两个队列Q1
和Q2
。myStackPush
:如果Q1
为空,则将新元素推入Q2
;否则推入Q1
。myStackPop
:如果Q2
不为空且Q1
为空,则交换两个队列的角色。然后,将Q2
中的元素逐个移动到Q1
,除了最后一个元素。最后,返回并移除Q2
中的最后一个元素。myStackTop
:返回栈顶元素。如果Q1
不为空,返回Q1
的尾部元素;否则返回Q2
的尾部元素。myStackEmpty
:如果两个队列都为空,则返回true
,表示栈为空。myStackFree
:销毁MyStack
实例,释放所有相关联的内存。使用队列实现栈的逻辑:
- 通过两个队列
Q1
和Q2
的交替使用,实现了栈的后进先出(LIFO)特性。当一个队列为空时,可以将另一个队列中的元素移动过来,最后一个元素就是栈顶元素。myStackPush
函数根据Q1
是否为空来决定将新元素推入哪个队列,这样做的目的是保持两个队列中的元素数量大致相等,从而在myStackPop
操作中能够高效地将元素从一个队列移动到另一个队列。注意事项:
- 在
myStackPop
和myStackTop
函数中,需要检查两个队列的状态,以确定当前栈顶元素所在的队列。myStackDestroy
函数中,确保先销毁队列,再释放MyStack
实例的内存。这种使用两个队列实现栈的方法利用了队列的先进先出特性来模拟栈的后进先出特性。这种方法的好处是可以在 O(1) 时间内完成栈的基本操作,同时避免了使用数组实现栈时可能需要的数组扩展和收缩操作。