题目的所有代码在文章末尾处
用队列实现栈的进阶版--详情见上篇博客--先学习上篇才能更好理解哦
a.用队列实现栈讲解链接
实际上,我觉得 栈实现队列 比用 队列实现栈 的题好做一点,可能是因为先做了 队列实现栈 思路更清晰了,接下来让我们一起来做用栈实现队列这道oj题吧。
b.题目链接
用栈实现队列https://leetcode.cn/problems/implement-queue-using-stacks/description/
c.题目
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
push
、pop
、peek
、empty
):实现
MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。- 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
题目所给的代码:
typedef struct {
} MyQueue;
MyQueue* myQueueCreate() {
}
void myQueuePush(MyQueue* obj, int x) {
}
int myQueuePop(MyQueue* obj) {
}
int myQueuePeek(MyQueue* obj) {
}
bool myQueueEmpty(MyQueue* obj) {
}
一、题意解析
a. 需要明白几点:
1.队列是什么?
2.栈是什么?
3.它们之间的区别是什么?
具体可见博客
栈和队列的概念和实现https://blog.csdn.net/2303_77756141/article/details/140506549?app_version=6.3.9&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22140506549%22%2C%22source%22%3A%222303_77756141%22%7D&utm_source=app通过清楚他们的概念与各种区别,现在我们可以开始做题了
b.思路
队列是先进先出,而栈是先进后出,要想用队列实现栈肯定需要将队的顺序进行一个调转。
队列删除是删除的队列最先中最早进去的元素,队列插入是从队尾入队;
栈删除是删除的是栈中最后进去的那个元素,即栈的栈顶元素,插入是从栈顶插入。
示例 1:
输入: ["MyQueue", "push", "push", "peek", "pop", "empty"] [[], [1], [2], [], [], []] 输出: [null, null, null, 1, 1, false] 解释: MyQueue myQueue = new MyQueue(); myQueue.push(1); // queue is: [1] myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) myQueue.peek(); // return 1 myQueue.pop(); // return 1, queue is [2] myQueue.empty(); // return false
*核心思想:
一个栈用于存数据,一个栈用于出数据,用于出数据的栈,直到栈空,再让新的元素进栈。
1.入队
如图,此时栈2所存元素的顺序,便是队列存好元素的顺序。
2.出队
如图,此时栈2的元素所存的元素仍然是队列所存元素的顺序。因此在此时出队1,2,实际上就是在栈2中将 1,2 依次pop掉
3.出栈后元素需要再倒回去吗?
请认真思考这个问题。可以自行画图看看。
1.把栈2中元素倒回栈1
让我们先画一画把栈2中元素倒回栈1后怎样怎样又获得新的队列
如图,需要经过a,b,c三次变换,才能再次得到新的队列从而能取到队头元素。
2.直接在栈1中添加元素
让我们先画一画直接在栈1中添加元素会怎么样
如图,此时若想pop队列中的元素,直接pop栈2的栈顶元素便可以了。而在 1 中需要经过a,b,c三个过程,何不将栈2中的元素pop完之后再装入栈1中的元素 6,7,8 呢,再去看 1 中是不是也是一样,将 3,4,5 pop掉之后,栈2中只剩6,7,8这样至顶而下的排列,因此我们得到这样的思路:将栈2中的元素全pop完之后,再将栈1中的元素push进栈2,即,用完再添。此时我们再去看核心思想是不是就清晰多了。
二、每个函数实现的具体讲解
1. 定义两个用于分别实现进队出队的结构体
也就是两个栈
typedef struct {
ST pushst;
ST popst;} MyQueue;
1. 注意这里定义的是结构体,而不是结构体的指针,结构体的指针是地址
2. 如果用的是结构体指针,在(MyQueue*)malloc....的时候,没有初始化这两个指针就是野指针,初始化过后就是空指针。
3. 而在初始化的时候,要求传入的参数是一个实际的指针
a. 空指针断言直接报错,
b. 野指针在pst->a,capacity,top时报错,因为野指针指向一个不属于它的空间
4.如果非要使用结构体指针, 还需在myQueueCreate()中进行两次 malloc ST 的结构变量, 将这两个结构体指针的空间给开辟出来,再用于初始化
5. 指针是一个地址
2. 用栈实现初始化队列
MyQueue* myQueueCreate() {
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&obj->pushst);}
3. 用栈实现从队尾入队
void myQueuePush(MyQueue* obj, int x) {
STPush(&obj->pushst, x);
}也就是让元素进入存元素的栈
5. 用栈实现 返回队列开头的元素,与pop是有区别的
int myQueuePeek(MyQueue* obj) {
if (STEmpty(&obj->popst))
{
while (!STEmpty(&obj->pushst))//当pushst中的值全都进到popst循环停止
//即pushst中为空
{
STPush(&obj->popst, STTop(&obj->pushst));
STPop(&obj->pushst);
}
}
return STTop(&obj->popst);
}1. 看数据但是不删除,
2. 就是将队列的头取出来,即最早进入的那个数据
3. 也就是取栈的尾部,因此又要将数据放入popst
4. 用栈实现队头元素出队
int myQueuePop(MyQueue* obj) {
int front = myQueuePeek(obj);//存该元素
STPop(&obj->popst);
return front;
}
6.用栈实现队列的判空
bool myQueueEmpty(MyQueue* obj) {
return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}
7.用栈实现队列的销毁
void myQueueFree(MyQueue* obj) {
STDestroy(&obj->pushst);
STDestroy(&obj->popst);
free(obj);
obj = NULL;
}注意:不要忘记先销毁开辟出来的两个栈,因为开辟出来的两个栈一直有空间
8.测试用例
这里我写了一个测试用例,方便大家用来调试检测代码中的错误。
int main()
{
MyQueue* obj = myQueueCreate();
myQueuePush(obj,1);
myQueuePush(obj,2);
printf("%d ", myQueuePeek(obj));printf("%d ", myQueuePop(obj));
myQueueEmpty(obj);
return 0;}
三、完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
pst->top = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
void STPush(ST* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
typedef struct {
ST pushst;
ST popst;
}MyQueue;
MyQueue* myQueueCreate() {
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&obj->pushst);
STInit(&obj->popst);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
STPush(&obj->pushst, x);
}
int myQueuePop(MyQueue* obj) {
int front = myQueuePeek(obj);
STPop(&obj->popst);
return front;
}
int myQueuePeek(MyQueue* obj) {
if (STEmpty(&obj->popst))
{
while (!STEmpty(&obj->pushst))
{
STPush(&obj->popst, STTop(&obj->pushst));
STPop(&obj->pushst);
}
}
return STTop(&obj->popst);
}
bool myQueueEmpty(MyQueue* obj) {
return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj) {
STDestroy(&obj->pushst);
STDestroy(&obj->popst);
free(obj);
obj = NULL;
}
结语:
在探索这道题的解答过程中,我深刻体会到知识的海洋浩瀚无垠,每一次解题都是一次自我挑战与成长的宝贵机会。虽然我已经尽力将解题思路和步骤清晰地呈现出来,但深知学无止境,仍有诸多改进和优化的空间。如果您在阅读过程中发现了更好的解法或是有任何疑问和建议,都非常欢迎您在评论区留言,让我们共同探讨,共同进步。
最后,如果您觉得这篇博客对您有所帮助,或是受到了些许启发,不妨动动手指,给予一个小小的鼓励——点个赞、收藏起来以便日后回顾,甚至分享给更多需要帮助的朋友。您的每一次互动,都是对我最大的支持与鼓励,也是我继续创作更多优质内容的动力源泉。
再次感谢您的阅读,期待在未来的解题之旅中,与您再次相遇,共同解锁更多知识的奥秘!