各位老友,欢迎造访本期博客!!

前段时间,我们已经学习了,怎样实现栈区,队列!

今天,正是我们将前段时间学习的知识,进行迁移!!

奥!银河啊!!这句“知识的迁移”勾起了,我的高中物理课···

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_01)_用队列实现栈


上述代码,是在OJ上运行的,已经通过!!感兴趣的好友,可以尝试一下!!

接下来,开始解析相关代码了,毕竟当时的理解,就有些挑战了!!

首先,在本次 OJ 题,用到了,嵌套结构体,其中匿名的部分,当时就掉进坑里了!

为了,方便解说,现在附上图示样解:

数据结构-->用队列实现栈(OJ_01)_三个结构体嵌套_02

请注意看,q1, q2 是两个变量,究竟属于谁?

---> 

数据结构-->用队列实现栈(OJ_01)_三个结构体嵌套_03

显然,q1, q2 与 Queue 这个结构体关系最为密切!!而如今,我们的思路是 :

通过创建两个队列,来实现栈的特征!!这可是题目要求啊!!千万别混淆了!!

所以说,q1, q2 是作为结构体 Queue 的两个字队列,注意,千万别理解成,成员关系了!!

要是理解成 成员关系,那就需要指针的指向了,这个时候初始化以及具体实现的时候,需要用到二级指针了!

显然,最终,我传疯狂了!! 太难控制了!!

这是一大难点之一,可以说,学习了数据结构那么久了,头一次遇到涉及三个结构体的事情!!可以说,是当时的理解天花板了!

另外,还有一个小插曲,需要提及一下子,那就是,在具体实现的环节当中,额外需要注意传地址的操作!!因为还是老话常谈, 防止出了作用域就销毁了!要是不明白,部分老友可以看看我之前推出的单链表期刊,那里有对这一模块的详细的解析!!

好了,各位老友!!现在让我们接着往下走!!另一个难点

-----> “倒数据”

数据结构-->用队列实现栈(OJ_01)_三个结构体嵌套_04


这模块,我打算再放一个模块,一块讲解!!毕竟,只要能想明白,栈区的原理同队列的原理正好相反,就不会迷糊了!!当时,我就糊涂了!


数据结构-->用队列实现栈(OJ_01)_三个结构体嵌套_05

在倒数据的过程中,由于开始是由队列进行的入数据!!那么,想要实现栈区原理 “后进先出”

我们需要,再找一个空队列用来接收,有数据的队列的头部出来的数据!!而这队列头部出来的数据 --->

显然是,队列按照时间次序出来的一个个数据!!最终会只剩下一个元素,而我们需要的就是这最后一个元素

此后不断重复循环操作即可!!

而这便是核心与精华!!具体的实现,写一个循环不断导数据到空队列即可,只剩下最后一个元素的时候,就可以停止循环了(跳出)另外,一些细节,比如我们根本不知道q1, q2 这两个队列哪一个才是空队列,因为一个有数据的队列,在导数据的过程中(包含弹出部分将最后一个元素弹出并返回),该队列总有全部导空的结果,那么角色便互换了。这就需要我们进行细心化的 if 条件判断了!!这样逻辑上更加严谨了!!

接下来的模块,也是换汤不换药!!其思想逻辑,也是时刻遵循队列原理转化为栈区原理

多啰嗦一些,还是解释一下比较好!

首先,我们需要判空,如果其中的一个队列不为空。那么,我们就要从队尾出数据,毕竟队尾的数据是最后进入的。如果该元素出来了,不就是成了 “后进先出”了吗!这样便是符合栈区的原理了!

另外,还需要讲解一个模块,容易出错,而且OJ测评检查不出来的!!那便是内存泄露!!

“面试官经常注意的,而我们容易忽视的,或者对什么是 ‘内存泄露’ 糊涂的”😊

这就是最后一个代码模块 “销毁”环节:

数据结构-->用队列实现栈(OJ_01)_用队列实现栈_06


各位老友,这就要回到我们一开始讲解模块难点的时候,对结构体的理解了!!其实最难的并不是逻辑,而是基础

很多时候,都是对基础知识的理解不到位,才产生各种各样的问题!!一开始,我就用了指针指向 q1, q2

后来发现,这条路走偏了许多!!最终寸步难行!!

而这里,销毁我们创建的队列式的栈区,并能忘记了,结构体队列里的 Queue 成员,需要将它们 指向NULL,防止演化成野指针!! 所以说,QueueDestroy(&obj ->q) 并不可少!!

当然了,彩色代码与我们这里的现代码,有一些出入,那是因为,我偷懒了,将QueueDestroy更改成了QDestroy

至此,本期的 OJ 题就已经结束了!!欢迎各位老友踊跃发言!!感谢阅读!!😊