栈和队列的习题详解(2):用队列实现栈
前言:
小编在上一篇博客写了栈和队列其中一个习题,为了体现出题目的重要性所以我把每个题目都分开写了,下面废话不多说,开启我们今天的做题之旅~
目录
1.用队列实现栈
1.1.题目介绍
1.2.做题方法介绍
1.3.栈功能的实现
1.3.1.栈的初始化(MyStack* myStackCreate())
1.3.2.入栈操作(myStackPush(MyStack* obj, int x))
1.3.3.判断栈是否为空(bool myStackEmpty(MyStack* obj))
1.3.4.出栈操作(void myStackPush(MyStack* obj, int x))
1.3.5.取栈顶操作( myStackTop(MyStack* obj))
1.3.6.栈的销毁(void myStackFree(MyStack* obj))
1.4.代码展示
2.总结
正文:
1.用队列实现栈
老规矩,先展示一下这个题目:225. 用队列实现栈 - 力扣(LeetCode)
1.1.题目介绍
这个题目的要求是让我们用队列实现栈,到这里可能有很多读者朋友会疑惑,栈和队列我们之前也实现过,我们也知道他俩是截然不同的两个结构,栈是后进先出的结构,队列是先进先出的结构,可这题却想让我们用两个队列实现一个栈,小编当时对这个题是只有一点思路的,我当时想着把两个队列“合并起来”,让一个队列负责出数据,一个队列负责进数据,之后我就没思路了,于是我就开始听取老师的讲解,在听完讲解后,我觉着这个思路也是蛮厉害的,下面小编就先给各位读者朋友讲述一下这个题目的其中一个解题方法~
1.2.做题方法介绍
此时题目给我们的就是两个队列,小编这个做题方法简单来说就是来回倒数据;首先,我们需要直接把队列我们实现的功能复制到这个题目来,然后我们需要先设置好我们的自己栈的结构体,由题目我们就可以知道我们仅需创建两个队列就好;然后我们就要进行栈的初始化操作,这里我们首先要动态开辟一个栈类型的指针,然后我们在把栈里面两个队列初始化就好了,此时就实现了初始化操作;然后就是入栈操作,此时就是涉及到了倒数据,我们可以在进行入栈操作的时候,先把数据放入一个不为空的队列;然后在想要出栈的时候,我们可以让不为空的队列里面除了最后一个数据全部放入到空的队列,此时涉及到了队列的取队头操作和出队列操作,之后我们保存好最后一个元素的数据后,在进行出队列操作,此时我们就可以实现出栈操作;当然对于栈,我们还有取栈顶的操作,此时我们可以直接返回不为空队列的队尾元素,此时的队尾其实就是栈的栈顶,毕竟栈顶是最后一个进入的数据;当然,我们在进行出栈或者取栈顶操作的时候,还需要去判断这个栈是否为空,所以我们需要写一个判断函数,此时这个判断函数就是判断两个队列是不是全空的,如果是全空的,则可以说这个栈是没有数据的,我们就不可以执行这两个操作,至于为什么是全空,小编在前面说过,我们插入是往不为空的队列插入数据,我们需要保证一个队列是空的,这样才可以进行出栈操作;最后,我们肯定要写栈的销毁操作,这里我们直接调用队列的销毁操作把两个队列销毁后,然后我们在把新创建的自己的栈结构free掉然后设置为空指针,这样我们便可以写完这个题目,肯定很多读者朋友和我一样,知道了思路但是代码不太会打,下面小编将讲述这些功能的代码实现!
1.3.栈功能的实现
在写功能之前,小编先把这个结构体的内容代码先呈现出来,防止各位读者朋友不清楚:
typedef struct {
Queue q1;
Queue q2;
} MyStack;
1.3.1.栈的初始化(MyStack* myStackCreate())
初始化操作刚才小编也简单的说了一下,由于此时的函数的返回值是MyStack*,意味着我们需要新建一个栈类型的指针然后返回指针,所以此时我们就用到了动态内存的知识,我们需要动态开辟一块栈类型的空间,创建完以后我们需要对里面的内容进行初始化,对于里面的初始化,我们直接去调用它自己的初始化函数就好,之后我们就完成了栈的初始化,然后返回新创立的指针即可,代码如下图所示:
MyStack* myStackCreate() {
MyStack* pq = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pq -> q1);
QueueInit(&pq -> q2);
return pq;
}
1.3.2.入栈操作(myStackPush(MyStack* obj, int x))
对于入栈操作,我们需要把我们想要的元素放入到一个不为空的队列中,我们需要留出一个空的队列来进行倒数据操作,所以此时我们需要用到if语句来帮助我们去实现寻找不为空的队列,此时我们可以通过我们在写队列时查询队列元素是否为空的函数来帮助我们寻找,我们直接先查看第一个队列是否为空,如果不为空的话,我们直接把数据放入到第二个队列,这里是要用if else语句,千万不要用两个if语句,因为刚开始的时候两个队列都是空的,如果此时用两个if语句我们就无法入队列了,之后我们寻找到不为空的队列以后,我们可以调用入队列的函数来实现数据进入队列,此时我们就完成了入栈操作,下面是代码展示图:
void myStackPush(MyStack* obj, int x) {
if(!Queuepanduan(&obj -> q1))
QueuePush(&obj -> q1,x);
else
{
QueuePush(&obj -> q2,x);
}
}
1.3.3.判断栈是否为空(bool myStackEmpty(MyStack* obj))
在写出栈操作的时候,我们首先需要判断栈中是否有元素,对于此函数,小编在前面也简单说过,此时如果栈中是有元素的,那么其中一个队列指定是有元素的,所以此时判空的条件就是两个队列均为空,此时就说明了这个栈是空的,下面直接展示代码:
bool myStackEmpty(MyStack* obj) {
return Queuepanduan(&obj ->q1 ) && Queuepanduan(&obj -> q2);
}
1.3.4.出栈操作(void myStackPush(MyStack* obj, int x))
出栈操作算是一个栈功能代码不太容易写的函数了,前面也说过,这里我们就涉及到了倒数据这一个操作,首先我们需要先找到不为空的队列和空的队列,此时我们也不知道是哪一个队列是空的,所以小编给出的操作就是我们先默认设置两个队列结构体指针,第一个队列是不空队列指针,第二个队列是空队列指针,之后我们在通过if语句来判断第一个队列是否为空,如果为空我们在把让不为空队列是第二个,空为第一个,这里我们就完成了寻找不为空队列的操作,之后我们就要通过循环,来开始让不为空队列的数据转移到空队列中,对于这方面的操作小编就通过图文来进行更好的解释,此时我们先默认不为空里面的元素是1,2,3。
刚开始,我们先通过取栈顶的操作设置一个新的变量来存储队头数据,之后我们把队头的数据放入到空的队列中,之后我们在进行出队列操作,此时空队列已经有1这个数据了,如下图所示:
之后我们继续重复进行上面的操作,直到原来的不为空队列中只有1个数据,如下图所示:
此时左边队列中仅存的一个数据,这个数据就是最后一个进入队列的数据,自然也就是栈顶数据,因为栈是后进先出,最后一个进的数据第一个出来,所以此时我们先设置一个变量保存这个数据,之后直接让这个数据出队列,然后返回这个变量就好了,代码如下图所示:
int myStackPop(MyStack* obj) {
assert(!myStackEmpty(obj));
Queue* empty = &obj -> q1; //空队列
Queue* nonety = &obj -> q2;
if(!Queuepanduan(&obj -> q1))
{
empty = &obj -> q2;
nonety = &obj -> q1;
}
while(QueueSize(nonety) > 1)
{
int front = QueueFront(nonety);
QueuePush(empty,front);
QueuePop(nonety);
}
int a = QueueFront(nonety);
QueuePop(nonety);
return a;
}
1.3.5.取栈顶操作( myStackTop(MyStack* obj))
取栈顶操作相比于上面的操作算是比较简单的了,可能很多读者朋友写这个函数的时候,会巧妙的把上面出栈操作的函数复制过来,因为出栈操作的最后有一个队列是保存着栈顶的元素,我们不在进行出栈操作,直接返回栈顶元素就完成了这个函数,乍一看,这么说还真是没毛病,但是,还记着小编在开头说过的话吗,我们这个题目的主旨就是倒数据实现出栈操作,必须有一个队列是空的,此时如果这么操作的话,两个队列都会有数据,我们在进行入栈操作的时候会随机往其中一个队列放入数据,此时这个题目就乱套了,我们在出栈操作的时候两个队列都有数据,我们就无法在实现倒数据的操作了,所以这个做法是错误的,小编当时也是犯了这样一个错误,找了好久才直到错因,下面小编说一下这个操作的正常实现流程:
其实我们如果想要取栈顶元素,无非就是想要寻找队列最后一个元素,此时我们就用到了我们在写队列的时候写过的取队尾数据,此时我们可以直接取不为空队列的队尾,此时的队尾就是栈顶元素,所以此时我们还需要寻找不为空的队列,寻找方法和入栈操作的寻找方式是一样的,当然,在进行这一切操作的前提下,就是这个栈是不为空的,所以我们还要判断栈是否为空,此时我们就成功的实现了取栈顶操作,仔细看看小编说的,其实这个函数难度不大,各位读者朋友可千万不要犯上面的错误,下面小编将展示代码:
int myStackTop(MyStack* obj) {
assert(!myStackEmpty(obj));
if(!Queuepanduan(&obj -> q1))
{
return QueueBack(&obj -> q1);
}
else
{
return QueueBack(&obj -> q2);
}
}
1.3.6.栈的销毁(void myStackFree(MyStack* obj))
栈的销毁操作其实就是把我们开辟的空间给free掉,对于栈中的队列,我们可以采取用他们各自的销毁操作来实现,在销毁完以后,不要忘记我们的obj也是动态开辟出来的,此时我们需要free掉obj,然后让它指向空就好,养成好习惯,尽量不要写出野指针,此时销毁操作也是完成了,下面上代码:
void myStackFree(MyStack* obj) {
QueueDestroy(&obj -> q1);
QueueDestroy(&obj -> q2);
free(obj);
obj = NULL;
}
此时我们已经实现了全部的功能,肯定有读者朋友想要知道这个题的代码怎么写,下面小编将会展示这个题目的完整代码。
1.4.代码展示
typedef int QDataType;
typedef struct list { //表示单链表
QDataType data;
struct list* next;
}list;
typedef struct Queue {
list* phead;
list* plist;
int size;
}Queue;
void QueueInit(Queue* pq)
{
pq->phead = pq->plist = NULL;
pq->size = 0;
}
list* Queuebuynode(x)
{
list* p1 = (list*)malloc(sizeof(list));
assert(p1);
p1->next = NULL;
p1->data = x;
return p1; //别忘记返回新节点,tnnd我记着单链表我也是这样错的
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
if (pq->phead == NULL) //先判断头结点是不是存在的
{
pq->phead = pq->plist = Queuebuynode(x);
}
else
{
list* newnode = Queuebuynode(x);
pq->plist->next = newnode;
pq->plist = newnode;
}
pq->size++;
}
bool Queuepanduan(Queue* pq)
{
assert(pq);
return pq->phead == NULL && pq -> plist == NULL;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!Queuepanduan(pq));
if (pq->phead == pq->plist)
{
free(pq ->phead);
pq->phead = pq->plist = NULL;
}
else
{
list* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq-> size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!Queuepanduan(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!Queuepanduan(pq));
return pq->plist->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
//assert(!Queuepanduan(pq));
list* p1 = pq -> phead;
while (p1)
{
list* next = p1 -> next;
free(p1);
p1 = next;
}
pq->phead = pq->plist = NULL;
pq->size = 0;
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* pq = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pq -> q1);
QueueInit(&pq -> q2);
return pq;
}
bool myStackEmpty(MyStack* obj) {
return Queuepanduan(&obj ->q1 ) && Queuepanduan(&obj -> q2);
}
void myStackPush(MyStack* obj, int x) {
if(!Queuepanduan(&obj -> q1))
QueuePush(&obj -> q1,x);
else
{
QueuePush(&obj -> q2,x);
}
}
int myStackPop(MyStack* obj) {
assert(!myStackEmpty(obj));
Queue* empty = &obj -> q1; //空队列
Queue* nonety = &obj -> q2;
if(!Queuepanduan(&obj -> q1))
{
empty = &obj -> q2;
nonety = &obj -> q1;
}
while(QueueSize(nonety) > 1)
{
int front = QueueFront(nonety);
QueuePush(empty,front);
QueuePop(nonety);
}
int a = QueueFront(nonety);
QueuePop(nonety);
return a;
}
int myStackTop(MyStack* obj) {
assert(!myStackEmpty(obj));
if(!Queuepanduan(&obj -> q1))
{
return QueueBack(&obj -> q1);
}
else
{
return QueueBack(&obj -> q2);
}
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj -> q1);
QueueDestroy(&obj -> q2);
free(obj);
obj = NULL;
}
2.总结
以上便就是这个题目的解题流程,这个题目很重要,各位读者朋友一定要去好好的理解,可能现在读者朋友会很好奇,我们都可以用队列实现了栈了,那么我们是否可以在用栈实现队列呢?答案是肯定的,小编准备下一篇博客就讲述用栈实现队列,大家敬请期待,小编算是一个编程界的小白,如果文章由错误,大家可以在评论区指出,小编一定会及时回答并更正,那么,我们下一篇文章见啦!