目录
前言
在我们分别学习了栈和队列之后,接下就看几道相关的OJ题吧!
代码中都附有相应的注释哦!
1.队列实现栈
思路:
- 使用两个队列,一个先存放数据,另一个保持为空;
- 当需要出栈操作时,先将存放数据的队列中的除开队尾的数据,都存放到空队列中,再对原先存放数据的队列进行出队列操作,以此来模拟出栈。
- 当需要入栈操作时,就对不为空的队列中进行入队列操作。
- 销毁时,要先销毁队列,因为这里队列是链表实现的,如果只释放obj的话,两个队列链表是不会被释放的。
- 注意:要始终保持一个队列为空,防止混淆。
- 此方法中,模拟的栈的栈顶就是有数据的队列的队尾。
代码:
//获取两个队列
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&(pst->q1));
QueueInit(&(pst->q2));
return pst;
}
void myStackPush(MyStack* obj, int x) {
//给不为空的队列里push数据
if(!QueueEmpty(&(obj->q1)))
{
QueuePush(&(obj->q1), x);
}
else
{
QueuePush(&(obj->q2), x);
}
}
int myStackPop(MyStack* obj) {
//先假设q1队列为空
Queue* empty = &(obj->q1);
Queue* noempty = &(obj->q2);
//利用判断语句,检验上述假设是否成立,不成立就改变赋值结果
if(!QueueEmpty(&(obj->q1)))
{
empty = &(obj->q2);
noempty = &(obj->q1);
}
//移动size-1个数据到空队列中
while(QueueSize(noempty) > 1)
{
//将此时不为空的队列中的队头数据取出,入队列到空队列中
QueuePush(empty, QueueFront(noempty));
//将本此入队列到空队列的数据,在原本的队列中删除
QueuePop(noempty);
}
//获取栈顶元素,此时的不为空队列只剩下队尾的数据
//此时将其取出就相当于模仿了栈的后进先出
int ret = QueueFront(noempty);
QueuePop(noempty);
return ret;
}
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);
}
需要注意的是,在上述代码以外,由于本题我们使用的是C语言解题,所以使用的队列Queue是需要我们自己实现的,可以将之前讲解队列的博客「数据结构」队列-CSDN博客中的,实现队列的代码提前拷贝到题目中。
2.栈实现队列
思路:
1.数组实现栈;
2.创建一个结构体统一管理两个栈(pushST和popST);
3.入队列操作:
入队列是从队尾进入队列中的,所以使用栈pushST可以直接模拟操作,直接将数据插入到pushST即可。
4.出队列操作:
a) 出队列操作是从队头出数据,所以只是用pushST无法模拟,将pushST中的数据一个一个出栈,然后入栈到popST中;
b) 再在popST中进行出栈,即可模拟出队列的操作,之后再出栈就直接对popST进行出栈操作即可。
c) 图解:
5.只有当popST为空之后,要再进行出队列操作时,再进行倒数据操作,将pushST中的数据倒置于popST,再模拟出队列;popST不为空时,不能进行倒数据的操作。
6.而要入队列操作时,直接向pushST里入栈数据即可,这样才不会打乱模拟的队列。
代码:
//包含两个栈
typedef struct {
ST pushST;
ST popST;
} MyQueue;
MyQueue* myQueueCreate() {
//动态申请的空间才能保存下来,如果直接创建变量,将导致是一个局部变量,无法返回
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
STInit(&(obj->pushST));
STInit(&(obj->popST));
return obj;
}
//直接向pushST中插入数据
void myQueuePush(MyQueue* obj, int x) {
STPush(&(obj->pushST), x);
}
int myQueuePop(MyQueue* obj) {
//调用Peek函数得到队头元素
int front = myQueuePeek(obj);
//直接出队列
STPop(&(obj->popST));
return front;
}
//返回队头数据
int myQueuePeek(MyQueue* obj) {
//判断popST是否为空,为空才能倒数据
if(STEmpty(&(obj->popST)))
{
//倒数据
while(!STEmpty(&(obj->pushST)))
{
//获取pushST中的栈顶数据
int top = STTop(&(obj->pushST));
//将这个栈顶数据放到popST中
STPush(&(obj->popST), top);
//删除pushST中的栈顶数据
STPop(&(obj->pushST));
}
}
//popST的栈顶,就相当于模拟的队列的队头,所以直接返回popST的栈顶元素即可
int ret = STTop(&(obj->popST));
return ret;
}
bool myQueueEmpty(MyQueue* obj) {
//两个栈都为空时代表模拟的队列为空
return STEmpty(&(obj->pushST)) && STEmpty(&(obj->popST));
}
void myQueueFree(MyQueue* obj) {
STDestroy(&(obj->popST));
STDestroy(&(obj->pushST));
free(obj);
}
注意,和上一题队列实现栈一样,我们需要提前写好栈数据结构的代码放到本题题解的前面备用。
相关实现代码可以到博客「数据结构」栈-CSDN博客中提取。
3.循环队列
思路:
1.使用数组实现(因为循环队列的大小固定,所以可以使用数组);
2.用head变量来表示头下标位置,tail变量表示尾元素的下一个位置,k为队列可使用的长度;
3.在创建数组时,动态申请空间申请k+1个空间,多申请一个空间,用于分辨循环队列为满和为空的情况;
若没有多的一个空间,tail == head同时代表了队列为空和队列为满的两种状态。
a) 当可用空间满时:(tail+1)%(k+1)== head;
b) 当tail == head,说明队列为空;
代码:
typedef struct {
int* a;
int head;//头下标和尾下标
int tail;
int k;//循环队列大小
} MyCircularQueue;
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
//初始化
MyCircularQueue* myCircularQueueCreate(int k) {
//先创建一个循环链表的结构体
MyCircularQueue* que = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
// if(que == NULL)
// {
// perror("malloc fail!");
// return NULL;
// }
//申请循环链表存储数据的数组,空间多申请一个
que->a = (int*)malloc(sizeof(int) * (k + 1));
que->head = que->tail = 0;
que->k = k;
return que;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//判断队列是否为满,满了不能插入数据,返回false
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->tail] = value;
obj->tail++;
obj->tail %= (obj->k + 1);
//tail指向的是最后一个数据的下一个位置
//当tall为k+1时,它就已经超出了队列的下标范围,所以通过模的方式使其实现循环
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//先判空,若为空就返回false
if(myCircularQueueIsEmpty(obj))
return false;
else
{
obj->head++;//直接将头指针往前走一个位置,访问不到就相当于删除数据
obj->head %= (obj->k + 1);//同过模k+1的方式,使其实现循环
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
//队列若为空返回-1
if(myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->head];
}
int myCircularQueueRear(MyCircularQueue* obj) {
//判空
if(obj->head == obj->tail)
return -1;
else//需要考虑tail的取值范围问题
return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k +1)];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
if(obj->head == obj->tail)
return true;
else
return false;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
if((obj->tail + 1) % (obj->k + 1) == obj->head)
return true;
else
return false;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
注意:
在获取队尾数据函数中的返回条件 return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k +1)] 解释:
后记
本篇讲解的3道OJ题理解上都有一些难度,大家可以多结合图解理解,可以自己动手画一画图,不要偷懒喔~
那么本篇就到这里了,如果看了图解和注释还有不好理解的地方,大家可以在评论区或者私信提问哦!
我们下篇再见!