各位老友,欢迎造访本期博客!!
前段时间,我们已经学习了,怎样实现栈区,队列!
今天,正是我们将前段时间学习的知识,进行迁移!!
奥!银河啊!!这句“知识的迁移”勾起了,我的高中物理课···
OJ_01 :>
1.请仅使用两个队列实现一个后进先出的栈,并支持普通的栈的全部四种操作(push, pop, top, destroy)
请看以下代码
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//队列初始化
void QInitial(Queue* pq);
//销毁
void QDestroy(Queue* pq);
//入队列
void QPush(Queue* pq);
//出队列
void QPop(Queue* pq);
//队列头部元素
QDataType QFront(Queue* pq);
//队列尾部元素
QDataType QBack(Queue* pq);
//队列元素个数
QDataType QSize(Queue* pq);
//布尔判空
void QEmpty(Queue* pq);
//队列初始化
void QInitial(Queue* pq)
{
assert(pq);
QEmpty(pq);
pq ->head = pq ->tail = NULL;
pq ->size = 0;
}
//销毁
void QDestroy(Queue* pq)
{
assert(pq);
QEmpty(pq);
Queue* cur = pq ->head;
while(cur)
{
Queue* next = pq ->head ->next;
free(pq);
pq ->head = next;
}
pq ->head = NULL;
}
//入队列
void QPush(Queue* pq)
{
Queue* newnode = (Queue*)malloc(sizeof(Queue));
if(newnode == NULL)
{
perror("malloc::fail");
return ;
}
newnode ->data = x;
newnode ->next = NULL;
if(pq ->head = NULL)
{
assert(pq ->tail == NULL);
pq ->head = pq ->tail = NULL;
}
else
{
pq ->tail ->next = newnode;
pq ->tail = newnode;
}
pq ->size++;
}
//出队列
void QPop(Queue* pq)
{
assert(pq);
QEmpty(pq);
if(pq ->head ->next == NULL)
{
free(pq ->head);
pq ->head = NULL;
}
else
{
Queue* next = pq ->head ->next;
free(pq ->head);
pq ->head = next;
}
pq ->size--;
}
//队列头部元素
QDataType QFront(Queue* pq)
{
assert(pq);
QEmpty(pq);
return pq ->head ->data;
}
//队列尾部元素
QDataType QBack(Queue* pq)
{
assert(pq);
QEmpty(pq);
return pq ->tail ->data;
}
//队列元素个数
QDataType QSize(Queue* pq)
{
assert(pq);
return pq ->size;
}
//布尔判空
void QEmpty(Queue* pq)
{
assert(pq);
return pq ->size == 0;
}
//开始实现用队列实现栈
//匿名结构体
typedef struct
{
Queue q1;
Queue q2;
}MYStack;
//创建栈
MYStack* mystackCreate()
{
MyStack* pst = (MYStack*)malloc(sizeof(MYStack));
if(pst == NULL)
{
perror("malloc::fail");
return NULL;
}
QInitial(&pst ->q1);
QInitial(&pst ->q2);
return pst;
}
//入栈
void mystackPush(MYStack* obj, int x)
{
if(!QEmpty(&obj ->q1))
{
QPush(&obj ->q1, x);
}
else
{
QPush(&obj ->q2, x);
}
}
//出栈并且返回栈顶元素
int mystackPop(MYStack* obj)
{
MYStack* emptyQ = &obj ->q1;
MYStack* nonemptyQ = &obj ->q2;
if(QEmpty(&obj ->q1))
{
emptyQ = &obj ->q2;
nonemptyQ = &obj ->q1;
}
//倒数据--->这部分才是核心
while(QSzie(nonemptyQ) > 1)
{
QPush(emptyQ, QFront(nonemptyQ));
QPop(nonemptyQ);
}
int top = QFront(nonemptyQ);
QPop(nonemptyQ);
return top;
}
//栈顶元素
int mystackTop(MYStack* obj)
{
if(!QEmpty(&obj ->q1))
{
return QBack(&obj ->q1);
}
else
{
return QBack(&obj ->q2);
}
}
//销毁
void mystackFree(MYStack* obj)
{
QDestroy(&obj ->q1);
QDestroy(&obj ->q2);
free(obj);
}
至此,我们的代码已书写完毕!!为了各位好友,方便观看,特此附上带有色彩图样的代码,毕竟观感好啊!!😊
上述代码,是在OJ上运行的,已经通过!!感兴趣的好友,可以尝试一下!!
接下来,开始解析相关代码了,毕竟当时的理解,就有些挑战了!!
首先,在本次 OJ 题,用到了,嵌套结构体,其中匿名的部分,当时就掉进坑里了!
为了,方便解说,现在附上图示样解:
请注意看,q1, q2 是两个变量,究竟属于谁?
--->
显然,q1, q2 与 Queue 这个结构体关系最为密切!!而如今,我们的思路是 :
通过创建两个队列,来实现栈的特征!!这可是题目要求啊!!千万别混淆了!!
所以说,q1, q2 是作为结构体 Queue 的两个字队列,注意,千万别理解成,成员关系了!!
要是理解成 成员关系,那就需要指针的指向了,这个时候初始化以及具体实现的时候,需要用到二级指针了!
显然,最终,我传疯狂了!! 太难控制了!!
这是一大难点之一,可以说,学习了数据结构那么久了,头一次遇到涉及三个结构体的事情!!可以说,是当时的理解天花板了!
另外,还有一个小插曲,需要提及一下子,那就是,在具体实现的环节当中,额外需要注意传地址的操作!!因为还是老话常谈, 防止出了作用域就销毁了!要是不明白,部分老友可以看看我之前推出的单链表期刊,那里有对这一模块的详细的解析!!
好了,各位老友!!现在让我们接着往下走!!另一个难点
-----> “倒数据”
这模块,我打算再放一个模块,一块讲解!!毕竟,只要能想明白,栈区的原理同队列的原理正好相反,就不会迷糊了!!当时,我就糊涂了!
在倒数据的过程中,由于开始是由队列进行的入数据!!那么,想要实现栈区原理 “后进先出”
我们需要,再找一个空队列用来接收,有数据的队列的头部出来的数据!!而这队列头部出来的数据 --->
显然是,队列按照时间次序出来的一个个数据!!最终会只剩下一个元素,而我们需要的就是这最后一个元素
此后不断重复循环操作即可!!
而这便是核心与精华!!具体的实现,写一个循环不断导数据到空队列即可,只剩下最后一个元素的时候,就可以停止循环了(跳出)另外,一些细节,比如我们根本不知道q1, q2 这两个队列哪一个才是空队列,因为一个有数据的队列,在导数据的过程中(包含弹出部分将最后一个元素弹出并返回),该队列总有全部导空的结果,那么角色便互换了。这就需要我们进行细心化的 if 条件判断了!!这样逻辑上更加严谨了!!
而接下来的模块,也是换汤不换药!!其思想逻辑,也是时刻遵循队列原理转化为栈区原理
多啰嗦一些,还是解释一下比较好!
首先,我们需要判空,如果其中的一个队列不为空。那么,我们就要从队尾出数据,毕竟队尾的数据是最后进入的。如果该元素出来了,不就是成了 “后进先出”了吗!这样便是符合栈区的原理了!
另外,还需要讲解一个模块,容易出错,而且OJ测评检查不出来的!!那便是内存泄露!!
“面试官经常注意的,而我们容易忽视的,或者对什么是 ‘内存泄露’ 糊涂的”😊
这就是最后一个代码模块 “销毁”环节:
各位老友,这就要回到我们一开始讲解模块难点的时候,对结构体的理解了!!其实最难的并不是逻辑,而是基础
很多时候,都是对基础知识的理解不到位,才产生各种各样的问题!!一开始,我就用了指针指向 q1, q2
后来发现,这条路走偏了许多!!最终寸步难行!!
而这里,销毁我们创建的队列式的栈区,并能忘记了,结构体队列里的 Queue 成员,需要将它们 指向NULL,防止演化成野指针!! 所以说,QueueDestroy(&obj ->q) 并不可少!!
当然了,彩色代码与我们这里的现代码,有一些出入,那是因为,我偷懒了,将QueueDestroy更改成了QDestroy
至此,本期的 OJ 题就已经结束了!!欢迎各位老友踊跃发言!!感谢阅读!!😊