目录
解释QueueInit(&pst->q1);的参数为什么这样写
1.题目
https://leetcode.cn/problems/implement-stack-using-queues/description/
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(
push
、top
、pop
和empty
)。实现
MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。注意:
- 你只能使用队列的标准操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。- 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入: ["MyStack", "push", "push", "top", "pop", "empty"] [[], [1], [2], [], [], []] 输出: [null, null, null, 2, 2, false] 解释: MyStack myStack = new MyStack(); myStack.push(1); myStack.push(2); myStack.top(); // 返回 2 myStack.pop(); // 返回 2 myStack.empty(); // 返回 False提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、top
和empty
- 每次调用
pop
和top
都保证栈不为空进阶:你能否仅用一个队列来实现栈。
代码模板
typedef struct {
} MyStack;
MyStack* myStackCreate() {
}
void myStackPush(MyStack* obj, int x) {
}
int myStackPop(MyStack* obj) {
}
int myStackTop(MyStack* obj) {
}
bool myStackEmpty(MyStack* obj) {
}
void myStackFree(MyStack* 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);
*/
2.分析
知识回顾
分析
详解队列的push和pop
读题可知:要将两个队列视为栈使用,要满足栈的性质
例如先后push 1,2,3,4,可画图为,注:由队列的性质只能在队尾插入
当要pop时,要先让4出队,则需要让原先的队列做出改变,使4在队头,能最先出队,这样第二个队列(空队列)就能派上用场了
做法:将1,2,3先临时保存在空队列中,(注意待4被pop后,这里不用将1,2,3再恢复回原来的队列,只要满足一个为空队列,另一个为非空队列即可),最后size--
画图为:
由于之前在文章中写过队列的增删查改,因此这里直接借用过来,合理接入LeetCode给的函数中
因此出栈总结为
1.保持一个队列为空,另外一个队列存储数据
2.先将前面的数据临时存储至空队列,再出栈
代码逐步实现
1.结构体的写法
注意到LeetCode提供的结构体有点特别,是匿名结构体
typedef struct {
} MyStack;
由于要实现两个队列,因此要将它们写入匿名结构体中,将两个队列整体视作栈
因此写为
//直接借用98.【C语言】数据结构之队列文章的代码
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//接入LeetCode的代码
typedef struct
{
Queue q1;
Queue q2;
}MyStack;
2.创建栈myStackCreate函数
注意到LeetCode提供的myStackCreate函数没有参数,返回值为结构体指针变量,因此就要在函数中手动开辟空间并初始化创建结构体
常见的错误写法
MyStack* myStackCreate()
{
MyStack st;
return &st;
}
函数返回时,栈区的局部变量会发生销毁,导致出现野指针,因此需要在堆区上开辟空间,可以使用malloc函数
正确写法
在myStackCreate嵌入QueueInit函数
MyStack* myStackCreate()
{
MyStack* pst=(MyStack*)malloc(sizeof(MyStack));
if (pst==NULL)
{
perror("malloc");
return NULL;
}
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
解释QueueInit(&pst->q1);的参数为什么这样写
1.不能写成QueueInit(MyStack->q1);或者QueueInit(&MyStack->q1);MyStack不是指针,为结构体
2.不能写成QueueInit(pst->q1);
pst->q1访问的是结构体成员变量,不是指针类型,因此不行
3.能不能写成QueueInit(&(pst->q1));?这和QueueInit(&pst->q1);有区别吗?
没有区别,->优先级高于&
4.特别提醒 !!!
不要将QueueInit(&pst->q1);看成QueueInit( (&pst) ->q1);!!!
3.入栈函数myStackPush
直接向非空队列尾插,如果都是非空,随便选一个尾插
这里需要借助之前写过的空队列检查函数QueueEmpty
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
void myStackPush(MyStack* obj, int x)
{
if (!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
4.出栈函数myStackPop
算法分析:入栈后,两个队列为空和非空,要先找到空和非空队列,再执行出栈(移除并返回top值)
麻烦的写法
//如果q1为非空队列,执行......,否则执行......
if (!QueueEmpty(&obj->q1))
{
//......
}
else
{
//......
}
这样会有重复的代码,不够简洁
优化策略:先假定q1为空队列,q2为非空队列,再检验假设,如果不正确就调整,代码写为
int myStackPop(MyStack* obj)
{
Queue* empty=&obj->q1;
Queue* noempty=&obj->q2;
if (!QueueEmpty(&obj->q1))
{
empty=&obj->q2;
noempty=&obj->q1;
}
//......
}
移除队列分析
设非空队列有多个节点.其节点个数为size,则要将前size-1个节点临时存储到空队列,显然是个循环,则循环可执行的条件为QueueSize(noempty)>1
循环的内容为:先QueuePush后QueuePop
QueuePush(empty,QueueFront(noempty));
QueuePop(noempty);
再将仅剩的节点pop(注意要先保存节点的值,以备返回)
因此代码为
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
int myStackPop(MyStack* obj)
{
Queue* empty=&obj->q1;
Queue* noempty=&obj->q2;
if (!QueueEmpty(&obj->q1))
{
empty=&obj->q2;
noempty=&obj->q1;
}
while(QueueSize(noempty)>1)
{
QueuePush(empty,QueueFront(noempty));
QueuePop(noempty);
}
int top=QueueFront(noempty);
QueuePop(noempty);
return top;
}
5.栈顶函数myStackTop
不需要挪动元素,直接调用QueueBack函数即可,返回非空(这里需要做判断)队列的栈顶元素(是队尾)
int myStackTop(MyStack* obj)
{
if (!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
6.栈空判断函数myStackEmpty
栈为空表明两个队列都为空,直接调用QueueEmpty函数即可
bool myStackEmpty(MyStack* obj)
{
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
7.栈的内存释放函数myStackFree
注意free的顺序,这里很容易出错!!!,画结构体图会很容易分析
三个结构体之间的关系图
QueueNode、Queue和匿名结构体(这里假设q1为非空队列,q2为空队列)
按什么顺序释放内存?
不能先释放队列,否则会形成野指针
正确顺序:模拟队列的链表(&obj->q1和&obj->q2,可以直接借用之前写过的QueueDestroy函数)-->匿名结构体(obj)
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
3.整合的代码
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
typedef struct
{
Queue q1;
Queue q2;
}MyStack;
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
MyStack* myStackCreate()
{
MyStack* pst=(MyStack*)malloc(sizeof(MyStack));
if (pst==NULL)
{
perror("malloc");
return NULL;
}
QueueInit(&pst->q1);
QueueInit(&pst->q2);
return pst;
}
void myStackPush(MyStack* obj, int x)
{
if (!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
pq->tail = NULL;
pq->size--;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int myStackPop(MyStack* obj)
{
Queue* empty=&obj->q1;
Queue* noempty=&obj->q2;
if (!QueueEmpty(&obj->q1))
{
empty=&obj->q2;
noempty=&obj->q1;
}
while(QueueSize(noempty)>1)
{
QueuePush(empty,QueueFront(noempty));
QueuePop(noempty);
}
int top=QueueFront(noempty);
QueuePop(noempty);
return top;
}
int myStackTop(MyStack* obj)
{
if (!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj)
{
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj)
{
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}